From de7f683ff9e070ca19565373c546d736663e5512 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 4 Aug 2026 15:35:29 +0200 Subject: [PATCH 01/91] tty: moxa: remove driver. This is a very old driver, and no known hardware is still around for it, and the company says they do not need it anymore, so let's remove it as the LLMs are starting to poke at it and find "interesting" things which will just waste everyone's time given it's not actually used. So let's drop it. If someone really needs it, it can come back in the future, in a more "modern" way, as the style here is very old... Reported-by: Jiri Slaby Link: https://lore.kernel.org/all/afqyY8FQ8ZfpH48_@moxa-ThinkCentre-M90t/ Acked-by: Crescent Hsieh Acked-by: Jiri Slaby Link: https://patch.msgid.link/20260804-x-remove-moxa-v2-1-947c039b66e8@linuxfoundation.org Signed-off-by: Greg Kroah-Hartman --- Documentation/admin-guide/devices.txt | 13 +- MAINTAINERS | 2 +- drivers/tty/Kconfig | 10 - drivers/tty/Makefile | 1 - drivers/tty/moxa.c | 2136 ------------------------- 5 files changed, 3 insertions(+), 2159 deletions(-) delete mode 100644 drivers/tty/moxa.c diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt index f544399675fa..855f2cad0535 100644 --- a/Documentation/admin-guide/devices.txt +++ b/Documentation/admin-guide/devices.txt @@ -2388,18 +2388,9 @@ 171 char Reserved for IEEE 1394 (Firewire) - 172 char Moxa Intellio serial card - 0 = /dev/ttyMX0 First Moxa port - 1 = /dev/ttyMX1 Second Moxa port - ... - 127 = /dev/ttyMX127 128th Moxa port - 128 = /dev/moxactl Moxa control port + 172 char - 173 char Moxa Intellio serial card - alternate devices - 0 = /dev/cumx0 Callout device for ttyMX0 - 1 = /dev/cumx1 Callout device for ttyMX1 - ... - 127 = /dev/cumx127 Callout device for ttyMX127 + 173 char 174 char SmartIO serial card 0 = /dev/ttySI0 First SmartIO port diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c..b5833db19ba8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18420,7 +18420,7 @@ F: Documentation/devicetree/bindings/net/dsa/motorcomm,yt921x.yaml F: drivers/net/dsa/yt921x.* F: net/dsa/tag_yt921x.c -MOXA SMARTIO/INDUSTIO/INTELLIO SERIAL CARD +MOXA SMARTIO/INDUSTIO SERIAL CARD M: Jiri Slaby S: Maintained F: Documentation/driver-api/tty/moxa-smartio.rst diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index 5d6b0dd4d423..2ed4f1e0e9ef 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -208,16 +208,6 @@ config SERIAL_NONSTANDARD Most people can say N here. -config MOXA_INTELLIO - tristate "Moxa Intellio support" - depends on SERIAL_NONSTANDARD && PCI - select FW_LOADER - help - Say Y here if you have a Moxa Intellio multiport serial card. - - To compile this driver as a module, choose M here: the - module will be called moxa. - config MOXA_SMARTIO tristate "Moxa SmartIO support v. 2.0" depends on SERIAL_NONSTANDARD && PCI && HAS_IOPORT diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index c6a063a541fd..43aa3f93b09a 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -17,7 +17,6 @@ obj-$(CONFIG_SERIAL_DEV_BUS) += serdev/ # tty drivers obj-$(CONFIG_AMIGA_BUILTIN_SERIAL) += amiserial.o -obj-$(CONFIG_MOXA_INTELLIO) += moxa.o obj-$(CONFIG_MOXA_SMARTIO) += mxser.o obj-$(CONFIG_NOZOMI) += nozomi.o obj-$(CONFIG_NULL_TTY) += ttynull.o diff --git a/drivers/tty/moxa.c b/drivers/tty/moxa.c deleted file mode 100644 index 40a1c614e23b..000000000000 --- a/drivers/tty/moxa.c +++ /dev/null @@ -1,2136 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/*****************************************************************************/ -/* - * moxa.c -- MOXA Intellio family multiport serial driver. - * - * Copyright (C) 1999-2000 Moxa Technologies (support@moxa.com). - * Copyright (c) 2007 Jiri Slaby - * - * This code is loosely based on the Linux serial driver, written by - * Linus Torvalds, Theodore T'so and others. - */ - -/* - * MOXA Intellio Series Driver - * for : LINUX - * date : 1999/1/7 - * version : 5.1 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* - * System Configuration - */ - -#define Magic_code 0x404 - -/* - * for C218 BIOS initialization - */ -#define C218_ConfBase 0x800 -#define C218_status (C218_ConfBase + 0) /* BIOS running status */ -#define C218_diag (C218_ConfBase + 2) /* diagnostic status */ -#define C218_key (C218_ConfBase + 4) /* WORD (0x218 for C218) */ -#define C218DLoad_len (C218_ConfBase + 6) /* WORD */ -#define C218check_sum (C218_ConfBase + 8) /* BYTE */ -#define C218chksum_ok (C218_ConfBase + 0x0a) /* BYTE (1:ok) */ -#define C218_TestRx (C218_ConfBase + 0x10) /* 8 bytes for 8 ports */ -#define C218_TestTx (C218_ConfBase + 0x18) /* 8 bytes for 8 ports */ -#define C218_RXerr (C218_ConfBase + 0x20) /* 8 bytes for 8 ports */ -#define C218_ErrFlag (C218_ConfBase + 0x28) /* 8 bytes for 8 ports */ - -#define C218_LoadBuf 0x0F00 -#define C218_KeyCode 0x218 -#define CP204J_KeyCode 0x204 - -/* - * for C320 BIOS initialization - */ -#define C320_ConfBase 0x800 -#define C320_LoadBuf 0x0f00 -#define STS_init 0x05 /* for C320_status */ - -#define C320_status C320_ConfBase + 0 /* BIOS running status */ -#define C320_diag C320_ConfBase + 2 /* diagnostic status */ -#define C320_key C320_ConfBase + 4 /* WORD (0320H for C320) */ -#define C320DLoad_len C320_ConfBase + 6 /* WORD */ -#define C320check_sum C320_ConfBase + 8 /* WORD */ -#define C320chksum_ok C320_ConfBase + 0x0a /* WORD (1:ok) */ -#define C320bapi_len C320_ConfBase + 0x0c /* WORD */ -#define C320UART_no C320_ConfBase + 0x0e /* WORD */ - -#define C320_KeyCode 0x320 - -#define FixPage_addr 0x0000 /* starting addr of static page */ -#define DynPage_addr 0x2000 /* starting addr of dynamic page */ -#define C218_start 0x3000 /* starting addr of C218 BIOS prg */ -#define Control_reg 0x1ff0 /* select page and reset control */ -#define HW_reset 0x80 - -/* - * Function Codes - */ -#define FC_CardReset 0x80 -#define FC_ChannelReset 1 /* C320 firmware not supported */ -#define FC_EnableCH 2 -#define FC_DisableCH 3 -#define FC_SetParam 4 -#define FC_SetMode 5 -#define FC_SetRate 6 -#define FC_LineControl 7 -#define FC_LineStatus 8 -#define FC_XmitControl 9 -#define FC_FlushQueue 10 -#define FC_SendBreak 11 -#define FC_StopBreak 12 -#define FC_LoopbackON 13 -#define FC_LoopbackOFF 14 -#define FC_ClrIrqTable 15 -#define FC_SendXon 16 -#define FC_SetTermIrq 17 /* C320 firmware not supported */ -#define FC_SetCntIrq 18 /* C320 firmware not supported */ -#define FC_SetBreakIrq 19 -#define FC_SetLineIrq 20 -#define FC_SetFlowCtl 21 -#define FC_GenIrq 22 -#define FC_InCD180 23 -#define FC_OutCD180 24 -#define FC_InUARTreg 23 -#define FC_OutUARTreg 24 -#define FC_SetXonXoff 25 -#define FC_OutCD180CCR 26 -#define FC_ExtIQueue 27 -#define FC_ExtOQueue 28 -#define FC_ClrLineIrq 29 -#define FC_HWFlowCtl 30 -#define FC_GetClockRate 35 -#define FC_SetBaud 36 -#define FC_SetDataMode 41 -#define FC_GetCCSR 43 -#define FC_GetDataError 45 -#define FC_RxControl 50 -#define FC_ImmSend 51 -#define FC_SetXonState 52 -#define FC_SetXoffState 53 -#define FC_SetRxFIFOTrig 54 -#define FC_SetTxFIFOCnt 55 -#define FC_UnixRate 56 -#define FC_UnixResetTimer 57 - -#define RxFIFOTrig1 0 -#define RxFIFOTrig4 1 -#define RxFIFOTrig8 2 -#define RxFIFOTrig14 3 - -/* - * Dual-Ported RAM - */ -#define DRAM_global 0 -#define INT_data (DRAM_global + 0) -#define Config_base (DRAM_global + 0x108) - -#define IRQindex (INT_data + 0) -#define IRQpending (INT_data + 4) -#define IRQtable (INT_data + 8) - -/* - * Interrupt Status - */ -#define IntrRx 0x01 /* receiver data O.K. */ -#define IntrTx 0x02 /* transmit buffer empty */ -#define IntrFunc 0x04 /* function complete */ -#define IntrBreak 0x08 /* received break */ -#define IntrLine 0x10 /* line status change - for transmitter */ -#define IntrIntr 0x20 /* received INTR code */ -#define IntrQuit 0x40 /* received QUIT code */ -#define IntrEOF 0x80 /* received EOF code */ - -#define IntrRxTrigger 0x100 /* rx data count reach trigger value */ -#define IntrTxTrigger 0x200 /* tx data count below trigger value */ - -#define Magic_no (Config_base + 0) -#define Card_model_no (Config_base + 2) -#define Total_ports (Config_base + 4) -#define Module_cnt (Config_base + 8) -#define Module_no (Config_base + 10) -#define Timer_10ms (Config_base + 14) -#define Disable_IRQ (Config_base + 20) -#define TMS320_PORT1 (Config_base + 22) -#define TMS320_PORT2 (Config_base + 24) -#define TMS320_CLOCK (Config_base + 26) - -/* - * DATA BUFFER in DRAM - */ -#define Extern_table 0x400 /* Base address of the external table - (24 words * 64) total 3K bytes - (24 words * 128) total 6K bytes */ -#define Extern_size 0x60 /* 96 bytes */ -#define RXrptr 0x00 /* read pointer for RX buffer */ -#define RXwptr 0x02 /* write pointer for RX buffer */ -#define TXrptr 0x04 /* read pointer for TX buffer */ -#define TXwptr 0x06 /* write pointer for TX buffer */ -#define HostStat 0x08 /* IRQ flag and general flag */ -#define FlagStat 0x0A -#define FlowControl 0x0C /* B7 B6 B5 B4 B3 B2 B1 B0 */ - /* x x x x | | | | */ - /* | | | + CTS flow */ - /* | | +--- RTS flow */ - /* | +------ TX Xon/Xoff */ - /* +--------- RX Xon/Xoff */ -#define Break_cnt 0x0E /* received break count */ -#define CD180TXirq 0x10 /* if non-0: enable TX irq */ -#define RX_mask 0x12 -#define TX_mask 0x14 -#define Ofs_rxb 0x16 -#define Ofs_txb 0x18 -#define Page_rxb 0x1A -#define Page_txb 0x1C -#define EndPage_rxb 0x1E -#define EndPage_txb 0x20 -#define Data_error 0x22 -#define RxTrigger 0x28 -#define TxTrigger 0x2a - -#define rRXwptr 0x34 -#define Low_water 0x36 - -#define FuncCode 0x40 -#define FuncArg 0x42 -#define FuncArg1 0x44 - -#define C218rx_size 0x2000 /* 8K bytes */ -#define C218tx_size 0x8000 /* 32K bytes */ - -#define C218rx_mask (C218rx_size - 1) -#define C218tx_mask (C218tx_size - 1) - -#define C320p8rx_size 0x2000 -#define C320p8tx_size 0x8000 -#define C320p8rx_mask (C320p8rx_size - 1) -#define C320p8tx_mask (C320p8tx_size - 1) - -#define C320p16rx_size 0x2000 -#define C320p16tx_size 0x4000 -#define C320p16rx_mask (C320p16rx_size - 1) -#define C320p16tx_mask (C320p16tx_size - 1) - -#define C320p24rx_size 0x2000 -#define C320p24tx_size 0x2000 -#define C320p24rx_mask (C320p24rx_size - 1) -#define C320p24tx_mask (C320p24tx_size - 1) - -#define C320p32rx_size 0x1000 -#define C320p32tx_size 0x1000 -#define C320p32rx_mask (C320p32rx_size - 1) -#define C320p32tx_mask (C320p32tx_size - 1) - -#define Page_size 0x2000U -#define Page_mask (Page_size - 1) -#define C218rx_spage 3 -#define C218tx_spage 4 -#define C218rx_pageno 1 -#define C218tx_pageno 4 -#define C218buf_pageno 5 - -#define C320p8rx_spage 3 -#define C320p8tx_spage 4 -#define C320p8rx_pgno 1 -#define C320p8tx_pgno 4 -#define C320p8buf_pgno 5 - -#define C320p16rx_spage 3 -#define C320p16tx_spage 4 -#define C320p16rx_pgno 1 -#define C320p16tx_pgno 2 -#define C320p16buf_pgno 3 - -#define C320p24rx_spage 3 -#define C320p24tx_spage 4 -#define C320p24rx_pgno 1 -#define C320p24tx_pgno 1 -#define C320p24buf_pgno 2 - -#define C320p32rx_spage 3 -#define C320p32tx_ofs C320p32rx_size -#define C320p32tx_spage 3 -#define C320p32buf_pgno 1 - -/* - * Host Status - */ -#define WakeupRx 0x01 -#define WakeupTx 0x02 -#define WakeupBreak 0x08 -#define WakeupLine 0x10 -#define WakeupIntr 0x20 -#define WakeupQuit 0x40 -#define WakeupEOF 0x80 /* used in VTIME control */ -#define WakeupRxTrigger 0x100 -#define WakeupTxTrigger 0x200 -/* - * Flag status - */ -#define Rx_over 0x01 -#define Xoff_state 0x02 -#define Tx_flowOff 0x04 -#define Tx_enable 0x08 -#define CTS_state 0x10 -#define DSR_state 0x20 -#define DCD_state 0x80 -/* - * FlowControl - */ -#define CTS_FlowCtl 1 -#define RTS_FlowCtl 2 -#define Tx_FlowCtl 4 -#define Rx_FlowCtl 8 -#define IXM_IXANY 0x10 - -#define LowWater 128 - -#define DTR_ON 1 -#define RTS_ON 2 -#define CTS_ON 1 -#define DSR_ON 2 -#define DCD_ON 8 - -/* mode definition */ -#define MX_CS8 0x03 -#define MX_CS7 0x02 -#define MX_CS6 0x01 -#define MX_CS5 0x00 - -#define MX_STOP1 0x00 -#define MX_STOP15 0x04 -#define MX_STOP2 0x08 - -#define MX_PARNONE 0x00 -#define MX_PAREVEN 0x40 -#define MX_PARODD 0xC0 -#define MX_PARMARK 0xA0 -#define MX_PARSPACE 0x20 - -#define MOXA_FW_HDRLEN 32 - -#define MOXAMAJOR 172 - -#define MAX_BOARDS 4 /* Don't change this value */ -#define MAX_PORTS_PER_BOARD 32 /* Don't change this value */ -#define MAX_PORTS (MAX_BOARDS * MAX_PORTS_PER_BOARD) - -#define MOXA_IS_320(brd) ((brd)->boardType == MOXA_BOARD_C320_PCI) - -enum { - MOXA_BOARD_C218_PCI = 1, - MOXA_BOARD_C320_PCI, - MOXA_BOARD_CP204J, -}; - -static char *moxa_brdname[] = -{ - "C218 Turbo PCI series", - "C320 Turbo PCI series", - "CP-204J series", -}; - -static const struct pci_device_id moxa_pcibrds[] = { - { PCI_DEVICE(PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_C218), - .driver_data = MOXA_BOARD_C218_PCI }, - { PCI_DEVICE(PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_C320), - .driver_data = MOXA_BOARD_C320_PCI }, - { PCI_DEVICE(PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP204J), - .driver_data = MOXA_BOARD_CP204J }, - { 0 } -}; -MODULE_DEVICE_TABLE(pci, moxa_pcibrds); - -struct moxa_port; - -static struct moxa_board_conf { - int boardType; - int numPorts; - - unsigned int ready; - - struct moxa_port *ports; - - void __iomem *basemem; - void __iomem *intNdx; - void __iomem *intPend; - void __iomem *intTable; -} moxa_boards[MAX_BOARDS]; - -struct moxa_port { - struct tty_port port; - struct moxa_board_conf *board; - void __iomem *tableAddr; - - int type; - int cflag; - unsigned long statusflags; - - u8 DCDState; /* Protected by the port lock */ - u8 lineCtrl; - u8 lowChkFlag; -}; - -/* statusflags */ -#define TXSTOPPED 1 -#define LOWWAIT 2 -#define EMPTYWAIT 3 - - -#define WAKEUP_CHARS 256 - -static int ttymajor = MOXAMAJOR; -static unsigned int moxaFuncTout = HZ / 2; -static unsigned int moxaLowWaterChk; -static DEFINE_MUTEX(moxa_openlock); -static DEFINE_SPINLOCK(moxa_lock); - -MODULE_AUTHOR("William Chen"); -MODULE_DESCRIPTION("MOXA Intellio Family Multiport Board Device Driver"); -MODULE_LICENSE("GPL"); -MODULE_FIRMWARE("c218tunx.cod"); -MODULE_FIRMWARE("cp204unx.cod"); -MODULE_FIRMWARE("c320tunx.cod"); - -module_param(ttymajor, int, 0); - -/* - * static functions: - */ -static int moxa_open(struct tty_struct *, struct file *); -static void moxa_close(struct tty_struct *, struct file *); -static ssize_t moxa_write(struct tty_struct *, const u8 *, size_t); -static unsigned int moxa_write_room(struct tty_struct *); -static void moxa_flush_buffer(struct tty_struct *); -static unsigned int moxa_chars_in_buffer(struct tty_struct *); -static void moxa_set_termios(struct tty_struct *, const struct ktermios *); -static void moxa_stop(struct tty_struct *); -static void moxa_start(struct tty_struct *); -static void moxa_hangup(struct tty_struct *); -static int moxa_tiocmget(struct tty_struct *tty); -static int moxa_tiocmset(struct tty_struct *tty, - unsigned int set, unsigned int clear); -static void moxa_poll(struct timer_list *); -static void moxa_set_tty_param(struct tty_struct *, const struct ktermios *); -static void moxa_shutdown(struct tty_port *); -static bool moxa_carrier_raised(struct tty_port *); -static void moxa_dtr_rts(struct tty_port *, bool); -/* - * moxa board interface functions: - */ -static void MoxaPortEnable(struct moxa_port *); -static void MoxaPortDisable(struct moxa_port *); -static int MoxaPortSetTermio(struct moxa_port *, struct ktermios *, speed_t); -static int MoxaPortGetLineOut(struct moxa_port *, bool *, bool *); -static void MoxaPortLineCtrl(struct moxa_port *, bool, bool); -static void MoxaPortFlowCtrl(struct moxa_port *, int, int, int, int, int); -static int MoxaPortLineStatus(struct moxa_port *); -static void MoxaPortFlushData(struct moxa_port *, int); -static ssize_t MoxaPortWriteData(struct tty_struct *, const u8 *, size_t); -static int MoxaPortReadData(struct moxa_port *); -static unsigned int MoxaPortTxQueue(struct moxa_port *); -static int MoxaPortRxQueue(struct moxa_port *); -static unsigned int MoxaPortTxFree(struct moxa_port *); -static void MoxaPortTxDisable(struct moxa_port *); -static void MoxaPortTxEnable(struct moxa_port *); -static int moxa_get_serial_info(struct tty_struct *, struct serial_struct *); -static int moxa_set_serial_info(struct tty_struct *, struct serial_struct *); -static void MoxaSetFifo(struct moxa_port *port, int enable); - -/* - * I/O functions - */ - -static DEFINE_SPINLOCK(moxafunc_lock); - -static void moxa_wait_finish(void __iomem *ofsAddr) -{ - unsigned long end = jiffies + moxaFuncTout; - - while (readw(ofsAddr + FuncCode) != 0) - if (time_after(jiffies, end)) - return; - if (readw(ofsAddr + FuncCode) != 0) - printk_ratelimited(KERN_WARNING "moxa function expired\n"); -} - -static void moxafunc(void __iomem *ofsAddr, u16 cmd, u16 arg) -{ - guard(spinlock_irqsave)(&moxafunc_lock); - writew(arg, ofsAddr + FuncArg); - writew(cmd, ofsAddr + FuncCode); - moxa_wait_finish(ofsAddr); -} - -static int moxafuncret(void __iomem *ofsAddr, u16 cmd, u16 arg) -{ - guard(spinlock_irqsave)(&moxafunc_lock); - writew(arg, ofsAddr + FuncArg); - writew(cmd, ofsAddr + FuncCode); - moxa_wait_finish(ofsAddr); - - return readw(ofsAddr + FuncArg); -} - -static void moxa_low_water_check(void __iomem *ofsAddr) -{ - u16 rptr, wptr, mask, len; - - if (readb(ofsAddr + FlagStat) & Xoff_state) { - rptr = readw(ofsAddr + RXrptr); - wptr = readw(ofsAddr + RXwptr); - mask = readw(ofsAddr + RX_mask); - len = (wptr - rptr) & mask; - if (len <= Low_water) - moxafunc(ofsAddr, FC_SendXon, 0); - } -} - -/* - * TTY operations - */ - -static int moxa_break_ctl(struct tty_struct *tty, int state) -{ - struct moxa_port *port = tty->driver_data; - - moxafunc(port->tableAddr, state ? FC_SendBreak : FC_StopBreak, - Magic_code); - return 0; -} - -static const struct tty_operations moxa_ops = { - .open = moxa_open, - .close = moxa_close, - .write = moxa_write, - .write_room = moxa_write_room, - .flush_buffer = moxa_flush_buffer, - .chars_in_buffer = moxa_chars_in_buffer, - .set_termios = moxa_set_termios, - .stop = moxa_stop, - .start = moxa_start, - .hangup = moxa_hangup, - .break_ctl = moxa_break_ctl, - .tiocmget = moxa_tiocmget, - .tiocmset = moxa_tiocmset, - .set_serial = moxa_set_serial_info, - .get_serial = moxa_get_serial_info, -}; - -static const struct tty_port_operations moxa_port_ops = { - .carrier_raised = moxa_carrier_raised, - .dtr_rts = moxa_dtr_rts, - .shutdown = moxa_shutdown, -}; - -static struct tty_driver *moxaDriver; -static DEFINE_TIMER(moxaTimer, moxa_poll); - -/* - * HW init - */ - -static int moxa_check_fw_model(struct moxa_board_conf *brd, u8 model) -{ - switch (brd->boardType) { - case MOXA_BOARD_C218_PCI: - if (model != 1) - goto err; - break; - case MOXA_BOARD_CP204J: - if (model != 3) - goto err; - break; - default: - if (model != 2) - goto err; - break; - } - return 0; -err: - return -EINVAL; -} - -static int moxa_check_fw(const void *ptr) -{ - const __le16 *lptr = ptr; - - if (*lptr != cpu_to_le16(0x7980)) - return -EINVAL; - - return 0; -} - -static int moxa_load_bios(struct moxa_board_conf *brd, const u8 *buf, - size_t len) -{ - void __iomem *baseAddr = brd->basemem; - u16 tmp; - - writeb(HW_reset, baseAddr + Control_reg); /* reset */ - msleep(10); - memset_io(baseAddr, 0, 4096); - memcpy_toio(baseAddr, buf, len); /* download BIOS */ - writeb(0, baseAddr + Control_reg); /* restart */ - - msleep(2000); - - switch (brd->boardType) { - case MOXA_BOARD_C218_PCI: - tmp = readw(baseAddr + C218_key); - if (tmp != C218_KeyCode) - goto err; - break; - case MOXA_BOARD_CP204J: - tmp = readw(baseAddr + C218_key); - if (tmp != CP204J_KeyCode) - goto err; - break; - default: - tmp = readw(baseAddr + C320_key); - if (tmp != C320_KeyCode) - goto err; - tmp = readw(baseAddr + C320_status); - if (tmp != STS_init) { - printk(KERN_ERR "MOXA: bios upload failed -- CPU/Basic " - "module not found\n"); - return -EIO; - } - break; - } - - return 0; -err: - printk(KERN_ERR "MOXA: bios upload failed -- board not found\n"); - return -EIO; -} - -static int moxa_load_320b(struct moxa_board_conf *brd, const u8 *ptr, - size_t len) -{ - void __iomem *baseAddr = brd->basemem; - - if (len < 7168) { - printk(KERN_ERR "MOXA: invalid 320 bios -- too short\n"); - return -EINVAL; - } - - writew(len - 7168 - 2, baseAddr + C320bapi_len); - writeb(1, baseAddr + Control_reg); /* Select Page 1 */ - memcpy_toio(baseAddr + DynPage_addr, ptr, 7168); - writeb(2, baseAddr + Control_reg); /* Select Page 2 */ - memcpy_toio(baseAddr + DynPage_addr, ptr + 7168, len - 7168); - - return 0; -} - -static int moxa_real_load_code(struct moxa_board_conf *brd, const void *ptr, - size_t len) -{ - void __iomem *baseAddr = brd->basemem; - const __le16 *uptr = ptr; - size_t wlen, len2, j; - unsigned long key, loadbuf, loadlen, checksum, checksum_ok; - unsigned int i, retry; - u16 usum, keycode; - - keycode = (brd->boardType == MOXA_BOARD_CP204J) ? CP204J_KeyCode : - C218_KeyCode; - - switch (brd->boardType) { - case MOXA_BOARD_CP204J: - case MOXA_BOARD_C218_PCI: - key = C218_key; - loadbuf = C218_LoadBuf; - loadlen = C218DLoad_len; - checksum = C218check_sum; - checksum_ok = C218chksum_ok; - break; - default: - key = C320_key; - keycode = C320_KeyCode; - loadbuf = C320_LoadBuf; - loadlen = C320DLoad_len; - checksum = C320check_sum; - checksum_ok = C320chksum_ok; - break; - } - - usum = 0; - wlen = len >> 1; - for (i = 0; i < wlen; i++) - usum += le16_to_cpu(uptr[i]); - retry = 0; - do { - wlen = len >> 1; - j = 0; - while (wlen) { - len2 = (wlen > 2048) ? 2048 : wlen; - wlen -= len2; - memcpy_toio(baseAddr + loadbuf, ptr + j, len2 << 1); - j += len2 << 1; - - writew(len2, baseAddr + loadlen); - writew(0, baseAddr + key); - for (i = 0; i < 100; i++) { - if (readw(baseAddr + key) == keycode) - break; - msleep(10); - } - if (readw(baseAddr + key) != keycode) - return -EIO; - } - writew(0, baseAddr + loadlen); - writew(usum, baseAddr + checksum); - writew(0, baseAddr + key); - for (i = 0; i < 100; i++) { - if (readw(baseAddr + key) == keycode) - break; - msleep(10); - } - retry++; - } while ((readb(baseAddr + checksum_ok) != 1) && (retry < 3)); - if (readb(baseAddr + checksum_ok) != 1) - return -EIO; - - writew(0, baseAddr + key); - for (i = 0; i < 600; i++) { - if (readw(baseAddr + Magic_no) == Magic_code) - break; - msleep(10); - } - if (readw(baseAddr + Magic_no) != Magic_code) - return -EIO; - - if (MOXA_IS_320(brd)) { - writew(0x3800, baseAddr + TMS320_PORT1); - writew(0x3900, baseAddr + TMS320_PORT2); - writew(28499, baseAddr + TMS320_CLOCK); - } - writew(1, baseAddr + Disable_IRQ); - writew(0, baseAddr + Magic_no); - for (i = 0; i < 500; i++) { - if (readw(baseAddr + Magic_no) == Magic_code) - break; - msleep(10); - } - if (readw(baseAddr + Magic_no) != Magic_code) - return -EIO; - - if (MOXA_IS_320(brd)) { - j = readw(baseAddr + Module_cnt); - if (j <= 0) - return -EIO; - brd->numPorts = j * 8; - writew(j, baseAddr + Module_no); - writew(0, baseAddr + Magic_no); - for (i = 0; i < 600; i++) { - if (readw(baseAddr + Magic_no) == Magic_code) - break; - msleep(10); - } - if (readw(baseAddr + Magic_no) != Magic_code) - return -EIO; - } - brd->intNdx = baseAddr + IRQindex; - brd->intPend = baseAddr + IRQpending; - brd->intTable = baseAddr + IRQtable; - - return 0; -} - -static int moxa_load_code(struct moxa_board_conf *brd, const void *ptr, - size_t len) -{ - void __iomem *ofsAddr, *baseAddr = brd->basemem; - struct moxa_port *port; - int retval, i; - - if (len % 2) { - printk(KERN_ERR "MOXA: bios length is not even\n"); - return -EINVAL; - } - - retval = moxa_real_load_code(brd, ptr, len); /* may change numPorts */ - if (retval) - return retval; - - switch (brd->boardType) { - case MOXA_BOARD_C218_PCI: - case MOXA_BOARD_CP204J: - port = brd->ports; - for (i = 0; i < brd->numPorts; i++, port++) { - port->board = brd; - port->DCDState = 0; - port->tableAddr = baseAddr + Extern_table + - Extern_size * i; - ofsAddr = port->tableAddr; - writew(C218rx_mask, ofsAddr + RX_mask); - writew(C218tx_mask, ofsAddr + TX_mask); - writew(C218rx_spage + i * C218buf_pageno, ofsAddr + Page_rxb); - writew(readw(ofsAddr + Page_rxb) + C218rx_pageno, ofsAddr + EndPage_rxb); - - writew(C218tx_spage + i * C218buf_pageno, ofsAddr + Page_txb); - writew(readw(ofsAddr + Page_txb) + C218tx_pageno, ofsAddr + EndPage_txb); - - } - break; - default: - port = brd->ports; - for (i = 0; i < brd->numPorts; i++, port++) { - port->board = brd; - port->DCDState = 0; - port->tableAddr = baseAddr + Extern_table + - Extern_size * i; - ofsAddr = port->tableAddr; - switch (brd->numPorts) { - case 8: - writew(C320p8rx_mask, ofsAddr + RX_mask); - writew(C320p8tx_mask, ofsAddr + TX_mask); - writew(C320p8rx_spage + i * C320p8buf_pgno, ofsAddr + Page_rxb); - writew(readw(ofsAddr + Page_rxb) + C320p8rx_pgno, ofsAddr + EndPage_rxb); - writew(C320p8tx_spage + i * C320p8buf_pgno, ofsAddr + Page_txb); - writew(readw(ofsAddr + Page_txb) + C320p8tx_pgno, ofsAddr + EndPage_txb); - - break; - case 16: - writew(C320p16rx_mask, ofsAddr + RX_mask); - writew(C320p16tx_mask, ofsAddr + TX_mask); - writew(C320p16rx_spage + i * C320p16buf_pgno, ofsAddr + Page_rxb); - writew(readw(ofsAddr + Page_rxb) + C320p16rx_pgno, ofsAddr + EndPage_rxb); - writew(C320p16tx_spage + i * C320p16buf_pgno, ofsAddr + Page_txb); - writew(readw(ofsAddr + Page_txb) + C320p16tx_pgno, ofsAddr + EndPage_txb); - break; - - case 24: - writew(C320p24rx_mask, ofsAddr + RX_mask); - writew(C320p24tx_mask, ofsAddr + TX_mask); - writew(C320p24rx_spage + i * C320p24buf_pgno, ofsAddr + Page_rxb); - writew(readw(ofsAddr + Page_rxb) + C320p24rx_pgno, ofsAddr + EndPage_rxb); - writew(C320p24tx_spage + i * C320p24buf_pgno, ofsAddr + Page_txb); - writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb); - break; - case 32: - writew(C320p32rx_mask, ofsAddr + RX_mask); - writew(C320p32tx_mask, ofsAddr + TX_mask); - writew(C320p32tx_ofs, ofsAddr + Ofs_txb); - writew(C320p32rx_spage + i * C320p32buf_pgno, ofsAddr + Page_rxb); - writew(readb(ofsAddr + Page_rxb), ofsAddr + EndPage_rxb); - writew(C320p32tx_spage + i * C320p32buf_pgno, ofsAddr + Page_txb); - writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb); - break; - } - } - break; - } - return 0; -} - -static int moxa_load_fw(struct moxa_board_conf *brd, const struct firmware *fw) -{ - const void *ptr = fw->data; - char rsn[64]; - u16 lens[5]; - size_t len; - unsigned int a, lenp, lencnt; - int ret = -EINVAL; - struct { - __le32 magic; /* 0x34303430 */ - u8 reserved1[2]; - u8 type; /* UNIX = 3 */ - u8 model; /* C218T=1, C320T=2, CP204=3 */ - u8 reserved2[8]; - __le16 len[5]; - } const *hdr = ptr; - - BUILD_BUG_ON(ARRAY_SIZE(hdr->len) != ARRAY_SIZE(lens)); - - if (fw->size < MOXA_FW_HDRLEN) { - strcpy(rsn, "too short (even header won't fit)"); - goto err; - } - if (hdr->magic != cpu_to_le32(0x30343034)) { - sprintf(rsn, "bad magic: %.8x", le32_to_cpu(hdr->magic)); - goto err; - } - if (hdr->type != 3) { - sprintf(rsn, "not for linux, type is %u", hdr->type); - goto err; - } - if (moxa_check_fw_model(brd, hdr->model)) { - sprintf(rsn, "not for this card, model is %u", hdr->model); - goto err; - } - - len = MOXA_FW_HDRLEN; - lencnt = hdr->model == 2 ? 5 : 3; - for (a = 0; a < ARRAY_SIZE(lens); a++) { - lens[a] = le16_to_cpu(hdr->len[a]); - if (lens[a] && len + lens[a] <= fw->size && - moxa_check_fw(&fw->data[len])) - printk(KERN_WARNING "MOXA firmware: unexpected input " - "at offset %u, but going on\n", (u32)len); - if (!lens[a] && a < lencnt) { - sprintf(rsn, "too few entries in fw file"); - goto err; - } - len += lens[a]; - } - - if (len != fw->size) { - sprintf(rsn, "bad length: %u (should be %u)", (u32)fw->size, - (u32)len); - goto err; - } - - ptr += MOXA_FW_HDRLEN; - lenp = 0; /* bios */ - - strcpy(rsn, "read above"); - - ret = moxa_load_bios(brd, ptr, lens[lenp]); - if (ret) - goto err; - - /* we skip the tty section (lens[1]), since we don't need it */ - ptr += lens[lenp] + lens[lenp + 1]; - lenp += 2; /* comm */ - - if (hdr->model == 2) { - ret = moxa_load_320b(brd, ptr, lens[lenp]); - if (ret) - goto err; - /* skip another tty */ - ptr += lens[lenp] + lens[lenp + 1]; - lenp += 2; - } - - ret = moxa_load_code(brd, ptr, lens[lenp]); - if (ret) - goto err; - - return 0; -err: - printk(KERN_ERR "firmware failed to load, reason: %s\n", rsn); - return ret; -} - -static int moxa_init_board(struct moxa_board_conf *brd, struct device *dev) -{ - const struct firmware *fw; - const char *file; - struct moxa_port *p; - unsigned int i, first_idx; - int ret; - - brd->ports = kzalloc_objs(*brd->ports, MAX_PORTS_PER_BOARD); - if (brd->ports == NULL) { - printk(KERN_ERR "cannot allocate memory for ports\n"); - ret = -ENOMEM; - goto err; - } - - for (i = 0, p = brd->ports; i < MAX_PORTS_PER_BOARD; i++, p++) { - tty_port_init(&p->port); - p->port.ops = &moxa_port_ops; - p->type = PORT_16550A; - p->cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL; - } - - switch (brd->boardType) { - case MOXA_BOARD_C218_PCI: - file = "c218tunx.cod"; - break; - case MOXA_BOARD_CP204J: - file = "cp204unx.cod"; - break; - default: - file = "c320tunx.cod"; - break; - } - - ret = request_firmware(&fw, file, dev); - if (ret) { - printk(KERN_ERR "MOXA: request_firmware failed. Make sure " - "you've placed '%s' file into your firmware " - "loader directory (e.g. /lib/firmware)\n", - file); - goto err_free; - } - - ret = moxa_load_fw(brd, fw); - - release_firmware(fw); - - if (ret) - goto err_free; - - 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++) - tty_port_register_device(&brd->ports[i].port, moxaDriver, - first_idx + i, dev); - - return 0; -err_free: - for (i = 0; i < MAX_PORTS_PER_BOARD; i++) - tty_port_destroy(&brd->ports[i].port); - kfree(brd->ports); -err: - return ret; -} - -static void moxa_board_deinit(struct moxa_board_conf *brd) -{ - unsigned int a, opened, first_idx; - - 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; - 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; - for (a = 0; a < brd->numPorts; a++) - tty_unregister_device(moxaDriver, first_idx + a); - - iounmap(brd->basemem); - brd->basemem = NULL; - kfree(brd->ports); -} - -static int moxa_pci_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) -{ - struct moxa_board_conf *board; - unsigned int i; - int board_type = ent->driver_data; - int retval; - - retval = pci_enable_device(pdev); - if (retval) { - dev_err(&pdev->dev, "can't enable pci device\n"); - goto err; - } - - for (i = 0; i < MAX_BOARDS; i++) - if (moxa_boards[i].basemem == NULL) - break; - - retval = -ENODEV; - if (i >= MAX_BOARDS) { - dev_warn(&pdev->dev, "more than %u MOXA Intellio family boards " - "found. Board is ignored.\n", MAX_BOARDS); - goto err; - } - - board = &moxa_boards[i]; - - retval = pci_request_region(pdev, 2, "moxa-base"); - if (retval) { - dev_err(&pdev->dev, "can't request pci region 2\n"); - goto err; - } - - board->basemem = ioremap(pci_resource_start(pdev, 2), 0x4000); - if (board->basemem == NULL) { - dev_err(&pdev->dev, "can't remap io space 2\n"); - retval = -ENOMEM; - goto err_reg; - } - - board->boardType = board_type; - switch (board_type) { - case MOXA_BOARD_C218_PCI: - board->numPorts = 8; - break; - - case MOXA_BOARD_CP204J: - board->numPorts = 4; - break; - default: - board->numPorts = 0; - break; - } - - retval = moxa_init_board(board, &pdev->dev); - if (retval) - goto err_base; - - pci_set_drvdata(pdev, board); - - dev_info(&pdev->dev, "board '%s' ready (%u ports, firmware loaded)\n", - moxa_brdname[board_type - 1], board->numPorts); - - return 0; -err_base: - iounmap(board->basemem); - board->basemem = NULL; -err_reg: - pci_release_region(pdev, 2); -err: - return retval; -} - -static void moxa_pci_remove(struct pci_dev *pdev) -{ - struct moxa_board_conf *brd = pci_get_drvdata(pdev); - - moxa_board_deinit(brd); - - pci_release_region(pdev, 2); -} - -static struct pci_driver moxa_pci_driver = { - .name = "moxa", - .id_table = moxa_pcibrds, - .probe = moxa_pci_probe, - .remove = moxa_pci_remove -}; - -static int __init moxa_init(void) -{ - int retval = 0; - - moxaDriver = tty_alloc_driver(MAX_PORTS, - TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_DEV); - if (IS_ERR(moxaDriver)) - return PTR_ERR(moxaDriver); - - moxaDriver->name = "ttyMX"; - moxaDriver->major = ttymajor; - moxaDriver->minor_start = 0; - moxaDriver->type = TTY_DRIVER_TYPE_SERIAL; - moxaDriver->subtype = SERIAL_TYPE_NORMAL; - moxaDriver->init_termios = tty_std_termios; - moxaDriver->init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL; - moxaDriver->init_termios.c_ispeed = 9600; - moxaDriver->init_termios.c_ospeed = 9600; - tty_set_operations(moxaDriver, &moxa_ops); - - if (tty_register_driver(moxaDriver)) { - printk(KERN_ERR "can't register MOXA Smartio tty driver!\n"); - tty_driver_kref_put(moxaDriver); - return -1; - } - - retval = pci_register_driver(&moxa_pci_driver); - if (retval) - printk(KERN_ERR "Can't register MOXA pci driver!\n"); - - return retval; -} - -static void __exit moxa_exit(void) -{ - pci_unregister_driver(&moxa_pci_driver); - - timer_delete_sync(&moxaTimer); - - tty_unregister_driver(moxaDriver); - tty_driver_kref_put(moxaDriver); -} - -module_init(moxa_init); -module_exit(moxa_exit); - -static void moxa_shutdown(struct tty_port *port) -{ - struct moxa_port *ch = container_of(port, struct moxa_port, port); - MoxaPortDisable(ch); - MoxaPortFlushData(ch, 2); -} - -static bool moxa_carrier_raised(struct tty_port *port) -{ - struct moxa_port *ch = container_of(port, struct moxa_port, port); - - guard(spinlock_irq)(&port->lock); - return ch->DCDState; -} - -static void moxa_dtr_rts(struct tty_port *port, bool active) -{ - struct moxa_port *ch = container_of(port, struct moxa_port, port); - MoxaPortLineCtrl(ch, active, active); -} - - -static int moxa_open(struct tty_struct *tty, struct file *filp) -{ - struct moxa_board_conf *brd; - struct moxa_port *ch; - int port = tty->index; - - 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) - return -ENODEV; - - 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); - } - } - - return tty_port_block_til_ready(&ch->port, tty, filp); -} - -static void moxa_close(struct tty_struct *tty, struct file *filp) -{ - struct moxa_port *ch = tty->driver_data; - ch->cflag = tty->termios.c_cflag; - tty_port_close(&ch->port, tty, filp); -} - -static ssize_t moxa_write(struct tty_struct *tty, const u8 *buf, size_t count) -{ - struct moxa_port *ch = tty->driver_data; - int len; - - if (ch == NULL) - return 0; - - scoped_guard(spinlock_irqsave, &moxa_lock) - len = MoxaPortWriteData(tty, buf, count); - - set_bit(LOWWAIT, &ch->statusflags); - return len; -} - -static unsigned int moxa_write_room(struct tty_struct *tty) -{ - struct moxa_port *ch; - - if (tty->flow.stopped) - return 0; - ch = tty->driver_data; - if (ch == NULL) - return 0; - return MoxaPortTxFree(ch); -} - -static void moxa_flush_buffer(struct tty_struct *tty) -{ - struct moxa_port *ch = tty->driver_data; - - if (ch == NULL) - return; - MoxaPortFlushData(ch, 1); - tty_wakeup(tty); -} - -static unsigned int moxa_chars_in_buffer(struct tty_struct *tty) -{ - struct moxa_port *ch = tty->driver_data; - unsigned int chars; - - chars = MoxaPortTxQueue(ch); - if (chars) - /* - * Make it possible to wakeup anything waiting for output - * in tty_ioctl.c, etc. - */ - set_bit(EMPTYWAIT, &ch->statusflags); - return chars; -} - -static int moxa_tiocmget(struct tty_struct *tty) -{ - struct moxa_port *ch = tty->driver_data; - bool dtr_active, rts_active; - int flag = 0; - int status; - - MoxaPortGetLineOut(ch, &dtr_active, &rts_active); - if (dtr_active) - flag |= TIOCM_DTR; - if (rts_active) - flag |= TIOCM_RTS; - status = MoxaPortLineStatus(ch); - if (status & 1) - flag |= TIOCM_CTS; - if (status & 2) - flag |= TIOCM_DSR; - if (status & 4) - flag |= TIOCM_CD; - return flag; -} - -static int moxa_tiocmset(struct tty_struct *tty, - unsigned int set, unsigned int clear) -{ - bool dtr_active, rts_active; - struct moxa_port *ch; - - guard(mutex)(&moxa_openlock); - ch = tty->driver_data; - if (!ch) - return -EINVAL; - - MoxaPortGetLineOut(ch, &dtr_active, &rts_active); - if (set & TIOCM_RTS) - rts_active = true; - if (set & TIOCM_DTR) - dtr_active = true; - if (clear & TIOCM_RTS) - rts_active = false; - if (clear & TIOCM_DTR) - dtr_active = false; - MoxaPortLineCtrl(ch, dtr_active, rts_active); - - return 0; -} - -static void moxa_set_termios(struct tty_struct *tty, - const struct ktermios *old_termios) -{ - struct moxa_port *ch = tty->driver_data; - - if (ch == NULL) - return; - moxa_set_tty_param(tty, old_termios); - if (!(old_termios->c_cflag & CLOCAL) && C_CLOCAL(tty)) - wake_up_interruptible(&ch->port.open_wait); -} - -static void moxa_stop(struct tty_struct *tty) -{ - struct moxa_port *ch = tty->driver_data; - - if (ch == NULL) - return; - MoxaPortTxDisable(ch); - set_bit(TXSTOPPED, &ch->statusflags); -} - - -static void moxa_start(struct tty_struct *tty) -{ - struct moxa_port *ch = tty->driver_data; - - if (ch == NULL) - return; - - if (!test_bit(TXSTOPPED, &ch->statusflags)) - return; - - MoxaPortTxEnable(ch); - clear_bit(TXSTOPPED, &ch->statusflags); -} - -static void moxa_hangup(struct tty_struct *tty) -{ - struct moxa_port *ch = tty->driver_data; - tty_port_hangup(&ch->port); -} - -static void moxa_new_dcdstate(struct moxa_port *p, u8 dcd) -{ - dcd = !!dcd; - - scoped_guard(spinlock_irqsave, &p->port.lock) { - if (dcd == p->DCDState) - return; - - p->DCDState = dcd; - } - - if (!dcd) - tty_port_tty_hangup(&p->port, true); -} - -static int moxa_poll_port(struct moxa_port *p, unsigned int handle, - u16 __iomem *ip) -{ - struct tty_struct *tty = tty_port_tty_get(&p->port); - bool inited = tty_port_initialized(&p->port); - void __iomem *ofsAddr; - u16 intr; - - if (tty) { - if (test_bit(EMPTYWAIT, &p->statusflags) && - MoxaPortTxQueue(p) == 0) { - clear_bit(EMPTYWAIT, &p->statusflags); - tty_wakeup(tty); - } - if (test_bit(LOWWAIT, &p->statusflags) && !tty->flow.stopped && - MoxaPortTxQueue(p) <= WAKEUP_CHARS) { - clear_bit(LOWWAIT, &p->statusflags); - tty_wakeup(tty); - } - - if (inited && !tty_throttled(tty) && - MoxaPortRxQueue(p) > 0) { /* RX */ - MoxaPortReadData(p); - tty_flip_buffer_push(&p->port); - } - } else { - clear_bit(EMPTYWAIT, &p->statusflags); - MoxaPortFlushData(p, 0); /* flush RX */ - } - - if (!handle) /* nothing else to do */ - goto put; - - intr = readw(ip); /* port irq status */ - if (intr == 0) - goto put; - - writew(0, ip); /* ACK port */ - ofsAddr = p->tableAddr; - if (intr & IntrTx) /* disable tx intr */ - writew(readw(ofsAddr + HostStat) & ~WakeupTx, - ofsAddr + HostStat); - - if (!inited) - goto put; - - if (tty && (intr & IntrBreak) && !I_IGNBRK(tty)) { /* BREAK */ - tty_insert_flip_char(&p->port, 0, TTY_BREAK); - tty_flip_buffer_push(&p->port); - } - - if (intr & IntrLine) - moxa_new_dcdstate(p, readb(ofsAddr + FlagStat) & DCD_state); -put: - tty_kref_put(tty); - - return 0; -} - -static void moxa_poll(struct timer_list *unused) -{ - struct moxa_board_conf *brd; - u16 __iomem *ip; - unsigned int card, port, served = 0; - - guard(spinlock)(&moxa_lock); - for (card = 0; card < MAX_BOARDS; card++) { - brd = &moxa_boards[card]; - if (!brd->ready) - continue; - - served++; - - ip = NULL; - if (readb(brd->intPend) == 0xff) - ip = brd->intTable + readb(brd->intNdx); - - for (port = 0; port < brd->numPorts; port++) - moxa_poll_port(&brd->ports[port], !!ip, ip + port); - - if (ip) - writeb(0, brd->intPend); /* ACK */ - - if (moxaLowWaterChk) { - struct moxa_port *p = brd->ports; - for (port = 0; port < brd->numPorts; port++, p++) - if (p->lowChkFlag) { - p->lowChkFlag = 0; - moxa_low_water_check(p->tableAddr); - } - } - } - moxaLowWaterChk = 0; - - if (served) - mod_timer(&moxaTimer, jiffies + HZ / 50); -} - -/******************************************************************************/ - -static void moxa_set_tty_param(struct tty_struct *tty, - const struct ktermios *old_termios) -{ - register struct ktermios *ts = &tty->termios; - struct moxa_port *ch = tty->driver_data; - int rts, cts, txflow, rxflow, xany, baud; - - rts = cts = txflow = rxflow = xany = 0; - if (ts->c_cflag & CRTSCTS) - rts = cts = 1; - if (ts->c_iflag & IXON) - txflow = 1; - if (ts->c_iflag & IXOFF) - rxflow = 1; - if (ts->c_iflag & IXANY) - xany = 1; - - MoxaPortFlowCtrl(ch, rts, cts, txflow, rxflow, xany); - baud = MoxaPortSetTermio(ch, ts, tty_get_baud_rate(tty)); - if (baud == -1) - baud = tty_termios_baud_rate(old_termios); - /* Not put the baud rate into the termios data */ - tty_encode_baud_rate(tty, baud, baud); -} - -/***************************************************************************** - * Driver level functions: * - *****************************************************************************/ - -static void MoxaPortFlushData(struct moxa_port *port, int mode) -{ - void __iomem *ofsAddr; - if (mode < 0 || mode > 2) - return; - ofsAddr = port->tableAddr; - moxafunc(ofsAddr, FC_FlushQueue, mode); - if (mode != 1) { - port->lowChkFlag = 0; - moxa_low_water_check(ofsAddr); - } -} - -/* - * Moxa Port Number Description: - * - * MOXA serial driver supports up to 4 MOXA-C218/C320 boards. And, - * the port number using in MOXA driver functions will be 0 to 31 for - * first MOXA board, 32 to 63 for second, 64 to 95 for third and 96 - * to 127 for fourth. For example, if you setup three MOXA boards, - * first board is C218, second board is C320-16 and third board is - * C320-32. The port number of first board (C218 - 8 ports) is from - * 0 to 7. The port number of second board (C320 - 16 ports) is form - * 32 to 47. The port number of third board (C320 - 32 ports) is from - * 64 to 95. And those port numbers form 8 to 31, 48 to 63 and 96 to - * 127 will be invalid. - * - * - * Moxa Functions Description: - * - * Function 1: Driver initialization routine, this routine must be - * called when initialized driver. - * Syntax: - * void MoxaDriverInit(); - * - * - * Function 2: Moxa driver private IOCTL command processing. - * Syntax: - * int MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port); - * - * unsigned int cmd : IOCTL command - * unsigned long arg : IOCTL argument - * int port : port number (0 - 127) - * - * return: 0 (OK) - * -EINVAL - * -ENOIOCTLCMD - * - * - * Function 6: Enable this port to start Tx/Rx data. - * Syntax: - * void MoxaPortEnable(int port); - * int port : port number (0 - 127) - * - * - * Function 7: Disable this port - * Syntax: - * void MoxaPortDisable(int port); - * int port : port number (0 - 127) - * - * - * Function 10: Setting baud rate of this port. - * Syntax: - * speed_t MoxaPortSetBaud(int port, speed_t baud); - * int port : port number (0 - 127) - * long baud : baud rate (50 - 115200) - * - * return: 0 : this port is invalid or baud < 50 - * 50 - 115200 : the real baud rate set to the port, if - * the argument baud is large than maximun - * available baud rate, the real setting - * baud rate will be the maximun baud rate. - * - * - * Function 12: Configure the port. - * Syntax: - * int MoxaPortSetTermio(int port, struct ktermios *termio, speed_t baud); - * int port : port number (0 - 127) - * struct ktermios * termio : termio structure pointer - * speed_t baud : baud rate - * - * return: -1 : this port is invalid or termio == NULL - * 0 : setting O.K. - * - * - * Function 13: Get the DTR/RTS state of this port. - * Syntax: - * int MoxaPortGetLineOut(int port, bool *dtrState, bool *rtsState); - * int port : port number (0 - 127) - * bool * dtr_active : pointer to bool to receive the current DTR - * state. (if NULL, this function will not - * write to this address) - * bool * rts_active : pointer to bool to receive the current RTS - * state. (if NULL, this function will not - * write to this address) - * - * return: -1 : this port is invalid - * 0 : O.K. - * - * - * Function 14: Setting the DTR/RTS output state of this port. - * Syntax: - * void MoxaPortLineCtrl(int port, bool dtrState, bool rtsState); - * int port : port number (0 - 127) - * bool dtr_active : DTR output state - * bool rts_active : RTS output state - * - * - * Function 15: Setting the flow control of this port. - * Syntax: - * void MoxaPortFlowCtrl(int port, int rtsFlow, int ctsFlow, int rxFlow, - * int txFlow,int xany); - * int port : port number (0 - 127) - * int rtsFlow : H/W RTS flow control (0: no, 1: yes) - * int ctsFlow : H/W CTS flow control (0: no, 1: yes) - * int rxFlow : S/W Rx XON/XOFF flow control (0: no, 1: yes) - * int txFlow : S/W Tx XON/XOFF flow control (0: no, 1: yes) - * int xany : S/W XANY flow control (0: no, 1: yes) - * - * - * Function 16: Get ths line status of this port - * Syntax: - * int MoxaPortLineStatus(int port); - * int port : port number (0 - 127) - * - * return: Bit 0 - CTS state (0: off, 1: on) - * Bit 1 - DSR state (0: off, 1: on) - * Bit 2 - DCD state (0: off, 1: on) - * - * - * Function 19: Flush the Rx/Tx buffer data of this port. - * Syntax: - * void MoxaPortFlushData(int port, int mode); - * int port : port number (0 - 127) - * int mode - * 0 : flush the Rx buffer - * 1 : flush the Tx buffer - * 2 : flush the Rx and Tx buffer - * - * - * Function 20: Write data. - * Syntax: - * ssize_t MoxaPortWriteData(int port, u8 *buffer, size_t length); - * int port : port number (0 - 127) - * u8 *buffer : pointer to write data buffer. - * size_t length : write data length - * - * return: 0 - length : real write data length - * - * - * Function 21: Read data. - * Syntax: - * int MoxaPortReadData(int port, struct tty_struct *tty); - * int port : port number (0 - 127) - * struct tty_struct *tty : tty for data - * - * return: 0 - length : real read data length - * - * - * Function 24: Get the Tx buffer current queued data bytes - * Syntax: - * int MoxaPortTxQueue(int port); - * int port : port number (0 - 127) - * - * return: .. : Tx buffer current queued data bytes - * - * - * Function 25: Get the Tx buffer current free space - * Syntax: - * int MoxaPortTxFree(int port); - * int port : port number (0 - 127) - * - * return: .. : Tx buffer current free space - * - * - * Function 26: Get the Rx buffer current queued data bytes - * Syntax: - * int MoxaPortRxQueue(int port); - * int port : port number (0 - 127) - * - * return: .. : Rx buffer current queued data bytes - * - * - * Function 28: Disable port data transmission. - * Syntax: - * void MoxaPortTxDisable(int port); - * int port : port number (0 - 127) - * - * - * Function 29: Enable port data transmission. - * Syntax: - * void MoxaPortTxEnable(int port); - * int port : port number (0 - 127) - * - * - * Function 31: Get the received BREAK signal count and reset it. - * Syntax: - * int MoxaPortResetBrkCnt(int port); - * int port : port number (0 - 127) - * - * return: 0 - .. : BREAK signal count - * - * - */ - -static void MoxaPortEnable(struct moxa_port *port) -{ - void __iomem *ofsAddr; - u16 lowwater = 512; - - ofsAddr = port->tableAddr; - writew(lowwater, ofsAddr + Low_water); - if (MOXA_IS_320(port->board)) - moxafunc(ofsAddr, FC_SetBreakIrq, 0); - else - writew(readw(ofsAddr + HostStat) | WakeupBreak, - ofsAddr + HostStat); - - moxafunc(ofsAddr, FC_SetLineIrq, Magic_code); - moxafunc(ofsAddr, FC_FlushQueue, 2); - - moxafunc(ofsAddr, FC_EnableCH, Magic_code); - MoxaPortLineStatus(port); -} - -static void MoxaPortDisable(struct moxa_port *port) -{ - void __iomem *ofsAddr = port->tableAddr; - - moxafunc(ofsAddr, FC_SetFlowCtl, 0); /* disable flow control */ - moxafunc(ofsAddr, FC_ClrLineIrq, Magic_code); - writew(0, ofsAddr + HostStat); - moxafunc(ofsAddr, FC_DisableCH, Magic_code); -} - -static speed_t MoxaPortSetBaud(struct moxa_port *port, speed_t baud) -{ - void __iomem *ofsAddr = port->tableAddr; - unsigned int clock, val; - speed_t max; - - max = MOXA_IS_320(port->board) ? 460800 : 921600; - if (baud < 50) - return 0; - if (baud > max) - baud = max; - clock = 921600; - val = clock / baud; - moxafunc(ofsAddr, FC_SetBaud, val); - baud = clock / val; - return baud; -} - -static int MoxaPortSetTermio(struct moxa_port *port, struct ktermios *termio, - speed_t baud) -{ - void __iomem *ofsAddr; - tcflag_t mode = 0; - - ofsAddr = port->tableAddr; - - mode = termio->c_cflag & CSIZE; - if (mode == CS5) - mode = MX_CS5; - else if (mode == CS6) - mode = MX_CS6; - else if (mode == CS7) - mode = MX_CS7; - else if (mode == CS8) - mode = MX_CS8; - - if (termio->c_cflag & CSTOPB) { - if (mode == MX_CS5) - mode |= MX_STOP15; - else - mode |= MX_STOP2; - } else - mode |= MX_STOP1; - - if (termio->c_cflag & PARENB) { - if (termio->c_cflag & PARODD) { - if (termio->c_cflag & CMSPAR) - mode |= MX_PARMARK; - else - mode |= MX_PARODD; - } else { - if (termio->c_cflag & CMSPAR) - mode |= MX_PARSPACE; - else - mode |= MX_PAREVEN; - } - } else - mode |= MX_PARNONE; - - moxafunc(ofsAddr, FC_SetDataMode, (u16)mode); - - if (MOXA_IS_320(port->board) && baud >= 921600) - return -1; - - baud = MoxaPortSetBaud(port, baud); - - if (termio->c_iflag & (IXON | IXOFF | IXANY)) { - 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); - } - return baud; -} - -static int MoxaPortGetLineOut(struct moxa_port *port, bool *dtr_active, - bool *rts_active) -{ - if (dtr_active) - *dtr_active = port->lineCtrl & DTR_ON; - if (rts_active) - *rts_active = port->lineCtrl & RTS_ON; - - return 0; -} - -static void MoxaPortLineCtrl(struct moxa_port *port, bool dtr_active, bool rts_active) -{ - u8 mode = 0; - - if (dtr_active) - mode |= DTR_ON; - if (rts_active) - mode |= RTS_ON; - port->lineCtrl = mode; - moxafunc(port->tableAddr, FC_LineControl, mode); -} - -static void MoxaPortFlowCtrl(struct moxa_port *port, int rts, int cts, - int txflow, int rxflow, int txany) -{ - int mode = 0; - - if (rts) - mode |= RTS_FlowCtl; - if (cts) - mode |= CTS_FlowCtl; - if (txflow) - mode |= Tx_FlowCtl; - if (rxflow) - mode |= Rx_FlowCtl; - if (txany) - mode |= IXM_IXANY; - moxafunc(port->tableAddr, FC_SetFlowCtl, mode); -} - -static int MoxaPortLineStatus(struct moxa_port *port) -{ - void __iomem *ofsAddr; - int val; - - ofsAddr = port->tableAddr; - if (MOXA_IS_320(port->board)) - val = moxafuncret(ofsAddr, FC_LineStatus, 0); - else - val = readw(ofsAddr + FlagStat) >> 4; - val &= 0x0B; - if (val & 8) - val |= 4; - moxa_new_dcdstate(port, val & 8); - val &= 7; - return val; -} - -static ssize_t MoxaPortWriteData(struct tty_struct *tty, const u8 *buffer, - size_t len) -{ - struct moxa_port *port = tty->driver_data; - void __iomem *baseAddr, *ofsAddr, *ofs; - size_t c, total; - u16 head, tail, tx_mask, spage, epage; - u16 pageno, pageofs, bufhead; - - ofsAddr = port->tableAddr; - baseAddr = port->board->basemem; - tx_mask = readw(ofsAddr + TX_mask); - spage = readw(ofsAddr + Page_txb); - epage = readw(ofsAddr + EndPage_txb); - tail = readw(ofsAddr + TXwptr); - head = readw(ofsAddr + TXrptr); - c = (head > tail) ? (head - tail - 1) : (head - tail + tx_mask); - if (c > len) - c = len; - total = c; - if (spage == epage) { - bufhead = readw(ofsAddr + Ofs_txb); - writew(spage, baseAddr + Control_reg); - while (c > 0) { - if (head > tail) - len = head - tail - 1; - else - len = tx_mask + 1 - tail; - len = (c > len) ? len : c; - ofs = baseAddr + DynPage_addr + bufhead + tail; - memcpy_toio(ofs, buffer, len); - buffer += len; - tail = (tail + len) & tx_mask; - c -= len; - } - } else { - pageno = spage + (tail >> 13); - pageofs = tail & Page_mask; - while (c > 0) { - len = Page_size - pageofs; - if (len > c) - len = c; - writeb(pageno, baseAddr + Control_reg); - ofs = baseAddr + DynPage_addr + pageofs; - memcpy_toio(ofs, buffer, len); - buffer += len; - if (++pageno == epage) - pageno = spage; - pageofs = 0; - c -= len; - } - tail = (tail + total) & tx_mask; - } - writew(tail, ofsAddr + TXwptr); - writeb(1, ofsAddr + CD180TXirq); /* start to send */ - return total; -} - -static int MoxaPortReadData(struct moxa_port *port) -{ - void __iomem *baseAddr, *ofsAddr, *ofs; - u8 *dst; - unsigned int count, len, total; - u16 tail, rx_mask, spage, epage; - u16 pageno, pageofs, bufhead, head; - - ofsAddr = port->tableAddr; - baseAddr = port->board->basemem; - head = readw(ofsAddr + RXrptr); - tail = readw(ofsAddr + RXwptr); - rx_mask = readw(ofsAddr + RX_mask); - spage = readw(ofsAddr + Page_rxb); - epage = readw(ofsAddr + EndPage_rxb); - count = (tail >= head) ? (tail - head) : (tail - head + rx_mask + 1); - if (count == 0) - return 0; - - total = count; - if (spage == epage) { - bufhead = readw(ofsAddr + Ofs_rxb); - writew(spage, baseAddr + Control_reg); - while (count > 0) { - ofs = baseAddr + DynPage_addr + bufhead + head; - len = (tail >= head) ? (tail - head) : - (rx_mask + 1 - head); - len = tty_prepare_flip_string(&port->port, &dst, - min(len, count)); - memcpy_fromio(dst, ofs, len); - head = (head + len) & rx_mask; - count -= len; - } - } else { - pageno = spage + (head >> 13); - pageofs = head & Page_mask; - while (count > 0) { - writew(pageno, baseAddr + Control_reg); - ofs = baseAddr + DynPage_addr + pageofs; - len = tty_prepare_flip_string(&port->port, &dst, - min(Page_size - pageofs, count)); - memcpy_fromio(dst, ofs, len); - - count -= len; - pageofs = (pageofs + len) & Page_mask; - if (pageofs == 0 && ++pageno == epage) - pageno = spage; - } - head = (head + total) & rx_mask; - } - writew(head, ofsAddr + RXrptr); - if (readb(ofsAddr + FlagStat) & Xoff_state) { - moxaLowWaterChk = 1; - port->lowChkFlag = 1; - } - return total; -} - - -static unsigned int MoxaPortTxQueue(struct moxa_port *port) -{ - void __iomem *ofsAddr = port->tableAddr; - u16 rptr, wptr, mask; - - rptr = readw(ofsAddr + TXrptr); - wptr = readw(ofsAddr + TXwptr); - mask = readw(ofsAddr + TX_mask); - return (wptr - rptr) & mask; -} - -static unsigned int MoxaPortTxFree(struct moxa_port *port) -{ - void __iomem *ofsAddr = port->tableAddr; - u16 rptr, wptr, mask; - - rptr = readw(ofsAddr + TXrptr); - wptr = readw(ofsAddr + TXwptr); - mask = readw(ofsAddr + TX_mask); - return mask - ((wptr - rptr) & mask); -} - -static int MoxaPortRxQueue(struct moxa_port *port) -{ - void __iomem *ofsAddr = port->tableAddr; - u16 rptr, wptr, mask; - - rptr = readw(ofsAddr + RXrptr); - wptr = readw(ofsAddr + RXwptr); - mask = readw(ofsAddr + RX_mask); - return (wptr - rptr) & mask; -} - -static void MoxaPortTxDisable(struct moxa_port *port) -{ - moxafunc(port->tableAddr, FC_SetXoffState, Magic_code); -} - -static void MoxaPortTxEnable(struct moxa_port *port) -{ - moxafunc(port->tableAddr, FC_SetXonState, Magic_code); -} - -static int moxa_get_serial_info(struct tty_struct *tty, - struct serial_struct *ss) -{ - struct moxa_port *info = tty->driver_data; - - if (!info) - return -ENODEV; - 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; - - return 0; -} - - -static int moxa_set_serial_info(struct tty_struct *tty, - struct serial_struct *ss) -{ - struct moxa_port *info = tty->driver_data; - unsigned int close_delay; - - if (!info) - return -ENODEV; - - close_delay = msecs_to_jiffies(ss->close_delay * 10); - - 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))) { - return -EPERM; - } - } else { - info->port.close_delay = close_delay; - - MoxaSetFifo(info, ss->type == PORT_16550A); - - info->type = ss->type; - } - - return 0; -} - - - -/***************************************************************************** - * Static local functions: * - *****************************************************************************/ - -static void MoxaSetFifo(struct moxa_port *port, int enable) -{ - void __iomem *ofsAddr = port->tableAddr; - - if (!enable) { - moxafunc(ofsAddr, FC_SetRxFIFOTrig, 0); - moxafunc(ofsAddr, FC_SetTxFIFOCnt, 1); - } else { - moxafunc(ofsAddr, FC_SetRxFIFOTrig, 3); - moxafunc(ofsAddr, FC_SetTxFIFOCnt, 16); - } -} From f8631f993e26ea6eea6242dec7185017ba2e566c Mon Sep 17 00:00:00 2001 From: Ibrahim Abdelkader Date: Wed, 19 Aug 2026 14:57:48 +0200 Subject: [PATCH 02/91] serdev: ttyport: Clear serport->tty after freeing Both error paths in ttyport_open(), and ttyport_close(), release the tty with tty_release_struct() and leave serport->tty pointing at freed memory. The serdev core itself never dereferences it afterwards. However, a buggy driver could easily trigger a use-after-free by calling a ttyport operation on a port that is not open, or by calling close() twice. While those drivers should be fixed, clearing the pointer, makes them fail deterministically instead of touching freed memory. Note that a driver which currently double closes gets away with it only by chance, depending on whether the freed tty has been reused. After this change such a driver oopses immediately instead, which is the intended outcome, but it may surface latent bugs elsewhere. Signed-off-by: Ibrahim Abdelkader Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Reviewed-by: Markus Probst Link: https://patch.msgid.link/20260819125748.194377-1-johannes.goede@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serdev/serdev-ttyport.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c index bab1b143b8a6..b6638f9f40d2 100644 --- a/drivers/tty/serdev/serdev-ttyport.c +++ b/drivers/tty/serdev/serdev-ttyport.c @@ -137,6 +137,7 @@ static int ttyport_open(struct serdev_controller *ctrl) err_unlock: tty_unlock(tty); tty_release_struct(tty, serport->tty_idx); + serport->tty = NULL; return ret; } @@ -154,6 +155,7 @@ static void ttyport_close(struct serdev_controller *ctrl) tty_unlock(tty); tty_release_struct(tty, serport->tty_idx); + serport->tty = NULL; } static unsigned int ttyport_set_baudrate(struct serdev_controller *ctrl, unsigned int speed) From 9d3aedc075ebbf35f33cdc4221e02341760c0b9e Mon Sep 17 00:00:00 2001 From: Xiaochun Li Date: Thu, 6 Aug 2026 16:48:27 +0800 Subject: [PATCH 03/91] serial: core: Fix a potential NULL pointer dereference in uart_parse_earlycon() console=uart and console=pl011 can reach the preferred console matching path without an options field when a comma is absent from the command line. In that case uart_parse_earlycon() receives a NULL pointer and immediately passes it to strncmp(), which triggers a NULL pointer dereference during console matching. Address this by returning -EINVAL when the options pointer is missing, ensuring incomplete console arguments are cleanly rejected while preserving the behavior of all valid earlycon-style command lines. As this is a trusted string, this is not a real security issue at all as it can not normally happen. Signed-off-by: Xiaochun Li Link: https://patch.msgid.link/20260806084827.16239-1-lixiaochun@open-hieco.net Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 95774b0f1484..9de26d8c9921 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -2110,7 +2110,8 @@ EXPORT_SYMBOL_GPL(uart_console_write); /** * uart_parse_earlycon - Parse earlycon options - * @p: ptr to 2nd field (ie., just beyond ',') + * @p: ptr to 2nd field (ie., just beyond ','); %NULL if + * no console options were supplied * @iotype: ptr for decoded iotype (out) * @addr: ptr for decoded mapbase/iobase (out) * @options: ptr for field; %NULL if not present (out) @@ -2130,6 +2131,9 @@ EXPORT_SYMBOL_GPL(uart_console_write); int uart_parse_earlycon(char *p, enum uart_iotype *iotype, resource_size_t *addr, char **options) { + if (!p) + return -EINVAL; + if (strncmp(p, "mmio,", 5) == 0) { *iotype = UPIO_MEM; p += 5; From 64419feb43e4f3c7b5f51ac8f5c92b5f9508fbe0 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 22 Sep 2026 11:59:42 +0200 Subject: [PATCH 04/91] dt-bindings: serial: renesas,hscif: Increase number of DMA channels to 6 On R-Car X5H, HSCIF serial interfaces are wired to three DMACs, hence referring to up to six DMA channels. Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/0fac2fa211f591d91c6e59fa16bc98791afc6559.1790071098.git.geert+renesas@glider.be Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/renesas,hscif.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/serial/renesas,hscif.yaml b/Documentation/devicetree/bindings/serial/renesas,hscif.yaml index 4b3f98a46cd9..409cab32de3d 100644 --- a/Documentation/devicetree/bindings/serial/renesas,hscif.yaml +++ b/Documentation/devicetree/bindings/serial/renesas,hscif.yaml @@ -97,14 +97,14 @@ properties: dmas: minItems: 2 - maxItems: 4 + maxItems: 6 description: Must contain a list of pairs of references to DMA specifiers, one for transmission, and one for reception. dma-names: minItems: 2 - maxItems: 4 + maxItems: 6 items: enum: - tx From 9c8849475bb97c3ef4769da556744f08c30d2c06 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 22 Sep 2026 12:00:09 +0200 Subject: [PATCH 05/91] dt-bindings: serial: renesas,scif: Increase number of DMA channels to 6 On R-Car X5H, SCIF serial interfaces are wired to three DMACs, hence referring to up to six DMA channels. Signed-off-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/4dc02f4ff8a63c2bd41b15359828ce9befd2da2f.1790071199.git.geert+renesas@glider.be Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/renesas,scif.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/serial/renesas,scif.yaml b/Documentation/devicetree/bindings/serial/renesas,scif.yaml index 82f54446835e..b465c93c8357 100644 --- a/Documentation/devicetree/bindings/serial/renesas,scif.yaml +++ b/Documentation/devicetree/bindings/serial/renesas,scif.yaml @@ -145,14 +145,14 @@ properties: dmas: minItems: 2 - maxItems: 4 + maxItems: 6 description: Must contain a list of pairs of references to DMA specifiers, one for transmission, and one for reception. dma-names: minItems: 2 - maxItems: 4 + maxItems: 6 items: enum: - tx From affcff6a565f9526d5df4eada993951c63d05336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:59:31 +0200 Subject: [PATCH 06/91] serial: 8250_pci: Catch up on new pci_device_id entry without named initializer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the patch that became 0481a041e956 ("serial: 8250: split Moxa PCIe serial board support out of 8250_pci") was created, commit 44e55f1f3088 ("serial: 8250_pci: Consistently define pci_device_ids using named initializers") wasn't applied yet and thus added an assignment to .driver_data without using a named initializer. Adapt the assignment to make the array consistent again and re-prepare the driver for the planned change to struct pci_device_id. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260814095931.1871387-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index c24a59896982..6e53d6d72a8e 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -3894,7 +3894,7 @@ static const struct pci_device_id blacklist[] = { { PCI_VDEVICE(ACCESSIO, PCI_ANY_ID), .driver_data = REPORT_8250_CONFIG(PERICOM), }, /* Moxa devices */ - { PCI_VDEVICE(MOXA, PCI_ANY_ID), REPORT_8250_CONFIG(MOXA), }, + { PCI_VDEVICE(MOXA, PCI_ANY_ID), .driver_data = REPORT_8250_CONFIG(MOXA), }, /* End of the black list */ { } From 78469580f668628a62fefd80cf2911132e1fe74e Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 11 Aug 2026 10:09:46 +0200 Subject: [PATCH 07/91] dt-bindings: serial: 8250: Reorganize compatible to single enum Instead of declaring each single compatible string with a const, move them to a single enum and reorder the Nuvoton compatible as it wasn't sorted. Put the generic ns first and then follow alphabetical order. Signed-off-by: Christian Marangi Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260811080950.664379-2-ansuelsmth@gmail.com Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/serial/8250.yaml | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml index 8c2c177faf89..1a08c2b4de83 100644 --- a/Documentation/devicetree/bindings/serial/8250.yaml +++ b/Documentation/devicetree/bindings/serial/8250.yaml @@ -100,19 +100,20 @@ allOf: properties: compatible: oneOf: - - const: ns8250 - - const: ns16450 - - const: ns16550 - - const: ns16550a - - const: ns16850 - - const: aspeed,ast2400-vuart - - const: aspeed,ast2500-vuart - - const: intel,xscale-uart - - const: mrvl,pxa-uart - - const: nuvoton,wpcm450-uart - - const: nuvoton,npcm750-uart - - const: nvidia,tegra20-uart - - const: nxp,lpc3220-uart + - enum: + - ns8250 + - ns16450 + - ns16550 + - ns16550a + - ns16850 + - aspeed,ast2400-vuart + - aspeed,ast2500-vuart + - intel,xscale-uart + - mrvl,pxa-uart + - nuvoton,npcm750-uart + - nuvoton,wpcm450-uart + - nvidia,tegra20-uart + - nxp,lpc3220-uart - items: - enum: - exar,xr16l2552 From bce284b00eea691c3534d834fb1ba0cb6171c6a6 Mon Sep 17 00:00:00 2001 From: Benjamin Larsson Date: Tue, 11 Aug 2026 10:09:47 +0200 Subject: [PATCH 08/91] dt-bindings: serial: 8250: Add Airoha compatibles The Airoha SoC family have a mostly 16550-compatible UART and High-Speed UART hardware with the exception of custom baud rate settings register. Signed-off-by: Benjamin Larsson Signed-off-by: Christian Marangi Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260811080950.664379-3-ansuelsmth@gmail.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/8250.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml index 1a08c2b4de83..50d5c977afa3 100644 --- a/Documentation/devicetree/bindings/serial/8250.yaml +++ b/Documentation/devicetree/bindings/serial/8250.yaml @@ -106,6 +106,8 @@ properties: - ns16550 - ns16550a - ns16850 + - airoha,an7581-hsuart + - airoha,en7523-uart - aspeed,ast2400-vuart - aspeed,ast2500-vuart - intel,xscale-uart @@ -114,6 +116,9 @@ properties: - nuvoton,wpcm450-uart - nvidia,tegra20-uart - nxp,lpc3220-uart + - items: + - const: airoha,an7581-uart + - const: airoha,en7523-uart - items: - enum: - exar,xr16l2552 From 92a8133c44de3340a35d638c0458886e633ba98c Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Tue, 11 Aug 2026 10:09:48 +0200 Subject: [PATCH 09/91] serial: 8250: Add Airoha SoC UART and HSUART support Add support for Airoha AN7523 UART and AN7581 HSUART. These implement a standard 16550 UART with only some custom logic for baud rate handling. Co-developed-by: Benjamin Larsson Signed-off-by: Benjamin Larsson Reviewed-by: Andy Shevchenko Signed-off-by: Christian Marangi Link: https://patch.msgid.link/20260811080950.664379-4-ansuelsmth@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 6 + drivers/tty/serial/8250/8250_airoha.c | 187 ++++++++++++++++++++++++++ drivers/tty/serial/8250/8250_port.c | 16 +++ drivers/tty/serial/8250/Kconfig | 11 ++ drivers/tty/serial/8250/Makefile | 1 + 5 files changed, 221 insertions(+) create mode 100644 drivers/tty/serial/8250/8250_airoha.c diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 9337fec9394e..77b79c95d207 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -73,6 +73,12 @@ struct serial8250_config { unsigned int flags; }; +enum uart_port_type { + /* Internal 8250 only */ + UART_PORT_AIROHA = 124, + UART_PORT_AIROHA_HS = 125, +}; + #define UART_CAP_FIFO BIT(8) /* UART has FIFO */ #define UART_CAP_EFR BIT(9) /* UART has EFR */ #define UART_CAP_SLEEP BIT(10) /* UART has IER sleep */ diff --git a/drivers/tty/serial/8250/8250_airoha.c b/drivers/tty/serial/8250/8250_airoha.c new file mode 100644 index 000000000000..fe72a39561c2 --- /dev/null +++ b/drivers/tty/serial/8250/8250_airoha.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Airoha UART driver + * + * Copyright (c) 2025 Genexis Sweden AB + * Author: Benjamin Larsson + * Christian Marangi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "8250.h" + +#define UART_AIROHA_XINCLKDR 10 +#define UART_AIROHA_XYD 11 +#define UART_AIROHA_XYD_X GENMASK(31, 16) +#define UART_AIROHA_XYD_Y GENMASK(15, 0) + +struct airoha_8250_priv { + int line; +}; + +#define UART_BRD_20M 0x0001 + +#define XINDIV_CLOCK (20 * HZ_PER_MHZ) +#define XYD_Y 65000 + +static const unsigned int airoha_clk_divs[] = { 2, 4, 10 }; + +static unsigned int airoha_get_divisor(struct uart_port *port, + unsigned int baud, + unsigned int *frac) +{ + /* Hardware always uses BRDIV = 1. */ + *frac = 0; + + return UART_BRD_20M; +} + +/* + * Airoha UART baud rate calculation logic + * + * crystal_clock = 20 MHz (fixed frequency) + * xindiv_clock = crystal_clock / clock_div + * (x/y) = XYD, 32 bit register with 16 bits of x and then 16 bits of y + * clock_div = XINCLK_DIVCNT (default set to 10 (0x4)), + * - 3 bit register [ 1, 2, 4, 8, 10, 12, 16, 20 ] + * + * baud_rate = ((xindiv_clock) * (x/y)) / ([BRDH,BRDL] * 16) + * + * Selecting divider needs to fulfill + * 1.8432 MHz <= xindiv_clk <= APB clock / 2 + * The clocks are unknown but a divider of value 1 did not result in a valid + * waveform. + * + * XYD_y seems to need to be larger then XYD_x for proper waveform generation. + * Setting [BRDH,BRDL] to [0,1] and XYD_y to 65000 gives even values + * for usual baud rates. + */ +static void airoha_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); + u32 xindiv_clk; + u64 xyd_x, nom; + int div_bit; + + /* Set baud rate calculation defaults (BRDIV [BRDH,BRDL] to 1) */ + serial8250_do_set_divisor(port, baud, UART_BRD_20M); + + /* + * Calculate XYD_x and XINCLKDR register by searching + * through a table of crystal_clock divisors. + */ + nom = (u64)baud * XYD_Y; + for (div_bit = ARRAY_SIZE(airoha_clk_divs) - 1; div_bit >= 0; div_bit--) { + unsigned int div = airoha_clk_divs[div_bit]; + + xindiv_clk = XINDIV_CLOCK / div; + xyd_x = div_u64(nom * 16, xindiv_clk); + + /* For the HSUART xyd_x needs to be scaled by a factor of 2 */ + if (port->type == UART_PORT_AIROHA_HS) + xyd_x /= 2; + + if (xyd_x < XYD_Y) + break; + } + + /* Couldn't find a valid xyd_x */ + if (div_bit < 0) { + dev_err(port->dev, "failed to find suitable clock divisor for baud %u\n", + baud); + return; + } + + serial_port_out(port, UART_AIROHA_XINCLKDR, BIT(div_bit)); + serial_port_out(port, UART_AIROHA_XYD, + FIELD_PREP(UART_AIROHA_XYD_X, xyd_x) | + FIELD_PREP(UART_AIROHA_XYD_Y, XYD_Y)); + + /* Restore normal register access. */ + serial_port_out(port, UART_LCR, up->lcr); +} + +static int airoha_8250_probe(struct platform_device *pdev) +{ + struct uart_8250_port uart = { }; + struct device *dev = &pdev->dev; + struct airoha_8250_priv *priv; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return dev_err_probe(dev, -EINVAL, "invalid address\n"); + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + uart.port.dev = dev; + if (device_is_compatible(dev, "airoha,an7581-hsuart")) + uart.port.type = UART_PORT_AIROHA_HS; + else + uart.port.type = UART_PORT_AIROHA; + uart.port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_PORT | + UPF_FIXED_TYPE | UPF_IOREMAP; + uart.port.set_divisor = airoha_set_divisor; + uart.port.get_divisor = airoha_get_divisor; + uart.port.mapbase = res->start; + uart.port.mapsize = resource_size(res); + + ret = uart_read_and_validate_port_properties(&uart.port); + if (ret) + return ret; + + ret = serial8250_register_8250_port(&uart); + if (ret < 0) + return ret; + + priv->line = ret; + platform_set_drvdata(pdev, priv); + + return 0; +} + +static void airoha_8250_remove(struct platform_device *ofdev) +{ + struct airoha_8250_priv *priv = platform_get_drvdata(ofdev); + + serial8250_unregister_port(priv->line); +} + +static const struct of_device_id airoha_8250_dt_ids[] = { + { .compatible = "airoha,en7523-uart" }, + { .compatible = "airoha,an7581-hsuart" }, + { } +}; +MODULE_DEVICE_TABLE(of, airoha_8250_dt_ids); + +static struct platform_driver airoha_8250_driver = { + .driver = { + .name = "8250_airoha", + .of_match_table = airoha_8250_dt_ids, + }, + .probe = airoha_8250_probe, + .remove = airoha_8250_remove, +}; +module_platform_driver(airoha_8250_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Airoha UART driver"); +MODULE_AUTHOR("Benjamin Larsson "); +MODULE_AUTHOR("Christian Marangi "); diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 38fa45e74a37..6f88b7dd2bc2 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -319,6 +319,22 @@ static const struct serial8250_config uart_config[] = { .rxtrig_bytes = {15, 31, 63, 111}, .flags = UART_CAP_FIFO, }, + [UART_PORT_AIROHA] = { + .name = "Airoha UART", + .fifo_size = 8, + .tx_loadsz = 1, + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01 | UART_FCR_CLEAR_RCVR, + .rxtrig_bytes = {1, 4, 4, 4}, + .flags = UART_CAP_FIFO, + }, + [UART_PORT_AIROHA_HS] = { + .name = "Airoha HSUART", + .fifo_size = 128, + .tx_loadsz = 128, + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01 | UART_FCR_CLEAR_RCVR, + .rxtrig_bytes = {1, 4}, + .flags = UART_CAP_FIFO, + }, }; /* Uart divisor latch read */ diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index fa6f33303cb0..1d5c516a7612 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -347,6 +347,17 @@ config SERIAL_8250_ACORN system, say Y to this option. The driver can handle 1, 2, or 3 port cards. If unsure, say N. +config SERIAL_8250_AIROHA + tristate "Airoha UART support" + depends on ARCH_AIROHA || COMPILE_TEST + depends on SERIAL_8250 + help + Selecting this option enables an Airoha SoC specific baud rate + calculation routine on an otherwise 16550 compatible UART hardware. + + If you have an Airoha based board and want to use the serial port, + say Y to this option. If unsure, say N. + config SERIAL_8250_BCM2835AUX tristate "BCM2835 auxiliar mini UART support" depends on ARCH_BCM2835 || COMPILE_TEST diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index 46adadcfe072..edf91c980a63 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE) += 8250_early.o obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o +obj-$(CONFIG_SERIAL_8250_AIROHA) += 8250_airoha.o obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o From 041a275af5b0d12f4435e999d75c3c27cf2c04ca Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 14 Aug 2026 13:16:29 +0200 Subject: [PATCH 10/91] serial: 8250_port: Remove redundant pm_runtime_mark_last_busy() call The pm_runtime_mark_last_busy() call is redundant in the wrapper function as pm_runtime_put_autosuspend() already calls pm_runtime_mark_last_busy() internally to update the last access time of the device before queuing autosuspend. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260814112435.3290545-2-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 6f88b7dd2bc2..3eac456fe189 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -542,7 +542,6 @@ void serial8250_rpm_put(struct uart_8250_port *p) { if (!(p->capabilities & UART_CAP_RPM)) return; - pm_runtime_mark_last_busy(p->port.dev); pm_runtime_put_autosuspend(p->port.dev); } EXPORT_SYMBOL_GPL(serial8250_rpm_put); @@ -683,7 +682,6 @@ static void serial8250_rpm_put_tx(struct uart_8250_port *p) rpm_active = xchg(&p->rpm_tx_active, 0); if (!rpm_active) return; - pm_runtime_mark_last_busy(p->port.dev); pm_runtime_put_autosuspend(p->port.dev); } From 456ce32b60c43da2129545161dca0660673ac703 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 7 Aug 2026 16:09:43 +0700 Subject: [PATCH 11/91] serial: qcom-geni: Propagate errors from optional IRQ lookup platform_get_irq_optional() returns a positive IRQ number on success or a negative error code on failure. For an optional IRQ, -ENXIO indicates that no IRQ is available, while other errors should be propagated. Propagate errors such as -EPROBE_DEFER and -EINVAL instead of continuing probe without the IRQ. Signed-off-by: bui duc phuc Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260807090945.41690-1-phucduc.bui@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 3633723acef8..7162fe69be4a 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1947,8 +1947,13 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) uport->irq = irq; uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE); - if (!data->console) + if (!data->console) { port->wakeup_irq = platform_get_irq_optional(pdev, 1); + if (port->wakeup_irq < 0 && port->wakeup_irq != -ENXIO) { + ret = port->wakeup_irq; + goto error; + } + } if (of_property_read_bool(pdev->dev.of_node, "rx-tx-swap")) port->rx_tx_swap = true; From 22a4e56a37c8b8badb325a55e72d626399070ffe Mon Sep 17 00:00:00 2001 From: Steffen Dirkwinkel Date: Thu, 13 Aug 2026 20:37:05 +0200 Subject: [PATCH 12/91] serial: 8250_dw: Add device HID for AMDI0021 AMD UART controller This ID is found on AMD V1202B. Like the AMDI0022 [1] this one works with the same match and the existing driver. Tested with a loopback cable. [1] commit 3c35d2a960c0 ("serial: 8250_dw: Add device HID for new AMD UART controller") Signed-off-by: Steffen Dirkwinkel Link: https://git.kernel.org/torvalds/c/3c35d2a960c0 [1] Reviewed-by: Andy Shevchenko Acked-by: Shyam Sundar S K Link: https://patch.msgid.link/20260813-b4-raven-ridge-uart-v1-1-fae442416ba1@beckhoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/acpi/acpi_apd.c | 1 + drivers/tty/serial/8250/8250_dw.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c index e7366fcb76ee..9fc667d6776c 100644 --- a/drivers/acpi/acpi_apd.c +++ b/drivers/acpi/acpi_apd.c @@ -256,6 +256,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = { { "AMDI0015", APD_ADDR(wt_i3c_desc) }, { "AMDI0019", APD_ADDR(wt_i2c_desc) }, { "AMDI0020", APD_ADDR(cz_uart_desc) }, + { "AMDI0021", APD_ADDR(cz_uart_desc) }, { "AMDI0022", APD_ADDR(cz_uart_desc) }, { "HYGO0010", APD_ADDR(wt_i2c_desc) }, #endif diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 51d026f20825..4588e9cf0389 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -910,6 +910,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = { { "8086228A", (kernel_ulong_t)&dw8250_dw_apb }, { "AMD0020", (kernel_ulong_t)&dw8250_dw_apb }, { "AMDI0020", (kernel_ulong_t)&dw8250_dw_apb }, + { "AMDI0021", (kernel_ulong_t)&dw8250_dw_apb }, { "AMDI0022", (kernel_ulong_t)&dw8250_dw_apb }, { "APMC0D08", (kernel_ulong_t)&dw8250_apmc0d08 }, { "BRCM2032", (kernel_ulong_t)&dw8250_dw_apb }, From 16ccb505ff021ebbd1207c66e5f68521f6d7fa4c Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 24 Aug 2026 20:00:17 +0530 Subject: [PATCH 13/91] serial: qcom-geni: Drop unsafe rx_buf realloc from setup_fifos() The RX buffer is allocated once during probe using a fixed DMA_RX_BUF_SIZE and is DMA-mapped for the lifetime of the port. However, setup_fifos() attempts to reallocate rx_buf whenever the reported RX FIFO depth changes. Since the DMA mapping is not re-established after reallocation, the buffer pointer may change while the DMA engine continues using the stale DMA address. This can result in RX DMA targeting memory that no longer corresponds to the active buffer, leading to invalid DMA accesses and potential memory corruption. The RX FIFO depth is unrelated to the size of rx_buf. The buffer is allocated independently using DMA_RX_BUF_SIZE and all RX DMA paths consume it at that fixed size. As such, resizing the buffer based on FIFO depth changes provides no functional benefit. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260824-drop-unsafe-rx-buf-realloc-from-setup-fifos-v1-1-52d231c840e1@oss.qualcomm.com Link: https://patch.msgid.link/20260824-drop-unsafe-rx-buf-realloc-from-setup-fifos-v2-1-9a1c8f711e98@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 7162fe69be4a..a942b46f83ff 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1291,7 +1291,6 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) static int setup_fifos(struct qcom_geni_serial_port *port) { struct uart_port *uport; - u32 old_rx_fifo_depth = port->rx_fifo_depth; uport = &port->uport; port->tx_fifo_depth = geni_se_get_tx_fifo_depth(&port->se); @@ -1300,19 +1299,6 @@ static int setup_fifos(struct qcom_geni_serial_port *port) uport->fifosize = (port->tx_fifo_depth * port->tx_fifo_width) / BITS_PER_BYTE; - if (port->rx_buf && (old_rx_fifo_depth != port->rx_fifo_depth) && port->rx_fifo_depth) { - /* - * Use krealloc rather than krealloc_array because rx_buf is - * accessed as 1 byte entries as well as 4 byte entries so it's - * not necessarily an array. - */ - port->rx_buf = devm_krealloc(uport->dev, port->rx_buf, - port->rx_fifo_depth * sizeof(u32), - GFP_KERNEL); - if (!port->rx_buf) - return -ENOMEM; - } - return 0; } From 6a8352bb4e4bfcf67fa23c49d2e0d14684ffe2e6 Mon Sep 17 00:00:00 2001 From: Dmitry Sinyavin Date: Sun, 30 Aug 2026 10:53:40 +0200 Subject: [PATCH 14/91] serial: msm: select PM_OPP Commit 173ebdedcd84 ("serial: msm: Use OPP table for DVFS support") made the MSM serial driver unconditionally configure the core clock through the OPP framework during probe. When CONFIG_PM_OPP is disabled, devm_pm_opp_set_clkname() is a stub that returns -EOPNOTSUPP, so every MSM serial port fails to probe. Select PM_OPP to keep minimal configurations functional. Fixes: 173ebdedcd84 ("serial: msm: Use OPP table for DVFS support") Signed-off-by: Dmitry Sinyavin Link: https://patch.msgid.link/20260830085340.2542624-1-sinyavin@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index cf7dba473b20..bf680d22199b 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -904,6 +904,7 @@ config SERIAL_JSM config SERIAL_MSM tristate "MSM on-chip serial port support" depends on ARCH_QCOM || COMPILE_TEST + select PM_OPP select SERIAL_CORE config SERIAL_MSM_CONSOLE From 4aa6ccfe4b84a4d82f7d690df419af429d19efcf Mon Sep 17 00:00:00 2001 From: Swark Yang Date: Wed, 5 Aug 2026 22:03:37 -0700 Subject: [PATCH 15/91] dt-bindings: serial: Document Axiado AX3005 UART The Axiado AX3005 UART is software-compatible with the Cadence r1p12 UART programming interface. Document the "axiado,ax3005-uart" SoC-specific compatible with "cdns,uart-r1p12" as its fallback. This preserves identification of the AX3005 integration while allowing the controller to use the existing Cadence r1p12 UART support. Acked-by: Krzysztof Kozlowski Signed-off-by: Swark Yang Acked-by: Michal Simek Link: https://patch.msgid.link/20260805-upstrea-ax3005-uart-v4-v4-1-8399f0773b13@axiado.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/cdns,uart.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/serial/cdns,uart.yaml b/Documentation/devicetree/bindings/serial/cdns,uart.yaml index 9d3e5c1d8502..11292de42c67 100644 --- a/Documentation/devicetree/bindings/serial/cdns,uart.yaml +++ b/Documentation/devicetree/bindings/serial/cdns,uart.yaml @@ -19,6 +19,7 @@ properties: - items: - enum: - axiado,ax3000-uart + - axiado,ax3005-uart - xlnx,zynqmp-uart - const: cdns,uart-r1p12 From 22c6a6ac74c2434735638e7ef1101597ae263aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:27 +0200 Subject: [PATCH 16/91] serial: txx9: Drop noop probe function and dangerous remove callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only platform device that can be bound to the driver is allocated by the driver itself. That device doesn't have platdata, so the probe function only consists of a loop that is never run and thus can be dropped without loss of functionality. The remove function then should better not undo things that were not done in the first place. That happens because serial_txx9_init() calls serial_txx9_register_ports() with the created platform device and so up->dev == &dev->dev evaluates to true and uart_remove_one_port() is called twice for each port. So let serial_txx9_exit() undo exactly what serial_txx9_init() does and remove the useless respectively wrong functions. Fixes: 0970769aceb9 ("[SERIAL] serial_txx9 driver update") Reviewed-by: Atsushi Nemoto Tested-by: Geert Uytterhoeven Acked-by: Andy Shevchenko Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/cd4fce2cef14907ebe4713c147f8896924380e44.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_txx9.c | 55 ++------------------------------ 1 file changed, 3 insertions(+), 52 deletions(-) diff --git a/drivers/tty/serial/serial_txx9.c b/drivers/tty/serial/serial_txx9.c index 4ae9a45c8e3a..ef56979321b9 100644 --- a/drivers/tty/serial/serial_txx9.c +++ b/drivers/tty/serial/serial_txx9.c @@ -1009,7 +1009,8 @@ static void serial_txx9_unregister_port(int line) struct uart_port *uart = &serial_txx9_ports[line]; mutex_lock(&serial_txx9_mutex); - uart_remove_one_port(&serial_txx9_reg, uart); + if (uart->iobase || uart->mapbase) + uart_remove_one_port(&serial_txx9_reg, uart); uart->flags = 0; uart->type = PORT_UNKNOWN; uart->iobase = 0; @@ -1019,52 +1020,6 @@ static void serial_txx9_unregister_port(int line) mutex_unlock(&serial_txx9_mutex); } -/* - * Register a set of serial devices attached to a platform device. - */ -static int serial_txx9_probe(struct platform_device *dev) -{ - struct uart_port *p = dev_get_platdata(&dev->dev); - struct uart_port port; - int ret, i; - - memset(&port, 0, sizeof(struct uart_port)); - for (i = 0; p && p->uartclk != 0; p++, i++) { - port.iobase = p->iobase; - port.membase = p->membase; - port.irq = p->irq; - port.uartclk = p->uartclk; - port.iotype = p->iotype; - port.flags = p->flags; - port.mapbase = p->mapbase; - port.dev = &dev->dev; - port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_TXX9_CONSOLE); - ret = serial_txx9_register_port(&port); - if (ret < 0) { - dev_err(&dev->dev, "unable to register port at index %d " - "(IO%lx MEM%llx IRQ%d): %d\n", i, - p->iobase, (unsigned long long)p->mapbase, - p->irq, ret); - } - } - return 0; -} - -/* - * Remove serial ports registered against a platform device. - */ -static void serial_txx9_remove(struct platform_device *dev) -{ - int i; - - for (i = 0; i < UART_NR; i++) { - struct uart_port *up = &serial_txx9_ports[i]; - - if (up->dev == &dev->dev) - serial_txx9_unregister_port(i); - } -} - #ifdef CONFIG_PM static int serial_txx9_suspend(struct platform_device *dev, pm_message_t state) { @@ -1096,8 +1051,6 @@ static int serial_txx9_resume(struct platform_device *dev) #endif static struct platform_driver serial_txx9_plat_driver = { - .probe = serial_txx9_probe, - .remove = serial_txx9_remove, #ifdef CONFIG_PM .suspend = serial_txx9_suspend, .resume = serial_txx9_resume, @@ -1251,9 +1204,7 @@ static void __exit serial_txx9_exit(void) platform_driver_unregister(&serial_txx9_plat_driver); platform_device_unregister(serial_txx9_plat_devs); for (i = 0; i < UART_NR; i++) { - struct uart_port *up = &serial_txx9_ports[i]; - if (up->iobase || up->mapbase) - uart_remove_one_port(&serial_txx9_reg, up); + serial_txx9_unregister_port(i); } uart_unregister_driver(&serial_txx9_reg); From 32ba8093a8fe877807e7e4cf25c7f85ce6828f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:28 +0200 Subject: [PATCH 17/91] serial: txx9: Drop usage of uart_match_port() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Where the uart_match_port() function is used it's known that .iotype is UPIO_PORT. So uart_match_port() is equivalent to comparing .iobase. Remove the call to uart_match_port() which then only has a single caller that is handled in the next commit. Signed-off-by: Uwe Kleine-König (The Capable Hub) Reviewed-by: Geert Uytterhoeven Reviewed-by: Atsushi Nemoto Link: https://patch.msgid.link/b9287ba4b13b32a11765c0532db5d34843227373.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_txx9.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/serial_txx9.c b/drivers/tty/serial/serial_txx9.c index ef56979321b9..5b574ed276b2 100644 --- a/drivers/tty/serial/serial_txx9.c +++ b/drivers/tty/serial/serial_txx9.c @@ -965,7 +965,12 @@ static int serial_txx9_register_port(struct uart_port *port) mutex_lock(&serial_txx9_mutex); for (i = 0; i < UART_NR; i++) { uart = &serial_txx9_ports[i]; - if (uart_match_port(uart, port)) { + /* + * serial_txx9_register_port() is only called from + * pciserial_txx9_init_one(). So .iotype is UPIO_PORT and + * comparing .iobase serves to distinguish ports. + */ + if (uart->iobase == port->iobase) { uart_remove_one_port(&serial_txx9_reg, uart); break; } From a0ffa28e874683cea74518936634155e89c27762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:29 +0200 Subject: [PATCH 18/91] serial: 8250: Make uart_match_port() a 8250 specific function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 8250 driver conglomerate is the only user of uart_match_port() and in fact it already is 8250 specific as it uses hub6_match_port() which is defined in drivers/tty/serial/8250/8250_hub6.c unless the dummy implementation is in use. Acked-by: Andy Shevchenko Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/be86d77ed317b0e77249c9a2f4d68780c1f60f90.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/serial/driver.rst | 2 +- drivers/tty/serial/8250/8250_core.c | 27 +++++++++++++++++++++- drivers/tty/serial/serial_core.c | 27 ---------------------- include/linux/serial_core.h | 2 -- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/Documentation/driver-api/serial/driver.rst b/Documentation/driver-api/serial/driver.rst index c1db6a1a67c4..975b0ba53e98 100644 --- a/Documentation/driver-api/serial/driver.rst +++ b/Documentation/driver-api/serial/driver.rst @@ -70,7 +70,7 @@ Other functions .. kernel-doc:: drivers/tty/serial/serial_core.c :identifiers: uart_update_timeout uart_get_baud_rate uart_get_divisor - uart_match_port uart_write_wakeup uart_register_driver + uart_write_wakeup uart_register_driver uart_unregister_driver uart_suspend_port uart_resume_port uart_add_one_port uart_remove_one_port uart_console_write uart_parse_earlycon uart_parse_options uart_set_options diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index b875d394796f..d8199c583830 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -673,6 +673,31 @@ EXPORT_SYMBOL(serial8250_resume_port); */ static DEFINE_MUTEX(serial_mutex); +/** + * serial8250_match_port - are the two ports equivalent? + * @port1: first port + * @port2: second port + * + * This utility function can be used to determine whether two uart_port + * structures describe the same port. + */ +static bool serial8250_match_port(const struct uart_port *port1, + const struct uart_port *port2) +{ + if (port1->iotype != port2->iotype) + return false; + else if (port1->iotype == UPIO_PORT) + return port1->iobase == port2->iobase; + else if (port1->iotype == UPIO_HUB6) + return hub6_match_port(port1, port2); + else if (uart_iotype_mmio(port1->iotype)) + return port1->mapbase == port2->mapbase; + else if (port1->iotype == UPIO_BUS) + return true; + else + return false; +} + static struct uart_8250_port *serial8250_find_match_or_unused(const struct uart_port *port) { int i; @@ -681,7 +706,7 @@ static struct uart_8250_port *serial8250_find_match_or_unused(const struct uart_ * First, find a port entry which matches. */ for (i = 0; i < nr_uarts; i++) - if (uart_match_port(&serial8250_ports[i].port, port)) + if (serial8250_match_port(&serial8250_ports[i].port, port)) return &serial8250_ports[i]; /* try line number first if still available */ diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 9de26d8c9921..bc863593ce68 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -33,7 +33,6 @@ #include #include "serial_base.h" -#include "8250/8250.h" /* For hub6_match_port() */ /* * This is used to lock changes in serial line configuration. @@ -3237,32 +3236,6 @@ static void serial_core_remove_one_port(struct uart_driver *drv, state->uart_port = NULL; } -/** - * uart_match_port - are the two ports equivalent? - * @port1: first port - * @port2: second port - * - * This utility function can be used to determine whether two uart_port - * structures describe the same port. - */ -bool uart_match_port(const struct uart_port *port1, - const struct uart_port *port2) -{ - if (port1->iotype != port2->iotype) - return false; - else if (port1->iotype == UPIO_PORT) - return port1->iobase == port2->iobase; - else if (port1->iotype == UPIO_HUB6) - return hub6_match_port(port1, port2); - else if (uart_iotype_mmio(port1->iotype)) - return port1->mapbase == port2->mapbase; - else if (port1->iotype == UPIO_BUS) - return true; - else - return false; -} -EXPORT_SYMBOL(uart_match_port); - static struct serial_ctrl_device * serial_core_get_ctrl_dev(struct serial_port_device *port_dev) { diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 904760876ca8..4eeb8253752a 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -1140,8 +1140,6 @@ int uart_add_one_port(struct uart_driver *reg, struct uart_port *port); void uart_remove_one_port(struct uart_driver *reg, struct uart_port *port); int uart_read_port_properties(struct uart_port *port); int uart_read_and_validate_port_properties(struct uart_port *port); -bool uart_match_port(const struct uart_port *port1, - const struct uart_port *port2); /* * Power Management From 4c510e2595c72a993401b3a10a4e7d2857f02653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:30 +0200 Subject: [PATCH 19/91] serial: 8250: Simplify serial8250_match_port() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For 8250 devices .iotype is never UPIO_BUS, so drop the respective if branch. Suggested-by: Hugo Villeneuve Link: https://lore.kernel.org/linux-serial/20260803152228.9e3cd296a41e68304b82a22a@hugovil.com Acked-by: Andy Shevchenko Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/5723fc435061359f9f6206d20413276de7a659a4.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index d8199c583830..11424fef7c08 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -692,8 +692,6 @@ static bool serial8250_match_port(const struct uart_port *port1, return hub6_match_port(port1, port2); else if (uart_iotype_mmio(port1->iotype)) return port1->mapbase == port2->mapbase; - else if (port1->iotype == UPIO_BUS) - return true; else return false; } From c526404a4a92dd83b2332446d544eac7cfd3d376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:31 +0200 Subject: [PATCH 20/91] serial: 8250: Fix corner case for port matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the case CONFIG_SERIAL_8250=y + CONFIG_SERIAL_8250_HUB6=m hub6_match_port() evaluates statically to false in serial8250_match_port() which is a bug and a change in behavior compared to before commit 7ab80d1e7243 ("serial: 8250: fix compile error with hub6_match_port() when compiled as a module"). To make the comparison correct, the dummy implementation of hub6_match_port() must only be used if CONFIG_SERIAL_8250_HUB6 is disabled. Then as there is a bidirectional dependency between 8250_hub6.c and 8250_core.c, put both in the same module. Fixes: 7ab80d1e7243 ("serial: 8250: fix compile error with hub6_match_port() when compiled as a module") Acked-by: Andy Shevchenko Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/9ad6983ae584ce9d962f2a841f4d5e90c2012ceb.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 8 +++++++- drivers/tty/serial/8250/8250_hub6.c | 8 +------- drivers/tty/serial/8250/8250_platform.c | 4 ++++ drivers/tty/serial/8250/Kconfig | 4 ++-- drivers/tty/serial/8250/Makefile | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 77b79c95d207..54b4d5a5e36e 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -322,6 +322,12 @@ static inline int serial8250_pnp_init(void) { return 0; } static inline void serial8250_pnp_exit(void) { } #endif +#ifdef CONFIG_SERIAL_8250_HUB6 +int serial8250_hub6_init(void); +#else +static inline int serial8250_hub6_init(void) { return 0; } +#endif + #ifdef CONFIG_SERIAL_8250_RSA void univ8250_rsa_support(struct uart_ops *ops, const struct uart_ops *core_ops); void rsa_enable(struct uart_8250_port *up); @@ -342,7 +348,7 @@ int fintek_8250_probe(struct uart_8250_port *uart); static inline int fintek_8250_probe(struct uart_8250_port *uart) { return 0; } #endif -#if IS_REACHABLE(CONFIG_SERIAL_8250_HUB6) +#if IS_ENABLED(CONFIG_SERIAL_8250_HUB6) bool hub6_match_port(const struct uart_port *port1, const struct uart_port *port2); #else static inline bool hub6_match_port(const struct uart_port *port1, const struct uart_port *port2) diff --git a/drivers/tty/serial/8250/8250_hub6.c b/drivers/tty/serial/8250/8250_hub6.c index b6767633c966..3e548530ec4e 100644 --- a/drivers/tty/serial/8250/8250_hub6.c +++ b/drivers/tty/serial/8250/8250_hub6.c @@ -49,13 +49,7 @@ bool hub6_match_port(const struct uart_port *port1, const struct uart_port *port } EXPORT_SYMBOL_GPL(hub6_match_port); -static int __init hub6_init(void) +int __init serial8250_hub6_init(void) { return platform_device_register(&hub6_device); } - -module_init(hub6_init); - -MODULE_AUTHOR("Russell King"); -MODULE_DESCRIPTION("8250 serial probe module for Hub6 cards"); -MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index af946d12e764..3a2dff9ce7b7 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -319,6 +319,10 @@ static int __init serial8250_init(void) if (ret) goto unreg_uart_drv; + ret = serial8250_hub6_init(); + if (ret) + goto unreg_pnp; + serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY); if (!serial8250_isa_devs) { ret = -ENOMEM; diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index 1d5c516a7612..82e761b8298a 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -304,8 +304,8 @@ config SERIAL_8250_EXAR_ST16C554 will be called 8250_exar_st16c554. config SERIAL_8250_HUB6 - tristate "Support Hub6 cards" - depends on SERIAL_8250 != n && ISA && SERIAL_8250_MANY_PORTS + bool "Support Hub6 cards" + depends on SERIAL_8250_MANY_PORTS && ISA help Say Y here if you have a HUB6 serial board. diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index edf91c980a63..5ff2ba414e13 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SERIAL_8250) += 8250.o 8250-y := 8250_core.o 8250-y += 8250_platform.o 8250-$(CONFIG_SERIAL_8250_PNP) += 8250_pnp.o +8250-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250) += 8250_base.o 8250_base-y := 8250_port.o @@ -36,7 +37,6 @@ obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554) += 8250_exar_st16c554.o obj-$(CONFIG_SERIAL_8250_FOURPORT) += 8250_fourport.o obj-$(CONFIG_SERIAL_8250_FSL) += 8250_fsl.o 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 From a300273c458ca32666f5bc6c1b3eb3a7cca206ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:32 +0200 Subject: [PATCH 21/91] serial: 8250: hub6: Add cleanup code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At module load time a platform device is registered. That should be undone when the module is unloaded. Fixes: ec9f47cd6a14 ("[PATCH] Serial: Split 8250 port table") Acked-by: Andy Shevchenko Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/4f1903ff2907f3e8bbabce345f628771e0bbd4e1.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 2 ++ drivers/tty/serial/8250/8250_hub6.c | 5 +++++ drivers/tty/serial/8250/8250_platform.c | 5 ++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 54b4d5a5e36e..4dfe8c65a3d8 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -324,8 +324,10 @@ static inline void serial8250_pnp_exit(void) { } #ifdef CONFIG_SERIAL_8250_HUB6 int serial8250_hub6_init(void); +void serial8250_hub6_exit(void); #else static inline int serial8250_hub6_init(void) { return 0; } +static inline void serial8250_hub6_exit(void) { } #endif #ifdef CONFIG_SERIAL_8250_RSA diff --git a/drivers/tty/serial/8250/8250_hub6.c b/drivers/tty/serial/8250/8250_hub6.c index 3e548530ec4e..4d4c54c951cb 100644 --- a/drivers/tty/serial/8250/8250_hub6.c +++ b/drivers/tty/serial/8250/8250_hub6.c @@ -53,3 +53,8 @@ int __init serial8250_hub6_init(void) { return platform_device_register(&hub6_device); } + +void __init_or_module serial8250_hub6_exit(void) +{ + platform_device_unregister(&hub6_device); +} diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index 3a2dff9ce7b7..0570366a71d0 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -326,7 +326,7 @@ static int __init serial8250_init(void) serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY); if (!serial8250_isa_devs) { ret = -ENOMEM; - goto unreg_pnp; + goto unreg_hub6; } ret = platform_device_add(serial8250_isa_devs); @@ -342,6 +342,8 @@ static int __init serial8250_init(void) platform_device_del(serial8250_isa_devs); put_dev: platform_device_put(serial8250_isa_devs); +unreg_hub6: + serial8250_hub6_exit(); unreg_pnp: serial8250_pnp_exit(); unreg_uart_drv: @@ -369,6 +371,7 @@ static void __exit serial8250_exit(void) platform_driver_unregister(&serial8250_isa_driver); platform_device_unregister(isa_dev); + serial8250_hub6_exit(); serial8250_pnp_exit(); #ifdef CONFIG_SPARC From 4cf187f9dea92cd878a2c2826f56d2dd6db3650e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 14 Aug 2026 11:19:33 +0200 Subject: [PATCH 22/91] serial: 8250: pnp: Annotate init and exit functions for conditional discarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit serial8250_pnp_init() is only called from serial8250_init() which is marked with __init, so the pnp init function can also get the same marking. serial8250_pnp_exit() is only called from __init and __exit code, so __init_or_module is appropriate for that. Acked-by: Andy Shevchenko Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/15308b3e517a9d7894d1dd1a1ad7163e78bb5929.1786697789.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pnp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index 6bfdeff5fe22..7decf62400ab 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -526,12 +526,12 @@ static struct pnp_driver serial_pnp_driver = { .id_table = pnp_dev_table, }; -int serial8250_pnp_init(void) +int __init serial8250_pnp_init(void) { return pnp_register_driver(&serial_pnp_driver); } -void serial8250_pnp_exit(void) +void __init_or_module serial8250_pnp_exit(void) { pnp_unregister_driver(&serial_pnp_driver); } From 2c5942fd0300cb966339dd0366890be626b405e8 Mon Sep 17 00:00:00 2001 From: Tate Whiteberg Date: Mon, 31 Aug 2026 16:59:07 +0000 Subject: [PATCH 23/91] serial: 8250: export and rename wait_for_xmitr() Export wait_for_xmitr() and rename it to follow naming convention. Signed-off-by: Tate Whiteberg Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260831165911.2029839-2-whiteberg@arista.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 1 + drivers/tty/serial/8250/8250_port.c | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 4dfe8c65a3d8..21ed9c41b751 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -186,6 +186,7 @@ void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p); void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, struct nbcon_write_context *wctxt, unsigned int count); +void serial8250_wait_for_xmitr(struct uart_8250_port *up, int bits); void serial8250_rpm_get(struct uart_8250_port *p); void serial8250_rpm_put(struct uart_8250_port *p); diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 3eac456fe189..b386c4292861 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -2038,7 +2038,7 @@ static bool wait_for_lsr(struct uart_8250_port *up, int bits) } /* Wait for transmitter and holding register to empty with timeout */ -static void wait_for_xmitr(struct uart_8250_port *up, int bits) +void serial8250_wait_for_xmitr(struct uart_8250_port *up, int bits) { unsigned int tmout; bool tx_ready; @@ -2066,6 +2066,7 @@ static void wait_for_xmitr(struct uart_8250_port *up, int bits) } } } +EXPORT_SYMBOL_NS_GPL(serial8250_wait_for_xmitr, "SERIAL_8250"); #ifdef CONFIG_CONSOLE_POLL /* @@ -2112,7 +2113,7 @@ static void serial8250_put_poll_char(struct uart_port *port, ier = serial_port_in(port, UART_IER); __serial8250_clear_IER(up); - wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); + serial8250_wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); /* * Send the character out. */ @@ -2122,7 +2123,7 @@ static void serial8250_put_poll_char(struct uart_port *port, * Finally, wait for transmitter to become empty * and restore the IER */ - wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); + serial8250_wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); serial_port_out(port, UART_IER, ier); } @@ -2237,7 +2238,7 @@ static void serial8250_THRE_test(struct uart_port *port) * Synchronize UART_IER access against the console. */ scoped_guard(uart_port_lock_irqsave, port) { - wait_for_xmitr(up, UART_LSR_THRE); + serial8250_wait_for_xmitr(up, UART_LSR_THRE); serial_port_out_sync(port, UART_IER, UART_IER_THRI); udelay(1); /* allow THRE to set */ iir_noint1 = serial_port_in(port, UART_IIR) & UART_IIR_NO_INT; @@ -3307,7 +3308,7 @@ static void serial8250_console_wait_putchar(struct uart_port *port, unsigned cha { struct uart_8250_port *up = up_to_u8250p(port); - wait_for_xmitr(up, UART_LSR_THRE); + serial8250_wait_for_xmitr(up, UART_LSR_THRE); serial8250_console_putchar(port, ch); } @@ -3518,7 +3519,7 @@ void serial8250_console_write(struct uart_8250_port *up, * Finally, wait for transmitter to become empty * and restore the IER */ - wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); + serial8250_wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); if (em485) { mdelay(port->rs485.delay_rts_after_send); From 1c305d5f1daabd317a60b6a2e2ac6b1aff7e05df Mon Sep 17 00:00:00 2001 From: Tate Whiteberg Date: Mon, 31 Aug 2026 16:59:08 +0000 Subject: [PATCH 24/91] serial: 8250_mid: wait for LSR tx empty before setting termios If mid8250_set_termios is called while data is still in transmission, the corresponding register updates will corrupt the transmission. Fix this by locking the port and and waiting for the transmitter to empty before performing updates. It is necessary to wait for both UART_LSR_THRE and UART_LSR_TEMT to ensure the final character is sent. Fixes: f549e94effa1 ("serial: 8250_pci: add Intel Penwell ports") Signed-off-by: Tate Whiteberg Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260831165911.2029839-3-whiteberg@arista.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mid.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c index 82656645b8a6..c02ecb96e8b6 100644 --- a/drivers/tty/serial/8250/8250_mid.c +++ b/drivers/tty/serial/8250/8250_mid.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -209,6 +210,7 @@ static void mid8250_set_termios(struct uart_port *p, struct ktermios *termios, const struct ktermios *old) { unsigned int baud = tty_termios_baud_rate(termios); + struct uart_8250_port *up = up_to_u8250p(p); struct mid8250 *mid = p->private_data; unsigned short ps = 16; unsigned long fuart = baud * ps; @@ -231,11 +233,16 @@ static void mid8250_set_termios(struct uart_port *p, struct ktermios *termios, } rational_best_approximation(fuart, mid->board->freq, w, w, &mul, &div); - p->uartclk = fuart * 16 / ps; /* core uses ps = 16 always */ - writel(ps, p->membase + INTEL_MID_UART_PS); /* set PS */ - writel(mul, p->membase + INTEL_MID_UART_MUL); /* set MUL */ - writel(div, p->membase + INTEL_MID_UART_DIV); + scoped_guard(spinlock_irq, &p->lock) { + p->uartclk = fuart * 16 / ps; /* core uses ps = 16 always */ + + serial8250_wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); + + writel(ps, p->membase + INTEL_MID_UART_PS); /* set PS */ + writel(mul, p->membase + INTEL_MID_UART_MUL); /* set MUL */ + writel(div, p->membase + INTEL_MID_UART_DIV); + } serial8250_do_set_termios(p, termios, old); } From cbfd7a22a7cb3ad06a1349aebae26b86ed861d1f Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Mon, 31 Aug 2026 11:27:43 +0300 Subject: [PATCH 25/91] tty: port: replace get_zeroed_page() with kzalloc() tty_port_alloc_xmit_buf() allocates the transmit buffer of a tty port. The buffer only backs the port's kfifo, the data being sent is copied in and out of it. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260831-tty-v2-1-88cd58c3c640@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_port.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c index 54359310e293..44122921fc45 100644 --- a/drivers/tty/tty_port.c +++ b/drivers/tty/tty_port.c @@ -240,13 +240,13 @@ EXPORT_SYMBOL_GPL(tty_port_unregister_device); int tty_port_alloc_xmit_buf(struct tty_port *port) { - /* We may sleep in get_zeroed_page() */ + /* We may sleep in kzalloc() */ guard(mutex)(&port->buf_mutex); if (port->xmit_buf) return 0; - port->xmit_buf = (u8 *)get_zeroed_page(GFP_KERNEL); + port->xmit_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (port->xmit_buf == NULL) return -ENOMEM; @@ -259,7 +259,7 @@ EXPORT_SYMBOL(tty_port_alloc_xmit_buf); void tty_port_free_xmit_buf(struct tty_port *port) { guard(mutex)(&port->buf_mutex); - free_page((unsigned long)port->xmit_buf); + kfree(port->xmit_buf); port->xmit_buf = NULL; INIT_KFIFO(port->xmit_fifo); } @@ -288,7 +288,7 @@ static void tty_port_destructor(struct kref *kref) /* check if last port ref was dropped before tty release */ if (WARN_ON(port->itty)) return; - free_page((unsigned long)port->xmit_buf); + kfree(port->xmit_buf); tty_port_destroy(port); if (port->ops && port->ops->destruct) port->ops->destruct(port); From cdf5e6ea0d4443b20f6182ec012006f59b7848b9 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Mon, 31 Aug 2026 11:27:44 +0300 Subject: [PATCH 26/91] serial: core: replace get_zeroed_page() with kzalloc() uart_alloc_xmit_buf() allocates the transmit buffer of a serial port. The buffer only backs the port's kfifo, the data being sent is copied in and out of it. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. While on it, make the local variable holding the buffer a pointer to get rid of the casts. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260831-tty-v2-2-88cd58c3c640@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index bc863593ce68..8ade35eda353 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -246,29 +246,29 @@ static int uart_alloc_xmit_buf(struct tty_port *port) struct uart_state *state = container_of(port, struct uart_state, port); struct uart_port *uport; unsigned long flags; - unsigned long page; + u8 *buf; /* * Initialise and allocate the transmit and temporary * buffer. */ - page = get_zeroed_page(GFP_KERNEL); - if (!page) + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) return -ENOMEM; uport = uart_port_ref_lock(state, &flags); if (!state->port.xmit_buf) { - state->port.xmit_buf = (unsigned char *)page; + state->port.xmit_buf = buf; kfifo_init(&state->port.xmit_fifo, state->port.xmit_buf, PAGE_SIZE); uart_port_unlock_deref(uport, flags); } else { uart_port_unlock_deref(uport, flags); /* - * Do not free() the page under the port lock, see + * Do not free() the buffer under the port lock, see * uart_free_xmit_buf(). */ - free_page(page); + kfree(buf); } return 0; @@ -279,10 +279,10 @@ static void uart_free_xmit_buf(struct tty_port *port) struct uart_state *state = container_of(port, struct uart_state, port); struct uart_port *uport; unsigned long flags; - char *xmit_buf; + u8 *xmit_buf; /* - * Do not free() the transmit buffer page under the port lock since + * Do not free() the transmit buffer under the port lock since * this can create various circular locking scenarios. For instance, * console driver may need to allocate/free a debug object, which * can end up in printk() recursion. @@ -293,7 +293,7 @@ static void uart_free_xmit_buf(struct tty_port *port) INIT_KFIFO(port->xmit_fifo); uart_port_unlock_deref(uport, flags); - free_page((unsigned long)xmit_buf); + kfree(xmit_buf); } /* From fbce8df31104c2a12e020b8adc402927700dc041 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Mon, 31 Aug 2026 11:27:45 +0300 Subject: [PATCH 27/91] tty: hvcs: replace __get_free_page() with kmalloc() hvcs_initialize() allocates the buffer that receives the partner info returned by the H_VTERM_PARTNER_INFO hypercall. The buffer is passed to the hypervisor as a physical address and it must not cross a page boundary. kmalloc() guarantees that a power of two sized allocation is aligned to its size, so a PAGE_SIZE allocation is page aligned as well. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260831-tty-v2-3-88cd58c3c640@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/hvc/hvcs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/tty/hvc/hvcs.c b/drivers/tty/hvc/hvcs.c index d32b1e3c50bf..b94d59fa3627 100644 --- a/drivers/tty/hvc/hvcs.c +++ b/drivers/tty/hvc/hvcs.c @@ -1470,7 +1470,7 @@ static int hvcs_initialize(void) goto register_fail; } - hvcs_pi_buff = (unsigned long *) __get_free_page(GFP_KERNEL); + hvcs_pi_buff = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!hvcs_pi_buff) { rc = -ENOMEM; goto buff_alloc_fail; @@ -1486,7 +1486,7 @@ static int hvcs_initialize(void) return 0; kthread_fail: - free_page((unsigned long)hvcs_pi_buff); + kfree(hvcs_pi_buff); buff_alloc_fail: tty_unregister_driver(hvcs_tty_driver); register_fail: @@ -1528,7 +1528,7 @@ static void __exit hvcs_module_exit(void) kthread_stop(hvcs_task); spin_lock(&hvcs_pi_lock); - free_page((unsigned long)hvcs_pi_buff); + kfree(hvcs_pi_buff); hvcs_pi_buff = NULL; spin_unlock(&hvcs_pi_lock); From 37d7703dbd6ab1303be7be8d1b18df26892a0ed9 Mon Sep 17 00:00:00 2001 From: Aniket Randive Date: Mon, 31 Aug 2026 16:42:59 +0530 Subject: [PATCH 28/91] serial: qcom-geni: Fix port_ida handling for console and probe errors The console port uses a fixed line number and is not allocated from port_ida. However, qcom_geni_serial_remove() unconditionally frees the line number for all ports, including the console port. Skip ida_free() for console ports so that only IDs allocated from port_ida are returned. Also release allocated IDs from the common probe error path. Currently, IDs are freed only on the wake-IRQ failure path, causing leaks when probe fails after a successful allocation. Move the ida_free() call to the common error path and guard it for console ports. Route resources_init() failures through the common error path so the allocated line number is released correctly. Allocate the port structure before calling ida_alloc_range() in get_port_from_line(). This prevents IDs from being left allocated when memory allocation fails. Signed-off-by: Aniket Randive Link: https://patch.msgid.link/20260828-master-v1-1-1f8afa5f82c2@oss.qualcomm.com Reviewed-by: Praveen Talari Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260831-master-v2-1-0032ec7fd1ca@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index a942b46f83ff..992622f6016e 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -289,6 +289,10 @@ static struct qcom_geni_serial_port *get_port_from_line(int line, bool console, } else { int max_alias_num = of_alias_get_highest_id("serial"); + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + if (line < 0 || line >= nr_ports) line = ida_alloc_range(&port_ida, max_alias_num + 1, nr_ports - 1, GFP_KERNEL); @@ -299,10 +303,6 @@ static struct qcom_geni_serial_port *get_port_from_line(int line, bool console, if (line < 0) return ERR_PTR(-ENXIO); - port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); - if (!port) - return ERR_PTR(-ENOMEM); - port->uport.iotype = UPIO_MEM; port->uport.ops = &qcom_geni_uart_pops; port->uport.flags = UPF_BOOT_AUTOCONF; @@ -1883,7 +1883,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) ret = port->dev_data->resources_init(&port->se); if (ret) - return ret; + goto error; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { @@ -1970,7 +1970,6 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) port->wakeup_irq); if (ret) { device_init_wakeup(&pdev->dev, false); - ida_free(&port_ida, uport->line); goto error; } } @@ -1994,6 +1993,8 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) return 0; error: + if (!data->console) + ida_free(&port_ida, uport->line); if (port->rx_dma_addr) { dma_unmap_single(pdev->dev.parent, port->rx_dma_addr, DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); @@ -2015,7 +2016,8 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) irq_work_sync(&port->tx_kick); dev_pm_clear_wake_irq(&pdev->dev); device_init_wakeup(&pdev->dev, false); - ida_free(&port_ida, uport->line); + if (!port->dev_data->console) + ida_free(&port_ida, uport->line); uart_remove_one_port(drv, &port->uport); if (port->rx_dma_addr) { From 4a86249c87ec07c0fa89671c39f7b3c1075394fc Mon Sep 17 00:00:00 2001 From: Aniket Randive Date: Tue, 1 Sep 2026 15:20:54 +0530 Subject: [PATCH 29/91] serial: qcom-geni: Avoid double-free of PM domain list The SA8255P UART variants attach PM domains through devm_pm_domain_attach_list(), which registers a devres cleanup action to detach and free the PM domain list automatically on probe failure or device removal. Remove the manual dev_pm_domain_detach_list() calls from the probe error path and remove callback. These calls can operate on memory that has already been freed by the devres-managed cleanup, resulting in a potential double-free. Also remove the unused pm_domain.h header. Suggested-by: Konrad Dybcio Signed-off-by: Aniket Randive Reviewed-by: Praveen Talari Reviewed-by: Konrad Dybcio Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20260901-probe_cleanup_uart-v1-1-0bc2b00c970e@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 992622f6016e..4fc60c745b4b 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -2000,7 +1999,6 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); port->rx_dma_addr = 0; } - dev_pm_domain_detach_list(port->se.pd_list); return ret; } @@ -2025,8 +2023,6 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); port->rx_dma_addr = 0; } - - dev_pm_domain_detach_list(port->se.pd_list); } static int __maybe_unused qcom_geni_serial_runtime_suspend(struct device *dev) From 27ca9a81485f179aac83d25fcc3234bc927fa17a Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 1 Sep 2026 12:15:17 +0800 Subject: [PATCH 30/91] serial: 8250_mxpcie: set the driver data before registering ports mxpcie8250_rs485_config() looks the board up with dev_get_drvdata() on the PCI device, but pci_set_drvdata() only runs after the registration loop. Where mxpcie8250_setup_port() presets rs485.flags to SER_RS485_ENABLED and the flag survives uart_get_rs485_mode() - the device has no firmware node, or its node sets linux,rs485-enabled-at-boot-time - uart_configure_port() calls ->rs485_config() from inside serial8250_register_8250_port(), and the callback dereferences a NULL board pointer. Publish the driver data before the first port is registered. Fixes: d21a1509c623 ("serial: 8250_mxpcie: support serial interface mode switching") Signed-off-by: Linmao Li Reviewed-by: Andy Shevchenko Reviewed-by: Crescent Hsieh Link: https://patch.msgid.link/20260901041519.114737-2-lilinmao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index ce873fbd62e9..c0e3517d4e4e 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -542,6 +542,8 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id mxpcie8250_init_board(pdev, priv); + pci_set_drvdata(pdev, priv); + up.port.dev = dev; up.port.irq = pdev->irq; up.port.uartclk = MOXA_PUART_BASE_BAUD * 16; @@ -578,7 +580,6 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id } priv->port[i].rx_trig_level = MOXA_PUART_RX_TRIG_DEFAULT; } - pci_set_drvdata(pdev, priv); return 0; } From 39eaa90b6bd1dbe147c90c345796bc042080d80e Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 1 Sep 2026 12:15:18 +0800 Subject: [PATCH 31/91] serial: 8250_mxpcie: only unregister the ports that were registered When serial8250_register_8250_port() fails the loop stops and the probe keeps the ports registered so far, like pciserial_init_ports() in 8250_pci.c this driver was split from. What the split lost is that function's priv->nr: mxpcie8250_remove() walks all num_ports entries, but the ones the loop never reached keep the zero devm_kzalloc() left there, and the one that failed keeps a negative error code. serial8250_unregister_port() checks neither, so removal unregisters line 0 - a port this driver does not own - and indexes serial8250_ports[] with a negative line number. Record how many ports were registered and unregister only those. Fixes: 0481a041e956 ("serial: 8250: split Moxa PCIe serial board support out of 8250_pci") Signed-off-by: Linmao Li Reviewed-by: Andy Shevchenko Reviewed-by: Crescent Hsieh Link: https://patch.msgid.link/20260901041519.114737-3-lilinmao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index c0e3517d4e4e..ef2516ec16da 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -109,6 +109,7 @@ struct mxpcie8250_port { struct mxpcie8250 { unsigned int supp_rs; unsigned int num_ports; + unsigned int nr; /* ports actually registered */ void __iomem *bar1_base; /* UART registers (MMIO) */ void __iomem *bar2_base; /* UIR / GPIO / CPLD (IO) */ struct mxpcie8250_port port[] __counted_by(num_ports); @@ -517,6 +518,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id struct mxpcie8250 *priv; unsigned short device = pdev->device; unsigned int num_ports; + unsigned int i; int ret; ret = pcim_enable_device(pdev); @@ -564,7 +566,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.handle_irq = mxpcie8250_handle_irq; up.port.break_ctl = mxpcie8250_break_ctl; - for (unsigned int i = 0; i < num_ports; i++) { + for (i = 0; i < num_ports; i++) { mxpcie8250_setup_port(pdev, priv, &up, i); dev_dbg(dev, "Setup PCI port: port %lx, irq %d, type %d\n", @@ -580,6 +582,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id } priv->port[i].rx_trig_level = MOXA_PUART_RX_TRIG_DEFAULT; } + priv->nr = i; return 0; } @@ -588,7 +591,7 @@ static void mxpcie8250_remove(struct pci_dev *pdev) { struct mxpcie8250 *priv = pci_get_drvdata(pdev); - for (unsigned int i = 0; i < priv->num_ports; i++) + for (unsigned int i = 0; i < priv->nr; i++) serial8250_unregister_port(priv->port[i].line); } From dda3502cf1838f806b90405a127ec2e66c885c9d Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Tue, 1 Sep 2026 12:15:19 +0800 Subject: [PATCH 32/91] serial: 8250_mxpcie: take the line settings from the new termios mxpcie8250_set_termios() reads the line settings out of port->state->port.tty, which is only set once the port has been opened. uart_set_options() builds a termios of its own and calls ->set_termios() with no tty behind it, so using such a board as the console (console=ttyS) dereferences a NULL tty during console setup, as does attaching kgdboc to it and resuming a suspended console from uart_resume_port(). Read the settings from the termios the serial core passes in instead. It holds the same values on the normal path - uart_change_line_settings() passes &tty->termios - and it is what serial8250_do_set_termios() right above already uses. Fixes: 55edf8511f47 ("serial: 8250_mxpcie: enable automatic RTS/CTS flow control") Signed-off-by: Linmao Li Tested-by: Crescent Hsieh Reviewed-by: Andy Shevchenko Reviewed-by: Crescent Hsieh Link: https://patch.msgid.link/20260901041519.114737-4-lilinmao@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index ef2516ec16da..19f1f51eec47 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -218,8 +218,6 @@ static void mxpcie8250_set_termios(struct uart_port *port, const struct ktermios *old) { struct uart_8250_port *up = up_to_u8250p(port); - struct tty_struct *tty = port->state->port.tty; - unsigned int cflag = tty->termios.c_cflag; u8 efr, val; serial8250_do_set_termios(port, new, old); @@ -229,23 +227,25 @@ static void mxpcie8250_set_termios(struct uart_port *port, efr = serial_in(up, MOXA_PUART_EFR); efr &= ~(MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS); - if (cflag & CRTSCTS) { + if (new->c_cflag & CRTSCTS) { efr |= (MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS); up->port.status |= (UPSTAT_AUTORTS | UPSTAT_AUTOCTS); } /* Set on-chip software flow control character */ - serial_out(up, MOXA_PUART_XON1, START_CHAR(tty)); - serial_out(up, MOXA_PUART_XON2, START_CHAR(tty)); - serial_out(up, MOXA_PUART_XOFF1, STOP_CHAR(tty)); - serial_out(up, MOXA_PUART_XOFF2, STOP_CHAR(tty)); + serial_out(up, MOXA_PUART_XON1, new->c_cc[VSTART]); + serial_out(up, MOXA_PUART_XON2, new->c_cc[VSTART]); + serial_out(up, MOXA_PUART_XOFF1, new->c_cc[VSTOP]); + serial_out(up, MOXA_PUART_XOFF2, new->c_cc[VSTOP]); - val = I_IXON(tty) ? MOXA_PUART_EFR_RX_FLOW_XON1_XOFF1 : MOXA_PUART_EFR_RX_FLOW_DISABLED; + val = (new->c_iflag & IXON) ? MOXA_PUART_EFR_RX_FLOW_XON1_XOFF1 : + MOXA_PUART_EFR_RX_FLOW_DISABLED; FIELD_MODIFY(MOXA_PUART_EFR_RX_FLOW_MASK, &efr, val); - val = I_IXOFF(tty) ? MOXA_PUART_EFR_TX_FLOW_XON1_XOFF1 : MOXA_PUART_EFR_TX_FLOW_DISABLED; + val = (new->c_iflag & IXOFF) ? MOXA_PUART_EFR_TX_FLOW_XON1_XOFF1 : + MOXA_PUART_EFR_TX_FLOW_DISABLED; FIELD_MODIFY(MOXA_PUART_EFR_TX_FLOW_MASK, &efr, val); - if (I_IXOFF(tty)) + if (new->c_iflag & IXOFF) up->port.status |= UPSTAT_AUTOXOFF; serial_out(up, MOXA_PUART_EFR, efr); From 1e52dd94a4a218a933abf5b4090ff7e817e896e7 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:49:08 +0530 Subject: [PATCH 33/91] tty: serial: fix typo "inteface" in comment Correct "inteface" to "interface", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904121909.27377-1-hemanth.selam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/kgdboc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/kgdboc.c b/drivers/tty/serial/kgdboc.c index 09648d643897..b4aa117f76d8 100644 --- a/drivers/tty/serial/kgdboc.c +++ b/drivers/tty/serial/kgdboc.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Based on the same principle as kgdboe using the NETPOLL api, this - * driver uses a console polling api to implement a gdb serial inteface + * driver uses a console polling api to implement a gdb serial interface * which is multiplexed on a console port. * * Maintainer: Jason Wessel From d940e3d6970cb3c827331f0dcb48214ceaab6ba3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:58:40 +0530 Subject: [PATCH 34/91] tty: serial: fix typo "satify" in comment Correct "satify" to "satisfy", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260907062840.17276-1-hemanth.selam@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/arc_uart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/arc_uart.c b/drivers/tty/serial/arc_uart.c index 5c4895d154c0..3988e5958896 100644 --- a/drivers/tty/serial/arc_uart.c +++ b/drivers/tty/serial/arc_uart.c @@ -304,7 +304,7 @@ static unsigned int arc_serial_get_mctrl(struct uart_port *port) { /* * Pretend we have a Modem status reg and following bits are - * always set, to satify the serial core state machine + * always set, to satisfy the serial core state machine * (DSR) Data Set Ready * (CTS) Clear To Send * (CAR) Carrier Detect From 9598f73f14bb2a1d4e8fcc38c108c005f23dc4fe Mon Sep 17 00:00:00 2001 From: Li Jun Date: Tue, 8 Sep 2026 19:37:19 +0800 Subject: [PATCH 35/91] tty: atmel: Replace SIMPLE_DEV_PM_OPS with DEFINE_SIMPLE_DEV_PM_OPS Replace the deprecated SIMPLE_DEV_PM_OPS with DEFINE_SIMPLE_DEV_PM_OPS. This also drops the __maybe_unused annotations on the suspend/resume callbacks, since pm_sleep_ptr() inside the new macro already handles the case where CONFIG_PM_SLEEP is disabled. When CONFIG_PM=y but CONFIG_PM_SLEEP=n (a valid configuration that enables runtime PM but disables system suspend/hibernation), this keeps a completely empty dev_pm_ops structure -- all callbacks are NULL -- as dead code in the object file.Modify pm_ptr to pm_sleep_ptr. Signed-off-by: Li Jun Link: https://patch.msgid.link/20260908113719.4078979-1-lijun01@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 5d8c1cfc1c60..c3880259e3bc 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -2723,7 +2723,7 @@ static bool atmel_serial_clk_will_stop(void) #endif } -static int __maybe_unused atmel_serial_suspend(struct device *dev) +static int atmel_serial_suspend(struct device *dev) { struct uart_port *port = dev_get_drvdata(dev); struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); @@ -2765,7 +2765,7 @@ static int __maybe_unused atmel_serial_suspend(struct device *dev) return 0; } -static int __maybe_unused atmel_serial_resume(struct device *dev) +static int atmel_serial_resume(struct device *dev) { struct uart_port *port = dev_get_drvdata(dev); struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); @@ -2996,7 +2996,7 @@ static void atmel_serial_remove(struct platform_device *pdev) pdev->dev.of_node = NULL; } -static SIMPLE_DEV_PM_OPS(atmel_serial_pm_ops, atmel_serial_suspend, +static DEFINE_SIMPLE_DEV_PM_OPS(atmel_serial_pm_ops, atmel_serial_suspend, atmel_serial_resume); static struct platform_driver atmel_serial_driver = { @@ -3005,7 +3005,7 @@ static struct platform_driver atmel_serial_driver = { .driver = { .name = "atmel_usart_serial", .of_match_table = of_match_ptr(atmel_serial_dt_ids), - .pm = pm_ptr(&atmel_serial_pm_ops), + .pm = pm_sleep_ptr(&atmel_serial_pm_ops), }, }; From c5b33c86d86da3ccff8c05e2a28719e3ec933f70 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 10 Sep 2026 23:15:00 -0400 Subject: [PATCH 36/91] serial: sh-sci: Unregister UART on sysfs setup failure sci_probe_single() registers the UART port before sci_probe() creates the RX FIFO tuning attributes. If either device_create_file() call fails, probe returns an error without removing the port from the serial core. Device-managed resources are then released while the port remains registered. Unregister the UART port before returning either sysfs creation error. If the second attribute fails, remove the first attribute before unregistering the port. Keep the existing probe failure policy and leave the successful initialization path unchanged. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 5d23188a473d ("serial: sh-sci: make RX FIFO parameters tunable via sysfs") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260911031500.5745-1-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 92193eb26abf..50ae9aae6b61 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -3937,12 +3937,12 @@ static int sci_probe(struct platform_device *dev) if (sp->port.fifosize > 1) { ret = device_create_file(&dev->dev, &dev_attr_rx_fifo_trigger); if (ret) - return ret; + goto err_remove_port; ret = device_create_file(&dev->dev, &dev_attr_rx_fifo_timeout); if (ret) { device_remove_file(&dev->dev, &dev_attr_rx_fifo_trigger); - return ret; + goto err_remove_port; } } @@ -3952,6 +3952,10 @@ static int sci_probe(struct platform_device *dev) sci_ports_in_use |= BIT(dev_id); return 0; + +err_remove_port: + uart_remove_one_port(&sci_uart_driver, &sp->port); + return ret; } static int sci_suspend(struct device *dev) From 790aacba3fdfd3a227cdd5e4952c596ed66a0ed8 Mon Sep 17 00:00:00 2001 From: Malathi A Date: Thu, 17 Sep 2026 04:05:09 +0000 Subject: [PATCH 37/91] serial: 8250_uniphier: Use devm_clk_get_enabled() uniphier_uart_probe() enables priv->clk by hand and is then responsible for disabling it again on every error path. It misses one: when uart_read_port_properties() fails the function returns directly, leaving the clock prepared and enabled. The neighbouring path, taken when serial8250_register_8250_port() fails, does call clk_disable_unprepare(), which shows the leak is an oversight rather than intent. Rather than adding another manual unwind, switch to devm_clk_get_enabled() so the clock is released by devres. That removes the error path entirely, along with the explicit disables in the register failure path and in uniphier_uart_remove(). The clock is still disabled and re-enabled by hand across system sleep, which stays balanced: only SET_SYSTEM_SLEEP_PM_OPS is used, so the device cannot be unbound while suspended and devres always sees an enabled clock at detach. Found by smatch: drivers/tty/serial/8250/8250_uniphier.c:232 uniphier_uart_probe() warn: 'priv->clk' from clk_prepare_enable() not released on lines: 205. Fixes: 26e8349c0d76 ("serial: 8250_uniphier: Switch to use uart_read_port_properties()") Signed-off-by: Malathi A Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260917040511.103232-1-malathi.a2000@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_uniphier.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/tty/serial/8250/8250_uniphier.c b/drivers/tty/serial/8250/8250_uniphier.c index e3db60bf50c9..6fffac12277a 100644 --- a/drivers/tty/serial/8250/8250_uniphier.c +++ b/drivers/tty/serial/8250/8250_uniphier.c @@ -180,16 +180,12 @@ static int uniphier_uart_probe(struct platform_device *pdev) memset(&up, 0, sizeof(up)); - priv->clk = devm_clk_get(dev, NULL); + priv->clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(priv->clk)) { - dev_err(dev, "failed to get clock\n"); + dev_err(dev, "failed to get and enable clock\n"); return PTR_ERR(priv->clk); } - ret = clk_prepare_enable(priv->clk); - if (ret) - return ret; - up.port.uartclk = clk_get_rate(priv->clk); spin_lock_init(&priv->atomic_write_lock); @@ -222,7 +218,6 @@ static int uniphier_uart_probe(struct platform_device *pdev) ret = serial8250_register_8250_port(&up); if (ret < 0) { dev_err(dev, "failed to register 8250 port\n"); - clk_disable_unprepare(priv->clk); return ret; } priv->line = ret; @@ -237,7 +232,6 @@ static void uniphier_uart_remove(struct platform_device *pdev) struct uniphier8250_priv *priv = platform_get_drvdata(pdev); serial8250_unregister_port(priv->line); - clk_disable_unprepare(priv->clk); } static int __maybe_unused uniphier_uart_suspend(struct device *dev) From e9d84d836c80b6eee7103cf4ac900df58a63226a Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 16 Sep 2026 23:27:30 +0800 Subject: [PATCH 38/91] tty: serial: fsl_lpuart: use dmaengine_get_dma_device() for DMA mapping Replace all chan->device->dev references with dmaengine_get_dma_device(chan) so that DMA mapping goes through the correct device when the DMA controller has per-channel IOMMU domains (chan_dma_dev=true). Without this, DMA map/unmap/sync bypasses the IOMMU context configured by the eDMA driver. Signed-off-by: Peng Fan Reviewed-by: Frank Li Link: https://patch.msgid.link/20260916-lpuart-dma-v1-1-021be5b03e53@nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/fsl_lpuart.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c index b7919c05f0fb..c8575c965203 100644 --- a/drivers/tty/serial/fsl_lpuart.c +++ b/drivers/tty/serial/fsl_lpuart.c @@ -491,7 +491,7 @@ static void lpuart_dma_tx(struct lpuart_port *sport) sport->dma_tx_nents = kfifo_dma_out_prepare(&tport->xmit_fifo, sgl, ARRAY_SIZE(sport->tx_sgl), sport->dma_tx_bytes); - ret = dma_map_sg(chan->device->dev, sgl, sport->dma_tx_nents, + ret = dma_map_sg(dmaengine_get_dma_device(chan), sgl, sport->dma_tx_nents, DMA_TO_DEVICE); if (!ret) { dev_err(dev, "DMA mapping error for TX.\n"); @@ -502,7 +502,7 @@ static void lpuart_dma_tx(struct lpuart_port *sport) ret, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); if (!sport->dma_tx_desc) { - dma_unmap_sg(chan->device->dev, sgl, sport->dma_tx_nents, + dma_unmap_sg(dmaengine_get_dma_device(chan), sgl, sport->dma_tx_nents, DMA_TO_DEVICE); dev_err(dev, "Cannot prepare TX slave DMA!\n"); return; @@ -535,7 +535,7 @@ static void lpuart_dma_tx_complete(void *arg) return; } - dma_unmap_sg(chan->device->dev, sgl, sport->dma_tx_nents, + dma_unmap_sg(dmaengine_get_dma_device(chan), sgl, sport->dma_tx_nents, DMA_TO_DEVICE); uart_xmit_advance(&sport->port, sport->dma_tx_bytes); @@ -606,7 +606,7 @@ static void lpuart_flush_buffer(struct uart_port *port) if (sport->lpuart_dma_tx_use) { if (sport->dma_tx_in_progress) { - dma_unmap_sg(chan->device->dev, &sport->tx_sgl[0], + dma_unmap_sg(dmaengine_get_dma_device(chan), &sport->tx_sgl[0], sport->dma_tx_nents, DMA_TO_DEVICE); sport->dma_tx_in_progress = false; } @@ -1176,7 +1176,7 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) } /* CPU claims ownership of RX DMA buffer */ - dma_sync_sg_for_cpu(chan->device->dev, &sport->rx_sgl, 1, + dma_sync_sg_for_cpu(dmaengine_get_dma_device(chan), &sport->rx_sgl, 1, DMA_FROM_DEVICE); /* @@ -1237,7 +1237,7 @@ static void lpuart_copy_rx_to_tty(struct lpuart_port *sport) sport->last_residue = state.residue; exit: - dma_sync_sg_for_device(chan->device->dev, &sport->rx_sgl, 1, + dma_sync_sg_for_device(dmaengine_get_dma_device(chan), &sport->rx_sgl, 1, DMA_FROM_DEVICE); uart_port_unlock_irqrestore(&sport->port, flags); @@ -1374,7 +1374,7 @@ static inline int lpuart_start_rx_dma(struct lpuart_port *sport) return -ENOMEM; sg_init_one(&sport->rx_sgl, ring->buf, sport->rx_dma_rng_buf_len); - nent = dma_map_sg(chan->device->dev, &sport->rx_sgl, 1, + nent = dma_map_sg(dmaengine_get_dma_device(chan), &sport->rx_sgl, 1, DMA_FROM_DEVICE); if (!nent) { @@ -1430,7 +1430,7 @@ static inline int lpuart_start_rx_dma(struct lpuart_port *sport) return 0; err_unmap_sg: - dma_unmap_sg(chan->device->dev, &sport->rx_sgl, 1, DMA_FROM_DEVICE); + dma_unmap_sg(dmaengine_get_dma_device(chan), &sport->rx_sgl, 1, DMA_FROM_DEVICE); err_free_buf: kfree(ring->buf); ring->buf = NULL; @@ -1447,7 +1447,7 @@ static void lpuart_dma_rx_free(struct uart_port *port) if (!sport->dma_idle_int) timer_delete_sync(&sport->lpuart_timer); - dma_unmap_sg(chan->device->dev, &sport->rx_sgl, 1, DMA_FROM_DEVICE); + dma_unmap_sg(dmaengine_get_dma_device(chan), &sport->rx_sgl, 1, DMA_FROM_DEVICE); kfree(sport->rx_ring.buf); sport->rx_ring.tail = 0; sport->rx_ring.head = 0; From b815e9b19ea5cb33edb842aa1abba6cd26b1314d Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Wed, 9 Sep 2026 00:35:33 +0530 Subject: [PATCH 39/91] soc: qcom: geni-se: Correct QUP Core ICC vote constants The GENI_TO_CORE ("qup-core") ICC vote selects the QUP Core 2X clock rate. The CORE_2X_*_MHZ constants are expressed in Bps, but their values are several orders of magnitude too small. For example, the 50 MHz threshold is represented by 2500 rather than 25000000 Bps. As a result, clients using these constants can severely under-vote the QUP Core clock. Correct the constants to their intended Bps thresholds so that the ICC provider selects the corresponding QUP Core 2X clock rate. Fixes: 58ffbba6a399 ("soc: qcom: geni: Support for ICC voting") Signed-off-by: Viken Dadhaniya Link: https://patch.msgid.link/20260909-correct-icc-bandwidth-vote-constants-v1-1-fbebf6b3c341@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- include/linux/soc/qcom/geni-se.h | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/include/linux/soc/qcom/geni-se.h b/include/linux/soc/qcom/geni-se.h index 29a53bbc0dd4..2243005f5e2e 100644 --- a/include/linux/soc/qcom/geni-se.h +++ b/include/linux/soc/qcom/geni-se.h @@ -347,17 +347,18 @@ struct geni_se { #define QUP_SE_VERSION_2_5 0x20050000 /* - * Define bandwidth thresholds that cause the underlying Core 2X interconnect - * clock to run at the named frequency. These baseline values are recommended - * by the hardware team, and are not dynamically scaled with GENI bandwidth - * beyond basic on/off. + * ICC bandwidth values in Bps for the GENI_TO_CORE ("qup-core") path. + * Convert them with Bps_to_icc() before setting avg_bw. The QUP ICC provider + * maps each threshold to the Core 2X rate named by the macro suffix. + * + * These values are core clock votes, not GENI transfer bandwidths. */ -#define CORE_2X_19_2_MHZ 960 -#define CORE_2X_50_MHZ 2500 -#define CORE_2X_100_MHZ 5000 -#define CORE_2X_150_MHZ 7500 -#define CORE_2X_200_MHZ 10000 -#define CORE_2X_236_MHZ 16383 +#define CORE_2X_19_2_MHZ 9600000 +#define CORE_2X_50_MHZ 25000000 +#define CORE_2X_100_MHZ 50000000 +#define CORE_2X_150_MHZ 75000000 +#define CORE_2X_200_MHZ 100000000 +#define CORE_2X_236_MHZ 118000000 #define GENI_DEFAULT_BW Bps_to_icc(1000) From d147a15be6840be7e842f7dcd658345a787e7867 Mon Sep 17 00:00:00 2001 From: Viken Dadhaniya Date: Wed, 9 Sep 2026 00:35:34 +0530 Subject: [PATCH 40/91] tty: serial: qcom_geni_serial: Keep console RX functional after deep idle At baud rates up to 115200, the serial console uses only a 1 kBps keepalive vote for the GENI_TO_CORE ("qup-core") ICC path. This vote keeps the path active but does not request a QUP Core 2X clock rate. When the CPU enters a deeper idle state, the missing Core clock vote can leave the console RX path unresponsive. Use the 19.2 MHz Core 2X vote at low baud rates, while retaining the 50 MHz vote at higher baud rates, so that console RX remains functional after deep idle transitions. Fixes: 7cf563b2c846 ("tty: serial: qcom_geni_serial: Add interconnect support") Signed-off-by: Viken Dadhaniya Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260909-correct-icc-bandwidth-vote-constants-v1-2-fbebf6b3c341@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 4fc60c745b4b..3502a3b6675e 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1467,9 +1467,8 @@ static int geni_serial_set_rate(struct geni_se *se, unsigned long baud) * Bump up BW vote on CPU and CORE path as driver supports FIFO mode * only. */ - avg_bw_core = (baud > 115200) ? Bps_to_icc(CORE_2X_50_MHZ) - : GENI_DEFAULT_BW; - port->se.icc_paths[GENI_TO_CORE].avg_bw = avg_bw_core; + avg_bw_core = (baud > 115200) ? CORE_2X_50_MHZ : CORE_2X_19_2_MHZ; + port->se.icc_paths[GENI_TO_CORE].avg_bw = Bps_to_icc(avg_bw_core); port->se.icc_paths[CPU_TO_GENI].avg_bw = Bps_to_icc(baud); geni_icc_set_bw(&port->se); From 7c08f3c4da9205fc64a9c99180f9cffc3e435f7f Mon Sep 17 00:00:00 2001 From: Yicong Yang Date: Tue, 8 Sep 2026 22:15:24 +0800 Subject: [PATCH 41/91] serial: 8250_dw: Prefer SRBR in bogus RX timeout workaround if available The DW uart could get into the cases where a bogus RX timeout interrupt is asserted but no available data. This could be workaround by doing a bogus read. Currently the driver's using the standard RBR (receive buffer register) for this bogus read. However the reading of RBR in this case is allowed to raise a hardware error if vendor choose to implement in this way (our platform). It's also allowed to do the bogus read using SRBR (shadow RBR) for workaround which won't raise the hardware error. So change to use the SRBR to workaround the issue if it's available. Signed-off-by: Yicong Yang Link: https://lore.kernel.org/linux-serial/20260907074035.77796-1-yang.yicong@picoheart.com/ Link: https://lore.kernel.org/linux-serial/20260629075510.32854-1-yang.yicong@picoheart.com/ Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260908141524.20302-1-yang.yicong@picoheart.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dw.c | 9 ++++++++- drivers/tty/serial/8250/8250_dwlib.c | 5 +++++ drivers/tty/serial/8250/8250_dwlib.h | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 4588e9cf0389..964c268670ea 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -439,8 +439,15 @@ static int dw8250_handle_irq(struct uart_port *p) if (!up->dma && rx_timeout) { status = serial_lsr_in(up); + /* + * Do the bogus read from Shadow RBR (SRBR) if provided. + * Both RBR and SRBR are supported ways to workaround + * this problem. But read RBR can cause hardware error + * (PSLVERR) if hardware choose to implement in this way + * (implement REG_TIMEOUT_WIDTH to 0), whereas SRBR won't. + */ if (!(status & (UART_LSR_DR | UART_LSR_BI))) - serial_port_in(p, UART_RX); + serial_port_in(p, d->data.srbr); } /* Manually stop the Rx DMA transfer when acting as flow controller */ diff --git a/drivers/tty/serial/8250/8250_dwlib.c b/drivers/tty/serial/8250/8250_dwlib.c index 8859e66d2d71..9bb02a4ab11f 100644 --- a/drivers/tty/serial/8250/8250_dwlib.c +++ b/drivers/tty/serial/8250/8250_dwlib.c @@ -247,5 +247,10 @@ void dw8250_setup_port(struct uart_port *p) if (reg & DW_UART_CPR_SIR_MODE) up->capabilities |= UART_CAP_IRDA; + + if (reg & DW_UART_CPR_SHADOW) + pd->srbr = DW_UART_SRBR_0; + else + pd->srbr = UART_RX; } EXPORT_SYMBOL_GPL(dw8250_setup_port); diff --git a/drivers/tty/serial/8250/8250_dwlib.h b/drivers/tty/serial/8250/8250_dwlib.h index 1fe52332e774..ee7a07fac0f6 100644 --- a/drivers/tty/serial/8250/8250_dwlib.h +++ b/drivers/tty/serial/8250/8250_dwlib.h @@ -13,6 +13,7 @@ #include "8250.h" /* Offsets for the DesignWare specific registers */ +#define DW_UART_SRBR_0 0x0c /* Shadow Receive Buffer Register */ #define DW_UART_USR 0x1f /* UART Status Register */ #define DW_UART_DMASA 0xa8 /* DMA Software Ack */ #define DW_UART_TCR 0xac /* Transceiver Control Register (RS485) */ @@ -90,6 +91,10 @@ struct dw8250_port_data { /* RS485 variables */ bool hw_rs485_support; + + /* Register offsets */ + /* Shadow RBR (if not defined RBR will be used) */ + int srbr; }; void dw8250_do_set_termios(struct uart_port *p, struct ktermios *termios, const struct ktermios *old); From 9b0bc6f8b31432f466915119b5e9c2baef2552f8 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:15:40 -0700 Subject: [PATCH 42/91] vt: keyboard: Allocate new_map with ARRAY_SIZE() In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) The new key map has as many entries as plain_map, but the size was taken from the whole array, which would make the allocation type a pointer to the array rather than the "unsigned short *" being assigned. Allocate ARRAY_SIZE-many entries instead. The resulting allocation size is the same. Build tested ARCH=x86_64 allmodconfig with GCC 16.2.0: drivers/tty/vt/keyboard.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260917211539.i.636-kees@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index c41d850b29c6..ef4a19aeb3ce 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -2002,7 +2002,9 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, return 0; #endif - unsigned short __free(kfree) *new_map = kmalloc(sizeof(plain_map), GFP_KERNEL); + unsigned short __free(kfree) *new_map = + kmalloc_array(ARRAY_SIZE(plain_map), sizeof(*new_map), + GFP_KERNEL); if (!new_map) return -ENOMEM; From e04afb34a1f35b494fc8ca6b3b81832bbb6b794f Mon Sep 17 00:00:00 2001 From: Malathi A Date: Fri, 18 Sep 2026 07:45:46 +0000 Subject: [PATCH 43/91] serial: 8250_uniphier: Use dev_err_probe() in probe error paths devm_clk_get_enabled() returns -EPROBE_DEFER while the clock provider is not registered yet, and uniphier_uart_probe() reports that with dev_err(). Every retry of an intermittently deferred probe therefore prints "failed to get and enable clock" at error level, which is only dmesg noise. Use dev_err_probe() instead: it demotes -EPROBE_DEFER to dev_dbg() and records the reason for /sys/kernel/debug/devices_deferred. Convert the other two error messages in probe as well, so the whole function follows the "return dev_err_probe()" pattern already used by other 8250 drivers such as 8250_em.c. Those two codes are never -EPROBE_DEFER, so there the conversion is a readability change only. Errors other than a deferral are still logged at error level, now with the error code added by the helper. No functional change otherwise. Suggested-by: Andy Shevchenko Signed-off-by: Malathi A Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260918074546.245342-1-malathi.a2000@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_uniphier.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/tty/serial/8250/8250_uniphier.c b/drivers/tty/serial/8250/8250_uniphier.c index 6fffac12277a..7d558b750f83 100644 --- a/drivers/tty/serial/8250/8250_uniphier.c +++ b/drivers/tty/serial/8250/8250_uniphier.c @@ -165,10 +165,8 @@ static int uniphier_uart_probe(struct platform_device *pdev) int ret; regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs) { - dev_err(dev, "failed to get memory resource\n"); - return -EINVAL; - } + if (!regs) + return dev_err_probe(dev, -EINVAL, "failed to get memory resource\n"); membase = devm_ioremap(dev, regs->start, resource_size(regs)); if (!membase) @@ -181,10 +179,9 @@ static int uniphier_uart_probe(struct platform_device *pdev) memset(&up, 0, sizeof(up)); priv->clk = devm_clk_get_enabled(dev, NULL); - if (IS_ERR(priv->clk)) { - dev_err(dev, "failed to get and enable clock\n"); - return PTR_ERR(priv->clk); - } + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "failed to get and enable clock\n"); up.port.uartclk = clk_get_rate(priv->clk); @@ -216,10 +213,9 @@ static int uniphier_uart_probe(struct platform_device *pdev) up.dl_write = uniphier_serial_dl_write; ret = serial8250_register_8250_port(&up); - if (ret < 0) { - dev_err(dev, "failed to register 8250 port\n"); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "failed to register 8250 port\n"); + priv->line = ret; platform_set_drvdata(pdev, priv); From a198cfbc91b0ad6d12aa2efa967bba322c66b18c Mon Sep 17 00:00:00 2001 From: Moteen Shah Date: Fri, 18 Sep 2026 15:33:45 +0530 Subject: [PATCH 44/91] dt-bindings: serial: snps-dw-apb-uart: Add ti,tda54-uart The TDA54 UART is compatible with the existing DesignWare APB UART binding. Add the ti,tda54-uart compatible string. Signed-off-by: Udit Kumar Signed-off-by: Moteen Shah Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260918100346.4044899-2-m-shah@ti.com 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 664305de4986..86dc25919311 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -80,6 +80,7 @@ properties: - starfive,jh7100-hsuart - starfive,jh7100-uart - starfive,jh7110-uart + - ti,tda54-uart - ultrarisc,dp1000-uart - const: snps,dw-apb-uart - const: snps,dw-apb-uart From 3f4e6289053c72a22f058ec60478daa9931fed53 Mon Sep 17 00:00:00 2001 From: Moteen Shah Date: Fri, 18 Sep 2026 15:33:46 +0530 Subject: [PATCH 45/91] serial: 8250_dw: Add ti,tda54-uart quirk to skip empty FIFO read dw8250_handle_irq() does a bogus RX read on RX_TIMEOUT with no data present, to avoid an interrupt storm. On the IP version used in TDA54, that interrupt storm no longer occurs, but reading an empty RX FIFO instead triggers a data abort. Add a DW_UART_QUIRK_SKIP_EMPTY_FIFO_READ quirk for ti,tda54-uart to skip that read and avoid the data abort. Signed-off-by: Udit Kumar Signed-off-by: Moteen Shah Link: https://patch.msgid.link/20260918100346.4044899-3-m-shah@ti.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dw.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 964c268670ea..0ab558230370 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -51,6 +51,7 @@ #define DW_UART_QUIRK_APMC0D08 BIT(4) #define DW_UART_QUIRK_CPR_VALUE BIT(5) #define DW_UART_QUIRK_IER_KICK BIT(6) +#define DW_UART_QUIRK_SKIP_EMPTY_FIFO_READ BIT(7) /* * Number of consecutive IIR_NO_INT interrupts required to trigger interrupt @@ -436,7 +437,7 @@ static int dw8250_handle_irq(struct uart_port *p) * This problem has only been observed so far when not in DMA mode * so we limit the workaround only to non-DMA mode. */ - if (!up->dma && rx_timeout) { + if (!(quirks & DW_UART_QUIRK_SKIP_EMPTY_FIFO_READ) && !up->dma && rx_timeout) { status = serial_lsr_in(up); /* @@ -895,6 +896,11 @@ static const struct dw8250_platform_data dw8250_ultrarisc_dp1000_data = { .quirks = DW_UART_QUIRK_CPR_VALUE, }; +static const struct dw8250_platform_data dw8250_tda54 = { + .usr_reg = DW_UART_USR, + .quirks = DW_UART_QUIRK_SKIP_EMPTY_FIFO_READ, +}; + static const struct of_device_id dw8250_of_match[] = { { .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb }, { .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data }, @@ -902,6 +908,7 @@ static const struct of_device_id dw8250_of_match[] = { { .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data }, { .compatible = "sophgo,sg2044-uart", .data = &dw8250_skip_set_rate_data }, { .compatible = "starfive,jh7100-uart", .data = &dw8250_skip_set_rate_data }, + { .compatible = "ti,tda54-uart", .data = &dw8250_tda54 }, { .compatible = "ultrarisc,dp1000-uart", .data = &dw8250_ultrarisc_dp1000_data }, { /* Sentinel */ } }; From 857f31656f352c4e290122c0a216b9dc74224f3d Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 18 Sep 2026 22:18:41 +0800 Subject: [PATCH 46/91] tty: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit and their non-atomic __set_bit/__clear_bit variants to the assign_bit/__assign_bit API. Done with Coccinelle semantic patch. Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260918141842.3023147-1-peng.fan@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 5 +---- drivers/tty/sysrq.c | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index cc7f7091ed9a..9a37008c4e91 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -138,10 +138,7 @@ static int pty_set_lock(struct tty_struct *tty, int __user *arg) if (get_user(val, arg)) return -EFAULT; - if (val) - set_bit(TTY_PTY_LOCK, &tty->flags); - else - clear_bit(TTY_PTY_LOCK, &tty->flags); + assign_bit(TTY_PTY_LOCK, &tty->flags, val); return 0; } diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index c2e4b31b699a..f4571d05b496 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -911,10 +911,7 @@ static bool sysrq_handle_keypress(struct sysrq_state *sysrq, * keyboard state so we can release keys that have been * pressed before entering SysRq mode. */ - if (value) - set_bit(code, sysrq->key_down); - else - clear_bit(code, sysrq->key_down); + assign_bit(code, sysrq->key_down, value); if (was_active) schedule_work(&sysrq->reinject_work); From bf961847813d9edcb5a9c089714069428d141fa9 Mon Sep 17 00:00:00 2001 From: Zongmin Zhou Date: Thu, 27 Aug 2026 09:45:24 +0800 Subject: [PATCH 47/91] serial: sifive: fix off-by-one in console port bounds check sifive_serial_console_ports[] is sized SIFIVE_SERIAL_MAX_PORTS (8), so valid indices are 0..7. The bounds check in sifive_serial_probe() uses "id > SIFIVE_SERIAL_MAX_PORTS", which lets id == 8 pass and causes an out-of-bounds write in __ssp_add_console_port() through sifive_serial_console_ports[ssp->port.line]. Fix the check to use >= instead, matching the check already used in sifive_serial_console_setup(). Fixes: 45c054d0815b ("tty: serial: add driver for the SiFive UART") Signed-off-by: Zongmin Zhou Link: https://patch.msgid.link/20260827014524.19375-1-min_halo@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sifive.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/sifive.c b/drivers/tty/serial/sifive.c index 110d67613192..5d76f0473626 100644 --- a/drivers/tty/serial/sifive.c +++ b/drivers/tty/serial/sifive.c @@ -999,7 +999,7 @@ static int sifive_serial_probe(struct platform_device *pdev) } #ifdef CONFIG_SERIAL_SIFIVE_CONSOLE - if (id > SIFIVE_SERIAL_MAX_PORTS) { + if (id >= SIFIVE_SERIAL_MAX_PORTS) { dev_err(&pdev->dev, "too many UARTs (%d)\n", id); return -EINVAL; } From d5392f41dcf0b86fd1ed81464160710c89b8c281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 12 Aug 2026 17:25:53 +0200 Subject: [PATCH 48/91] serial: tegra-tcu: Make use of dev_err_probe() in .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usage of dev_err_probe() is (sometimes) a bit more compact than dev_err(), it properly handles ENOMEM and EPROBE_DEFER and emits the error code symbolically. Also introduce a variable to hold &pdev->dev to reduce line length a bit. Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/e5a1565edc2b8c0241827f4b9c40c53fca064415.1786547950.git.u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/tegra-tcu.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/drivers/tty/serial/tegra-tcu.c b/drivers/tty/serial/tegra-tcu.c index 7033dbfe8ba1..077023ce1842 100644 --- a/drivers/tty/serial/tegra-tcu.c +++ b/drivers/tty/serial/tegra-tcu.c @@ -179,9 +179,10 @@ static int tegra_tcu_probe(struct platform_device *pdev) { struct uart_port *port; struct tegra_tcu *tcu; + struct device *dev = &pdev->dev; int err; - tcu = devm_kzalloc(&pdev->dev, sizeof(*tcu), GFP_KERNEL); + tcu = devm_kzalloc(dev, sizeof(*tcu), GFP_KERNEL); if (!tcu) return -ENOMEM; @@ -190,11 +191,9 @@ static int tegra_tcu_probe(struct platform_device *pdev) tcu->rx_client.rx_callback = tegra_tcu_receive; tcu->tx = mbox_request_channel_byname(&tcu->tx_client, "tx"); - if (IS_ERR(tcu->tx)) { - err = PTR_ERR(tcu->tx); - dev_err(&pdev->dev, "failed to get tx mailbox: %d\n", err); - return err; - } + if (IS_ERR(tcu->tx)) + return dev_err_probe(dev, PTR_ERR(tcu->tx), + "failed to get tx mailbox\n"); #if IS_ENABLED(CONFIG_SERIAL_TEGRA_TCU_CONSOLE) /* setup the console */ @@ -218,15 +217,14 @@ static int tegra_tcu_probe(struct platform_device *pdev) err = uart_register_driver(&tcu->driver); if (err) { - dev_err(&pdev->dev, "failed to register UART driver: %d\n", - err); + dev_err_probe(dev, err, "failed to register UART driver\n"); goto free_tx; } /* setup the port */ port = &tcu->port; spin_lock_init(&port->lock); - port->dev = &pdev->dev; + port->dev = dev; port->type = PORT_TEGRA_TCU; port->ops = &tegra_tcu_uart_ops; port->fifosize = 1; @@ -236,7 +234,7 @@ static int tegra_tcu_probe(struct platform_device *pdev) err = uart_add_one_port(&tcu->driver, port); if (err) { - dev_err(&pdev->dev, "failed to add UART port: %d\n", err); + dev_err_probe(dev, err, "failed to add UART port\n"); goto unregister_uart; } @@ -246,8 +244,8 @@ static int tegra_tcu_probe(struct platform_device *pdev) */ tcu->rx = mbox_request_channel_byname(&tcu->rx_client, "rx"); if (IS_ERR(tcu->rx)) { - err = PTR_ERR(tcu->rx); - dev_err(&pdev->dev, "failed to get rx mailbox: %d\n", err); + err = dev_err_probe(dev, PTR_ERR(tcu->rx), + "failed to get rx mailbox\n"); goto remove_uart_port; } From 9e4846fe7a4f69ce85c9f6edd95c5d9f4b05c9da Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Wed, 2 Sep 2026 15:17:09 -0700 Subject: [PATCH 49/91] serial: 8250: Add Armada 38x earlycon support Register the Armada 38x UART compatible with the built-in 8250 earlycon implementation so stdout-path can enable an early console without adding a generic ns16550a compatible to the device tree. Keeping the vendor-specific compatible avoids exposing the UART to the generic OF 8250 runtime driver while preserving the DesignWare driver Armada-specific runtime handling. Assisted-by: Codex:GPT-5 Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260902221709.340393-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_early.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/8250/8250_early.c b/drivers/tty/serial/8250/8250_early.c index 44ec209f37c4..265d49850e15 100644 --- a/drivers/tty/serial/8250/8250_early.c +++ b/drivers/tty/serial/8250/8250_early.c @@ -175,6 +175,7 @@ EARLYCON_DECLARE(uart8250, early_serial8250_setup); EARLYCON_DECLARE(uart, early_serial8250_setup); OF_EARLYCON_DECLARE(ns16550, "ns16550", early_serial8250_setup); OF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup); +OF_EARLYCON_DECLARE(uart, "marvell,armada-38x-uart", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup); From 789f52f83305032ecfaaf7efae9a8ba75815c01b Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Sun, 27 Sep 2026 01:01:17 +0200 Subject: [PATCH 50/91] serial: 8250_mid: Add missing module namespace import for SERIAL_8250 After commit 1c305d5f1daa ("serial: 8250_mid: wait for LSR tx empty before setting termios"), modpost errors (or warning with CONFIG_MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS=y): ERROR: modpost: drivers/tty/serial/8250/8250_mid.ko: module uses symbol 'serial8250_wait_for_xmitr' from namespace 'SERIAL_8250', but does not import it. Add the missing module namespace import to clear up the error/warning. Fixes: 1c305d5f1daa ("serial: 8250_mid: wait for LSR tx empty before setting termios") Signed-off-by: Nathan Chancellor Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260927-tty-8250_mid-ns-import-v1-1-84d0482e1c5d@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c index c02ecb96e8b6..47f820fcd1a4 100644 --- a/drivers/tty/serial/8250/8250_mid.c +++ b/drivers/tty/serial/8250/8250_mid.c @@ -415,3 +415,4 @@ module_pci_driver(mid8250_pci_driver); MODULE_AUTHOR("Intel Corporation"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel MID UART driver"); +MODULE_IMPORT_NS("SERIAL_8250"); From f6ea556620bfd5a5a596d656546634ea1dcd51ec Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 24 Sep 2026 14:24:20 +0200 Subject: [PATCH 51/91] serial: 8250_port: properly handle runtime PM in IRQ We can't and basically don't need to call runtime PM in IRQ handler. If IRQ is ours, the device must be powered on. Otherwise check if the device is powered off and return immediately. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260924122549.668680-3-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index b386c4292861..71a5e7d68daf 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1894,13 +1894,22 @@ EXPORT_SYMBOL_GPL(serial8250_handle_irq); static int serial8250_default_handle_irq(struct uart_port *port) { - struct uart_8250_port *up = up_to_u8250p(port); unsigned int iir; + int pm_status; + int ret; - guard(serial8250_rpm)(up); + /* if driver suspended, return, probably shared interrupt */ + pm_status = pm_runtime_get_if_active(port->dev); + if (!pm_status) + return 0; iir = serial_port_in(port, UART_IIR); - return serial8250_handle_irq(port, iir); + ret = serial8250_handle_irq(port, iir); + + if (pm_status > 0) + pm_runtime_put_autosuspend(port->dev); + + return ret; } /* From df5a38b065c919658c4ca10bf0f25fcc30f21500 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 28 Sep 2026 14:32:35 -0400 Subject: [PATCH 52/91] serial: use dmaengine_get_dma_device() instead of chan->device->dev Replace direct dma_chan::device::dev access with the proper dmaengine_get_dma_device() for consumer API chan->device->dev is not always the device used for DMA mapping. Some DMA engines support per-channel IOMMU mappings, so different channels may use different DMA devices. dmaengine_get_dma_device() returns the correct device for each channel. This also prepares for making the DMA engine provider data structures private. DMA consumers should not access DMA engine internals directly. Assisted-by: LLM Signed-off-by: Frank Li Link: https://patch.msgid.link/20260928183252.1965169-1-Frank.Li@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_dma.c | 29 ++++++++++++++++------------- drivers/tty/serial/8250/8250_omap.c | 4 ++-- drivers/tty/serial/amba-pl011.c | 12 ++++++------ drivers/tty/serial/samsung_tty.c | 27 +++++++++++++++------------ drivers/tty/serial/sh-sci.c | 14 ++++++++------ 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/drivers/tty/serial/8250/8250_dma.c b/drivers/tty/serial/8250/8250_dma.c index 5a83e5269b41..1da605cc8601 100644 --- a/drivers/tty/serial/8250/8250_dma.c +++ b/drivers/tty/serial/8250/8250_dma.c @@ -19,7 +19,7 @@ static void __dma_tx_complete(void *param) unsigned long flags; int ret; - dma_sync_single_for_cpu(dma->txchan->device->dev, dma->tx_addr, + dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); uart_port_lock_irqsave(&p->port, &flags); @@ -136,7 +136,7 @@ int serial8250_tx_dma(struct uart_8250_port *p) dma->tx_cookie = dmaengine_submit(desc); - dma_sync_single_for_device(dma->txchan->device->dev, dma->tx_addr, + dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); dma_async_issue_pending(dma->txchan); @@ -223,6 +223,8 @@ EXPORT_SYMBOL_GPL(serial8250_rx_dma_flush); int serial8250_request_dma(struct uart_8250_port *p) { struct uart_8250_dma *dma = p->dma; + struct device *rx_dev; + struct device *tx_dev; phys_addr_t rx_dma_addr = dma->rx_dma_addr ? dma->rx_dma_addr : p->port.mapbase; phys_addr_t tx_dma_addr = dma->tx_dma_addr ? @@ -282,24 +284,25 @@ int serial8250_request_dma(struct uart_8250_port *p) dmaengine_slave_config(dma->txchan, &dma->txconf); + rx_dev = dmaengine_get_dma_device(dma->rxchan); + tx_dev = dmaengine_get_dma_device(dma->txchan); + /* RX buffer */ if (!dma->rx_size) dma->rx_size = PAGE_SIZE; - dma->rx_buf = dma_alloc_coherent(dma->rxchan->device->dev, dma->rx_size, - &dma->rx_addr, GFP_KERNEL); + dma->rx_buf = dma_alloc_coherent(rx_dev, dma->rx_size, &dma->rx_addr, + GFP_KERNEL); if (!dma->rx_buf) { ret = -ENOMEM; goto err; } /* TX buffer */ - dma->tx_addr = dma_map_single(dma->txchan->device->dev, - p->port.state->port.xmit_buf, - UART_XMIT_SIZE, - DMA_TO_DEVICE); - if (dma_mapping_error(dma->txchan->device->dev, dma->tx_addr)) { - dma_free_coherent(dma->rxchan->device->dev, dma->rx_size, + dma->tx_addr = dma_map_single(tx_dev, p->port.state->port.xmit_buf, + UART_XMIT_SIZE, DMA_TO_DEVICE); + if (dma_mapping_error(tx_dev, dma->tx_addr)) { + dma_free_coherent(rx_dev, dma->rx_size, dma->rx_buf, dma->rx_addr); ret = -ENOMEM; goto err; @@ -326,14 +329,14 @@ void serial8250_release_dma(struct uart_8250_port *p) /* Release RX resources */ dmaengine_terminate_sync(dma->rxchan); dma->rx_running = 0; - dma_free_coherent(dma->rxchan->device->dev, dma->rx_size, dma->rx_buf, - dma->rx_addr); + dma_free_coherent(dmaengine_get_dma_device(dma->rxchan), dma->rx_size, + dma->rx_buf, dma->rx_addr); dma_release_channel(dma->rxchan); dma->rxchan = NULL; /* Release TX resources */ dmaengine_terminate_sync(dma->txchan); - dma_unmap_single(dma->txchan->device->dev, dma->tx_addr, + dma_unmap_single(dmaengine_get_dma_device(dma->txchan), dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); dma_release_channel(dma->txchan); dma->txchan = NULL; diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 3c7775df27ef..9b1ed015c489 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -1083,7 +1083,7 @@ static void omap_8250_dma_tx_complete(void *param) bool en_thri = false; struct omap8250_priv *priv = p->port.private_data; - dma_sync_single_for_cpu(dma->txchan->device->dev, dma->tx_addr, + dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->txchan), dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); guard(uart_port_lock_irqsave)(&p->port); @@ -1200,7 +1200,7 @@ static int omap_8250_tx_dma(struct uart_8250_port *p) dma->tx_cookie = dmaengine_submit(desc); - dma_sync_single_for_device(dma->txchan->device->dev, dma->tx_addr, + dma_sync_single_for_device(dmaengine_get_dma_device(dma->txchan), dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); dma_async_issue_pending(dma->txchan); diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index c4824c201e1c..eb4ba7881502 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -435,8 +435,8 @@ static int pl011_fifo_to_tty(struct uart_amba_port *uap) static int pl011_dmabuf_init(struct dma_chan *chan, struct pl011_dmabuf *db, enum dma_data_direction dir) { - db->buf = dma_alloc_coherent(chan->device->dev, PL011_DMA_BUFFER_SIZE, - &db->dma, GFP_KERNEL); + db->buf = dma_alloc_coherent(dmaengine_get_dma_device(chan), + PL011_DMA_BUFFER_SIZE, &db->dma, GFP_KERNEL); if (!db->buf) return -ENOMEM; db->len = PL011_DMA_BUFFER_SIZE; @@ -448,7 +448,7 @@ static void pl011_dmabuf_free(struct dma_chan *chan, struct pl011_dmabuf *db, enum dma_data_direction dir) { if (db->buf) { - dma_free_coherent(chan->device->dev, + dma_free_coherent(dmaengine_get_dma_device(chan), PL011_DMA_BUFFER_SIZE, db->buf, db->dma); } } @@ -609,7 +609,7 @@ static void pl011_dma_tx_callback(void *data) uart_port_lock_irqsave(&uap->port, &flags); if (uap->dmatx.queued) - dma_unmap_single(dmatx->chan->device->dev, dmatx->dma, + dma_unmap_single(dmaengine_get_dma_device(dmatx->chan), dmatx->dma, dmatx->len, DMA_TO_DEVICE); dmacr = uap->dmacr; @@ -867,7 +867,7 @@ __acquires(&uap->port.lock) dmaengine_terminate_async(uap->dmatx.chan); if (uap->dmatx.queued) { - dma_unmap_single(uap->dmatx.chan->device->dev, uap->dmatx.dma, + dma_unmap_single(dmaengine_get_dma_device(uap->dmatx.chan), uap->dmatx.dma, uap->dmatx.len, DMA_TO_DEVICE); uap->dmatx.queued = false; uap->dmacr &= ~UART011_TXDMAE; @@ -1249,7 +1249,7 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) /* In theory, this should already be done by pl011_dma_flush_buffer */ dmaengine_terminate_sync(uap->dmatx.chan); if (uap->dmatx.queued) { - dma_unmap_single(uap->dmatx.chan->device->dev, + dma_unmap_single(dmaengine_get_dma_device(uap->dmatx.chan), uap->dmatx.dma, uap->dmatx.len, DMA_TO_DEVICE); uap->dmatx.queued = false; diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index 63d0232dffc2..640f85e2d636 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -300,7 +300,7 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port) dmaengine_pause(dma->tx_chan); dmaengine_tx_status(dma->tx_chan, dma->tx_cookie, &state); dmaengine_terminate_all(dma->tx_chan); - dma_sync_single_for_cpu(dma->tx_chan->device->dev, + dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->tx_chan), dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE); async_tx_ack(dma->tx_desc); @@ -333,7 +333,7 @@ static void s3c24xx_serial_tx_dma_complete(void *args) count = dma->tx_bytes_requested - state.residue; async_tx_ack(dma->tx_desc); - dma_sync_single_for_cpu(dma->tx_chan->device->dev, + dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->tx_chan), dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE); @@ -435,7 +435,7 @@ static int s3c24xx_serial_start_tx_dma(struct s3c24xx_uart_port *ourport, dma->tx_size = count & ~(dma_get_cache_alignment() - 1); dma->tx_transfer_addr = dma->tx_addr + tail; - dma_sync_single_for_device(dma->tx_chan->device->dev, + dma_sync_single_for_device(dmaengine_get_dma_device(dma->tx_chan), dma->tx_transfer_addr, dma->tx_size, DMA_TO_DEVICE); @@ -509,7 +509,7 @@ static void s3c24xx_uart_copy_rx_to_tty(struct s3c24xx_uart_port *ourport, if (!count) return; - dma_sync_single_for_cpu(dma->rx_chan->device->dev, dma->rx_addr, + dma_sync_single_for_cpu(dmaengine_get_dma_device(dma->rx_chan), dma->rx_addr, dma->rx_size, DMA_FROM_DEVICE); ourport->port.icount.rx += count; @@ -631,7 +631,7 @@ static void s3c64xx_start_rx_dma(struct s3c24xx_uart_port *ourport) { struct s3c24xx_uart_dma *dma = ourport->dma; - dma_sync_single_for_device(dma->rx_chan->device->dev, dma->rx_addr, + dma_sync_single_for_device(dmaengine_get_dma_device(dma->rx_chan), dma->rx_addr, dma->rx_size, DMA_FROM_DEVICE); dma->rx_desc = dmaengine_prep_slave_single(dma->rx_chan, @@ -1101,20 +1101,23 @@ static int s3c24xx_serial_request_dma(struct s3c24xx_uart_port *p) goto err_release_tx; } - dma->rx_addr = dma_map_single(dma->rx_chan->device->dev, dma->rx_buf, + struct device *rx_dev = dmaengine_get_dma_device(dma->rx_chan); + struct device *tx_dev = dmaengine_get_dma_device(dma->tx_chan); + + dma->rx_addr = dma_map_single(rx_dev, dma->rx_buf, dma->rx_size, DMA_FROM_DEVICE); - if (dma_mapping_error(dma->rx_chan->device->dev, dma->rx_addr)) { + if (dma_mapping_error(rx_dev, dma->rx_addr)) { reason = "DMA mapping error for RX buffer"; ret = -EIO; goto err_free_rx; } /* TX buffer */ - dma->tx_addr = dma_map_single(dma->tx_chan->device->dev, + dma->tx_addr = dma_map_single(tx_dev, p->port.state->port.xmit_buf, UART_XMIT_SIZE, DMA_TO_DEVICE); - if (dma_mapping_error(dma->tx_chan->device->dev, dma->tx_addr)) { + if (dma_mapping_error(tx_dev, dma->tx_addr)) { reason = "DMA mapping error for TX buffer"; ret = -EIO; goto err_unmap_rx; @@ -1123,7 +1126,7 @@ static int s3c24xx_serial_request_dma(struct s3c24xx_uart_port *p) return 0; err_unmap_rx: - dma_unmap_single(dma->rx_chan->device->dev, dma->rx_addr, + dma_unmap_single(dmaengine_get_dma_device(dma->rx_chan), dma->rx_addr, dma->rx_size, DMA_FROM_DEVICE); err_free_rx: kfree(dma->rx_buf); @@ -1143,7 +1146,7 @@ static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p) if (dma->rx_chan) { dmaengine_terminate_all(dma->rx_chan); - dma_unmap_single(dma->rx_chan->device->dev, dma->rx_addr, + dma_unmap_single(dmaengine_get_dma_device(dma->rx_chan), dma->rx_addr, dma->rx_size, DMA_FROM_DEVICE); kfree(dma->rx_buf); dma_release_channel(dma->rx_chan); @@ -1152,7 +1155,7 @@ static void s3c24xx_serial_release_dma(struct s3c24xx_uart_port *p) if (dma->tx_chan) { dmaengine_terminate_all(dma->tx_chan); - dma_unmap_single(dma->tx_chan->device->dev, dma->tx_addr, + dma_unmap_single(dmaengine_get_dma_device(dma->tx_chan), dma->tx_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); dma_release_channel(dma->tx_chan); dma->tx_chan = NULL; diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 50ae9aae6b61..bd58bbfa65aa 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -1495,7 +1495,7 @@ static void sci_dma_rx_release(struct sci_port *s) uart_port_unlock_irqrestore(port, flags); dmaengine_terminate_sync(chan); - dma_free_coherent(chan->device->dev, s->buf_len_rx * 2, s->rx_buf[0], + dma_free_coherent(dmaengine_get_dma_device(chan), s->buf_len_rx * 2, s->rx_buf[0], sg_dma_address(&s->sg_rx[0])); dma_release_channel(chan); } @@ -1591,7 +1591,7 @@ static void sci_dma_tx_release(struct sci_port *s) s->chan_tx_saved = s->chan_tx = NULL; s->cookie_tx = -EINVAL; dmaengine_terminate_sync(chan); - dma_unmap_single(chan->device->dev, s->tx_dma_addr, UART_XMIT_SIZE, + dma_unmap_single(dmaengine_get_dma_device(chan), s->tx_dma_addr, UART_XMIT_SIZE, DMA_TO_DEVICE); dma_release_channel(chan); } @@ -1676,7 +1676,7 @@ static void sci_dma_tx_work_fn(struct work_struct *work) goto switch_to_pio; } - dma_sync_single_for_device(chan->device->dev, buf, s->tx_dma_len, + dma_sync_single_for_device(dmaengine_get_dma_device(chan), buf, s->tx_dma_len, DMA_TO_DEVICE); desc->callback = sci_dma_tx_complete; @@ -1832,12 +1832,13 @@ static void sci_request_dma(struct uart_port *port) chan = sci_request_dma_chan(port, DMA_MEM_TO_DEV); dev_dbg(port->dev, "%s: TX: got channel %p\n", __func__, chan); if (chan) { + struct device *dma_dev = dmaengine_get_dma_device(chan); /* UART circular tx buffer is an aligned page. */ - s->tx_dma_addr = dma_map_single(chan->device->dev, + s->tx_dma_addr = dma_map_single(dma_dev, tport->xmit_buf, UART_XMIT_SIZE, DMA_TO_DEVICE); - if (dma_mapping_error(chan->device->dev, s->tx_dma_addr)) { + if (dma_mapping_error(dma_dev, s->tx_dma_addr)) { dev_warn(port->dev, "Failed mapping Tx DMA descriptor\n"); dma_release_channel(chan); } else { @@ -1853,12 +1854,13 @@ static void sci_request_dma(struct uart_port *port) chan = sci_request_dma_chan(port, DMA_DEV_TO_MEM); dev_dbg(port->dev, "%s: RX: got channel %p\n", __func__, chan); if (chan) { + struct device *dma_dev = dmaengine_get_dma_device(chan); unsigned int i; dma_addr_t dma; void *buf; s->buf_len_rx = 2 * max_t(size_t, 16, port->fifosize); - buf = dma_alloc_coherent(chan->device->dev, s->buf_len_rx * 2, + buf = dma_alloc_coherent(dma_dev, s->buf_len_rx * 2, &dma, GFP_KERNEL); if (!buf) { dev_warn(port->dev, From 90e96a3252cffb3ddd073193dd8e46a9f91b7932 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 28 Sep 2026 14:32:36 -0400 Subject: [PATCH 53/91] serial: amba-pl011: use dmaengine public API instead of raw ops Replace direct calls to the internal DMA device ops (device->device_tx_status() and device->device_issue_pending()) with the proper public DMA engine wrappers: - dmaengine_tx_status() - dma_async_issue_pending() Drivers must not call device ops directly; the wrappers provide locking, debug checks, and a stable interface that hides internal implementation details of the DMA controller driver. Signed-off-by: Frank Li Link: https://patch.msgid.link/20260928183252.1965169-2-Frank.Li@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index eb4ba7881502..fcfa9f00aec6 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -721,7 +721,7 @@ static int pl011_dma_tx_refill(struct uart_amba_port *uap) dmaengine_submit(desc); /* Fire the DMA transaction */ - dma_dev->device_issue_pending(chan); + dma_async_issue_pending(chan); uap->dmacr |= UART011_TXDMAE; pl011_write(uap->dmacr, uap, REG_DMACR); @@ -1011,8 +1011,7 @@ static void pl011_dma_rx_irq(struct uart_amba_port *uap) */ if (dmaengine_pause(rxchan)) dev_err(uap->port.dev, "unable to pause DMA transfer\n"); - dmastat = rxchan->device->device_tx_status(rxchan, - dmarx->cookie, &state); + dmastat = dmaengine_tx_status(rxchan, dmarx->cookie, &state); if (dmastat != DMA_PAUSED) dev_err(uap->port.dev, "unable to pause DMA transfer\n"); @@ -1066,7 +1065,7 @@ static void pl011_dma_rx_callback(void *data) * Rx data can be taken by the UART interrupts during * the DMA irq handler. So we check the residue here. */ - rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state); + dmaengine_tx_status(rxchan, dmarx->cookie, &state); pending = dbuf->len - state.residue; BUG_ON(pending > PL011_DMA_BUFFER_SIZE); /* Then we terminate the transfer - we now know our residue */ @@ -1124,7 +1123,7 @@ static void pl011_dma_rx_poll(struct timer_list *t) struct dma_tx_state state; dbuf = dmarx->use_buf_b ? &uap->dmarx.dbuf_b : &uap->dmarx.dbuf_a; - rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state); + dmaengine_tx_status(rxchan, dmarx->cookie, &state); if (likely(state.residue < dmarx->last_residue)) { dmataken = dbuf->len - dmarx->last_residue; size = dmarx->last_residue - state.residue; From b290393074a149ed2f45da6759c94719c06b48c4 Mon Sep 17 00:00:00 2001 From: Cui GaoSheng Date: Tue, 29 Sep 2026 10:31:55 +0800 Subject: [PATCH 54/91] serial: core: fix NULL/dangling port_dev on failed re-register serial8250_unregister_port() removes a port and then re-registers it against the legacy serial8250 device when that device is still present, ignoring the return value of uart_add_one_port(). When the re-register fails, the error path of serial_core_register_port() removes the port device but leaves port->port_dev dangling (or NULL if the port device allocation itself failed). serial8250 matches ports on ->dev, so such half-registered slots still reach serial_core_unregister_port() on the next unbind: KASAN: null-ptr-deref in range [0x40-0x47] RIP: serial_core_unregister_port+0xe5/0xa00 serial8250_unregister_port+0x103 serial8250_remove+0x61 Fix the error path to clear port->port_dev, and bail out of serial_core_unregister_port() when port_dev is NULL. Read port->port_dev under port_mutex, like all of its writers. Fixes: 84a9582fd203 ("serial: core: Start managing serial controllers to enable runtime PM") Assisted-by: LLM Signed-off-by: Cui GaoSheng Link: https://patch.msgid.link/20260929023155.3259567-1-cuigaosheng1@huawei.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 8ade35eda353..d811a04d5d25 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3335,6 +3335,7 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) err_unregister_port_dev: serial_base_port_device_remove(port->port_dev); + port->port_dev = NULL; err_unregister_ctrl_dev: serial_base_ctrl_device_remove(new_ctrl_dev); @@ -3349,12 +3350,19 @@ int serial_core_register_port(struct uart_driver *drv, struct uart_port *port) void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port) { struct device *phys_dev = port->dev; - struct serial_port_device *port_dev = port->port_dev; - struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev); + struct serial_port_device *port_dev; + struct serial_ctrl_device *ctrl_dev; int ctrl_id = port->ctrl_id; guard(mutex)(&port_mutex); + /* May have never been registered. */ + port_dev = port->port_dev; + if (!port_dev) + return; + + ctrl_dev = serial_core_get_ctrl_dev(port_dev); + port->flags |= UPF_DEAD; serial_core_remove_one_port(drv, port); From c44a4925cdac02af781ebfae96df68a7bf3580b0 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Sat, 26 Sep 2026 11:35:06 -0500 Subject: [PATCH 55/91] serial: 8250: Fix section mismatch with CONFIG_MODULES=n Without CONFIG_MODULES, __init_or_module means __init, so serial8250_pnp_exit() and serial8250_hub6_exit() end up in .init.text. They are also called from serial8250_exit(), which is __exit, and modpost warns: WARNING: modpost: vmlinux: section mismatch in reference: serial8250_exit+0x2a (section: .exit.text) -> serial8250_pnp_exit (section: .init.text) The exit code never runs in such a kernel, so nothing breaks at runtime. But a function that is called from both __init and __exit code can't carry either annotation, so drop it. Fixes: a300273c458c ("serial: 8250: hub6: Add cleanup code") Fixes: 4cf187f9dea9 ("serial: 8250: pnp: Annotate init and exit functions for conditional discarding") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260926163506.487201-1-danishkhateeb03@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_hub6.c | 2 +- drivers/tty/serial/8250/8250_pnp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_hub6.c b/drivers/tty/serial/8250/8250_hub6.c index 4d4c54c951cb..848d9723ac56 100644 --- a/drivers/tty/serial/8250/8250_hub6.c +++ b/drivers/tty/serial/8250/8250_hub6.c @@ -54,7 +54,7 @@ int __init serial8250_hub6_init(void) return platform_device_register(&hub6_device); } -void __init_or_module serial8250_hub6_exit(void) +void serial8250_hub6_exit(void) { platform_device_unregister(&hub6_device); } diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index 7decf62400ab..a6257cd6e246 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -531,7 +531,7 @@ int __init serial8250_pnp_init(void) return pnp_register_driver(&serial_pnp_driver); } -void __init_or_module serial8250_pnp_exit(void) +void serial8250_pnp_exit(void) { pnp_unregister_driver(&serial_pnp_driver); } From e95442c31ae65b57ddffdfa609531c549055f308 Mon Sep 17 00:00:00 2001 From: David Heidelberg Date: Fri, 18 Sep 2026 00:14:11 +0200 Subject: [PATCH 56/91] serial: qcom-geni: power the console back up on system resume The console holds a runtime PM reference on its port, which keeps the port powered while the tty is closed. On system suspend pm_runtime_force_suspend() powers the port down regardless, but a single reference makes pm_runtime_need_not_resume() report the port as not in use, so it is marked runtime suspended instead of being flagged for pm_runtime_force_resume(). On system resume the port therefore stays powered down while uart_resume_port() goes on to program the termios settings and re-enable the console. If the tty has been opened before, uart_resume_port() ends up in startup(), whose runtime PM reference powers the port up again, and only the register writes made before that are lost. If the tty has never been opened (no getty on the port, /dev/console backed by another console), nothing powers the port back up and the console stays dead after the first suspend. Resume the port explicitly before uart_resume_port() when the console was force suspended. Assisted-by: LLM Fixes: aee1f94dab13 ("serial: qcom-geni: remove .pm callback, use runtime PM in startup/shutdown") Signed-off-by: David Heidelberg Link: https://patch.msgid.link/20260918-qcom-geni-pm-v1-1-cb587e215607@ixit.cz Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 4a6f526340b8..bfdc155c128d 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -2117,6 +2117,12 @@ static int qcom_geni_serial_resume(struct device *dev) return ret; } + if (console_suspend_enabled && uart_console(uport)) { + ret = pm_runtime_resume(dev); + if (ret < 0) + return ret; + } + ret = uart_resume_port(private_data->drv, uport); if (uart_console(uport)) { geni_icc_set_tag(&port->se, QCOM_ICC_TAG_ALWAYS); From ca576923c39863ac528427b563ea2f6631703d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Thu, 1 Oct 2026 11:04:32 +0200 Subject: [PATCH 57/91] serial: 8250_bcm2835aux: Add support for RTS/CTS auto flow control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bcm2835aux mini UART can automatically de-assert RTS based on the RX FIFO fill level and gate the transmitter on CTS, but the driver never made use of it. Wire it up: - Enable AUTORTS/AUTOCTS (with the RTS4 threshold and inverted, i.e. conventional active-low, polarity) whenever CRTSCTS is set on ports declaring the "uart-has-rtscts" property, advertised via UPF_HARD_FLOW. - Gate the AUTORTS function on TIOCM_RTS in set_mctrl() so that clearing RTS (port close, TIOCMBIC, B0) actually de-asserts the pin while auto flow control is active. - Implement throttle()/unthrottle() by disabling the RX interrupts and letting the FIFO fill up, making the hardware de-assert RTS - the same approach as in 8250_omap. Re-sync this state on termios and rs485 changes, as those paths may re-enable the RX interrupts (or leave them disabled) behind the throttle's back. - Never enable auto flow control together with rs485, which uses RTS as the transceiver direction control. As the software CTS fallback of the serial core cannot work without a modem status interrupt, strip CRTSCTS whenever it cannot be handled in hardware. Keep it when a CTS GPIO is present, as the serial core's mctrl_gpio handling then delivers the interrupt and the software fallback keeps working. - Restore RXEN when rs485 is disabled, as the emulation may be torn down without the rs485_stop_tx() callback ever being called, which would leave the receiver disabled. Assisted-by: Claude:claude-fable-5 Signed-off-by: Jan Čermák Link: https://patch.msgid.link/20261001090432.1045270-1-sairon@sairon.cz Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_bcm2835aux.c | 167 +++++++++++++++++++++- 1 file changed, 163 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c index 0609582a62f7..626e2e61ef41 100644 --- a/drivers/tty/serial/8250/8250_bcm2835aux.c +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c @@ -7,9 +7,9 @@ * Based on 8250_lpc18xx.c: * Copyright (C) 2015 Joachim Eastwood * - * The bcm2835aux is capable of RTS auto flow-control, but this driver doesn't - * take advantage of it yet. When adding support, be sure not to enable it - * simultaneously to rs485. + * The bcm2835aux's RTS/CTS auto flow-control is enabled for ports declaring + * the "uart-has-rtscts" property, but never simultaneously to rs485, which + * repurposes RTS as the transceiver direction control. */ #include @@ -19,6 +19,7 @@ #include #include #include +#include #include "8250.h" @@ -34,6 +35,18 @@ #define BCM2835_AUX_UART_CNTL_RTSINV 0x40 /* Invert auto RTS polarity */ #define BCM2835_AUX_UART_CNTL_CTSINV 0x80 /* Invert auto CTS polarity */ +/* + * Set auto RTS to de-assert with 4 FIFO slots left (RTS4). + * + * The RTSINV/CTSINV bits select the auto flow assert level, which out of + * reset is active-high. Set them to get the conventional active-low RTS/CTS. + */ +#define BCM2835_AUX_UART_CNTL_AUTORTS_MASK \ + (BCM2835_AUX_UART_CNTL_AUTORTS | BCM2835_AUX_UART_CNTL_RTS4 | \ + BCM2835_AUX_UART_CNTL_RTSINV) +#define BCM2835_AUX_UART_CNTL_AUTOCTS_MASK \ + (BCM2835_AUX_UART_CNTL_AUTOCTS | BCM2835_AUX_UART_CNTL_CTSINV) + /** * struct bcm2835aux_data - driver private data of BCM2835 auxiliary UART * @clk: clock producer of the port's uartclk @@ -46,6 +59,146 @@ struct bcm2835aux_data { u32 cntl; }; +static bool bcm2835aux_tty_throttled(struct uart_port *port) +{ + if (!port->state) + return false; + + scoped_guard(tty_port_tty, &port->state->port) + return tty_throttled(scoped_tty()); + + return false; +} + +/* Program the CNTL auto flow bits from port->status and mctrl */ +static void bcm2835aux_update_flow(struct uart_port *port, unsigned int mctrl) +{ + struct bcm2835aux_data *data = dev_get_drvdata(port->dev); + struct uart_8250_port *up = up_to_u8250p(port); + + data->cntl &= ~(BCM2835_AUX_UART_CNTL_AUTORTS_MASK | + BCM2835_AUX_UART_CNTL_AUTOCTS_MASK); + if ((port->status & UPSTAT_AUTORTS) && (mctrl & TIOCM_RTS)) + data->cntl |= BCM2835_AUX_UART_CNTL_AUTORTS_MASK; + if (port->status & UPSTAT_AUTOCTS) + data->cntl |= BCM2835_AUX_UART_CNTL_AUTOCTS_MASK; + + serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl); +} + +static void bcm2835aux_enable_rx_irq(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + + up->ier |= UART_IER_RLSI | UART_IER_RDI; + serial_out(up, UART_IER, up->ier); +} + +static void bcm2835aux_set_autoflow(struct uart_port *port, tcflag_t cflag, + bool rs485_enabled) +{ + bool was_enabled = port->status & UPSTAT_AUTORTS; + bool enable; + + /* + * rs485 uses RTS as the transceiver direction control, which is + * mutually exclusive with the auto flow-control of the pin. + */ + enable = (cflag & CRTSCTS) && (port->flags & UPF_HARD_FLOW) && + !rs485_enabled; + + if (enable) { + port->status |= UPSTAT_AUTORTS | UPSTAT_AUTOCTS; + /* Restore the throttle state lost in startup/resume/rs485 paths */ + if (bcm2835aux_tty_throttled(port)) { + /* let the FIFO fill up and de-assert RTS in hardware */ + port->ops->stop_rx(port); + /* reclaim an RTS lowered by software flow control */ + if ((cflag & CBAUD) != B0) + port->mctrl |= TIOCM_RTS; + } + } else { + port->status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS); + /* unthrottle() will no longer be called after leaving auto-RTS mode */ + if (was_enabled) + bcm2835aux_enable_rx_irq(port); + } + + bcm2835aux_update_flow(port, port->mctrl); +} + +static void bcm2835aux_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + serial8250_do_set_mctrl(port, mctrl); + bcm2835aux_update_flow(port, mctrl); +} + +static void bcm2835aux_set_termios(struct uart_port *port, + struct ktermios *termios, + const struct ktermios *old) +{ + struct uart_8250_port *up = up_to_u8250p(port); + bool hw_flow = (port->flags & UPF_HARD_FLOW) && + !(port->rs485.flags & SER_RS485_ENABLED); + + /* + * Strip CRTSCTS when the hardware auto flow-control cannot be + * used, as the serial core's software CTS fallback cannot work + * without a modem status interrupt. A CTS GPIO provides one. + */ + if (!hw_flow && !mctrl_gpio_to_gpiod(up->gpios, UART_GPIO_CTS)) + termios->c_cflag &= ~CRTSCTS; + + serial8250_do_set_termios(port, termios, old); + + guard(uart_port_lock_irqsave)(port); + + bcm2835aux_set_autoflow(port, termios->c_cflag, + port->rs485.flags & SER_RS485_ENABLED); +} + +static void bcm2835aux_throttle(struct uart_port *port) +{ + guard(uart_port_lock_irqsave)(port); + + port->ops->stop_rx(port); +} + +static void bcm2835aux_unthrottle(struct uart_port *port) +{ + guard(uart_port_lock_irqsave)(port); + + bcm2835aux_enable_rx_irq(port); +} + +static int bcm2835aux_rs485_config(struct uart_port *port, + struct ktermios *termios, + struct serial_rs485 *rs485) +{ + tcflag_t cflag = termios ? termios->c_cflag : 0; + bool rs485_enabled; + int ret; + + ret = serial8250_em485_config(port, termios, rs485); + if (ret) + return ret; + + rs485_enabled = rs485->flags & SER_RS485_ENABLED; + if (!rs485_enabled) { + struct bcm2835aux_data *data = dev_get_drvdata(port->dev); + + data->cntl |= BCM2835_AUX_UART_CNTL_RXEN; + } + + /* + * termios is NULL when rs485 is set up from firmware properties at + * port registration, before the port has been opened. + */ + bcm2835aux_set_autoflow(port, cflag, rs485_enabled); + + return 0; +} + static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up, bool toggle_ier) { if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) { @@ -99,7 +252,11 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) up.port.dev = &pdev->dev; up.port.type = PORT_16550; up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP; - up.port.rs485_config = serial8250_em485_config; + up.port.set_termios = bcm2835aux_set_termios; + up.port.set_mctrl = bcm2835aux_set_mctrl; + up.port.throttle = bcm2835aux_throttle; + up.port.unthrottle = bcm2835aux_unthrottle; + up.port.rs485_config = bcm2835aux_rs485_config; up.port.rs485_supported = serial8250_em485_supported; up.rs485_start_tx = bcm2835aux_rs485_start_tx; up.rs485_stop_tx = bcm2835aux_rs485_stop_tx; @@ -134,6 +291,8 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) ret = uart_read_port_properties(&up.port); if (ret) goto rm_swnode; + if (device_property_read_bool(&pdev->dev, "uart-has-rtscts")) + up.port.flags |= UPF_HARD_FLOW; up.port.regshift = 2; up.port.fifosize = 8; From 8bb50e95c0abd44607bc60a43032c1dbf7470d5a Mon Sep 17 00:00:00 2001 From: Martin Fuzzey Date: Thu, 1 Oct 2026 10:05:35 +0000 Subject: [PATCH 58/91] serial: sc16is7xx: fix data corruption due to unexpected xon/xoff The hardware supports automatically sending XON/OFF based on the FIFO status and that is already implemented by the driver. However that capability was not announced with the UPF_SOFT_FLOW bit. When the port is opened with the default of XON enabled the driver configures that in the hardware. If userspace does not want XON/OFF (which is normally the case) it will use TCSETS to disactivate it. However the serial core optimises this to only notify the driver if someting imporant has changed, and for XON/XOFF thit is only if the driver declares UPF_SOFT_FLOW. So we end up with unwanted XON/XOFF in the data stream. The problem only occurs if nothing else needed to be set by termios, ie if the user space requested values are the same as the default values, except for XON/XOFF. Since the default speed is 9600 this means that the problem only occurs when requesting 9600, otherwise the call to the driver will be made to change the speed, and that will also clear the XON/XOFF. As 9600 is quite rare these days that probably explains why this hasn't already been seen. It also only occurs on the first port open after boot since the subsequent opens will use the "logical state" set by TCSETS but not communicated to the driver the first time. Signed-off-by: Martin Fuzzey Fixes: dfeae619d781 ("serial: sc16is7xx") Cc: stable@kernel.org Link: https://patch.msgid.link/20261001100534.1225894-1-martin.fuzzey@flowbird.group Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 296079c16fdb..7ddc8a38adab 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1516,7 +1516,7 @@ static int sc16is7xx_setup_channel(struct sc16is7xx_one *one, int i, /* Initialize port data */ port->type = PORT_SC16IS7XX; port->fifosize = SC16IS7XX_FIFO_SIZE; - port->flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; + port->flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY | UPF_SOFT_FLOW; port->iotype = UPIO_BUS; port->rs485_config = sc16is7xx_config_rs485; port->rs485_supported = sc16is7xx_rs485_supported; From 408001136ba84c1dd69dd084f61fcbad590ab5f5 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 2 Oct 2026 16:09:23 -0400 Subject: [PATCH 59/91] tty: serial: pch_uart: use dmaengine_submit() instead of ->tx_submit() Replace direct dma_async_tx_descriptor->tx_submit() calls with the proper dmaengine_submit() API wrapper. Assisted-by: LLM Signed-off-by: Frank Li Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20261002200927.236033-1-Frank.Li@oss.nxp.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/pch_uart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/pch_uart.c b/drivers/tty/serial/pch_uart.c index db5c62b0322f..9505d255f09c 100644 --- a/drivers/tty/serial/pch_uart.c +++ b/drivers/tty/serial/pch_uart.c @@ -811,7 +811,7 @@ static int dma_handle_rx(struct eg20t_port *priv) priv->desc_rx = desc; desc->callback = pch_dma_rx_complete; desc->callback_param = priv; - desc->tx_submit(desc); + dmaengine_submit(desc); dma_async_issue_pending(priv->chan_rx); return PCH_UART_HANDLED_RX_INT; @@ -971,7 +971,7 @@ static unsigned int dma_handle_tx(struct eg20t_port *priv) desc->callback = pch_dma_tx_complete; desc->callback_param = priv; - desc->tx_submit(desc); + dmaengine_submit(desc); dma_async_issue_pending(priv->chan_tx); From 810d467c11f0c85107874d08dfdd11acba6aa0c0 Mon Sep 17 00:00:00 2001 From: Igor Putko Date: Sat, 3 Oct 2026 19:33:37 +0300 Subject: [PATCH 60/91] serial: core: shut down initialized port on removal When a serial port configured as a console is opened and subsequently closed, tty_port_shutdown() skips invoking port->ops->shutdown() because port->console is true. As a result, tty_port_initialized() remains true and the port's interrupt handler stays registered in the irq subsystem. If the underlying device is later unbound or removed (e.g. via sysfs driver unbind or device hot-unplug), serial_core_remove_one_port() unregisters the console, frees uport->name via kfree(), and tears down the port without shutting it down or freeing its IRQ. Consequently, the irqaction descriptor remains registered in genirq with action->name pointing to freed memory. This triggers a use-after-free read whenever genirq accesses action->name, such as when reading /proc/interrupts: BUG: KASAN: slab-use-after-free in string+0x476/0x570 Call Trace: string+0x476/0x570 vsnprintf+0x422/0x1300 seq_printf+0x... show_interrupts+... Fix this by acquiring port->mutex in serial_core_remove_one_port() after console unregistration, clearing port->console under the mutex to prevent data races with concurrent tty_port_shutdown(), and invoking uart_shutdown(NULL, state) if the port remains initialized. This ensures that port->ops->shutdown() is called and the IRQ is freed before kfree(uport->name). Reported-by: syzbot+843bf2f48f4d12e6682e@syzkaller.appspotmail.com Cc: stable Closes: https://syzkaller.appspot.com/bug?extid=843bf2f48f4d12e6682e Fixes: 761ed4a94582 ("tty: serial_core: convert uart_close to use tty_port_close") Fixes: f7048b15900f ("tty: serial_core: Add name field to uart_port struct") Signed-off-by: Igor Putko Link: https://patch.msgid.link/20261003163337.45577-1-igorpetindev@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 319a4b427f3a..05450529871e 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3239,6 +3239,13 @@ static void serial_core_remove_one_port(struct uart_driver *drv, if (uart_console(uport)) unregister_console(uport->cons); + scoped_guard(mutex, &port->mutex) { + port->console = false; + + if (tty_port_initialized(port)) + uart_shutdown(NULL, state); + } + /* * Free the port IO and memory resources, if any. */ From 831e5c42fd922072a4c80d69e369dc5a60f38f25 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 3 Oct 2026 14:31:48 -0700 Subject: [PATCH 61/91] serial: hvc: use modern IRQ acquisition API Use platform_get_irq_optional() to be able to handle potential -EPROBE_DEFER and other errors. Move it earlier to avoid having to call kfree() on the global array. Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20261003213148.137206-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/hvc/hvc_opal.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/tty/hvc/hvc_opal.c b/drivers/tty/hvc/hvc_opal.c index 402da9e7c534..fb9a566ca213 100644 --- a/drivers/tty/hvc/hvc_opal.c +++ b/drivers/tty/hvc/hvc_opal.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -157,8 +156,9 @@ static int hvc_opal_probe(struct platform_device *dev) struct hvc_struct *hp; struct hvc_opal_priv *pv; hv_protocol_t proto; - unsigned int termno, irq, boot = 0; + unsigned int termno, boot = 0; const __be32 *reg; + int irq; if (of_device_is_compatible(dev->dev.of_node, "ibm,opal-console-raw")) { proto = HV_PROTOCOL_RAW; @@ -176,6 +176,10 @@ static int hvc_opal_probe(struct platform_device *dev) reg = of_get_property(dev->dev.of_node, "reg", NULL); termno = reg ? be32_to_cpup(reg) : 0; + irq = platform_get_irq_optional(dev, 0); + if (irq == -EPROBE_DEFER) + return irq; + /* Is it our boot one ? */ if (hvc_opal_privs[termno] == &hvc_opal_boot_priv) { pv = hvc_opal_privs[termno]; @@ -209,8 +213,7 @@ static int hvc_opal_probe(struct platform_device *dev) dev->dev.of_node, boot ? " (boot console)" : ""); - irq = irq_of_parse_and_map(dev->dev.of_node, 0); - if (!irq) { + if (irq < 0) { pr_info("hvc%d: No interrupts property, using OPAL event\n", termno); irq = opal_event_request(ilog2(OPAL_EVENT_CONSOLE_INPUT)); From 1c41d09dcd07ee9648e20aa8238e404c2e6a12fb Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Tue, 6 Oct 2026 15:25:37 -0400 Subject: [PATCH 62/91] serial: sc16is7xx: declare MCR and IER as volatile registers MCR register has the same address as XON1 register. IER register has the same address as DLH register. Which register is accessed depends on bits configuration in some other registers. To prevent potential access errors, declare MCR and IER as volatile registers. Fixes: dfeae619d781 ("serial: sc16is7xx") Cc: stable@kernel.org Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20261006192540.234402-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 7ddc8a38adab..fba588559595 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -501,7 +501,9 @@ static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg) { switch (reg) { case SC16IS7XX_RHR_REG: /* Shared address space with THR & DLL */ + case SC16IS7XX_IER_REG: /* Shared address space with DLH */ case SC16IS7XX_IIR_REG: /* Shared address space with FCR & EFR */ + case SC16IS7XX_MCR_REG: /* Shared address space with XON1 */ 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 */ From 7662a02e53a6143c12508f0cc3b2c7c08c2afa84 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Sat, 10 Oct 2026 18:01:57 +0800 Subject: [PATCH 63/91] tty: serial: move sdio_uart from mmc/core to tty/serial sdio_uart is an SDIO function driver, not part of the MMC/SDIO core. Keeping it in drivers/mmc/core/ is misplaced: it registers to the SDIO bus (sdio_register_driver) and only uses the public SDIO API from include/linux/mmc/, with no dependency on any mmc core internals. Other SDIO function drivers live under their respective functional subsystem, e.g. the SDIO WiFi drivers are under drivers/net/wireless/. sdio_uart should likewise be placed under its functional subsystem, tty, just like the PCIe and I2C/SPI transport drivers (men_z135_uart, sc16is7xx, max310x) that live under drivers/tty/serial/. Historically almost all changes to this driver were tty API changes (e.g. tty_operations adjustments, alloc_tty_driver removal), yet scripts/get_maintainer.pl never listed the TTY maintainers while the file lived under drivers/mmc/core/. Moving it under drivers/tty/serial/ makes get_maintainer.pl report the proper TTY/serial maintainers and reviewers, which benefits future maintenance of this driver. No functional change. The CONFIG_SDIO_UART symbol is intentionally kept unchanged to avoid breaking existing configs; the dependency is updated to 'TTY && MMC' accordingly. Signed-off-by: Shawn Lin Link: https://patch.msgid.link/1791626517-18161-1-git-send-email-shawn.lin@rock-chips.com Signed-off-by: Greg Kroah-Hartman --- drivers/mmc/core/Kconfig | 7 ------- drivers/mmc/core/Makefile | 1 - drivers/tty/serial/Kconfig | 10 ++++++++++ drivers/tty/serial/Makefile | 1 + drivers/{mmc/core => tty/serial}/sdio_uart.c | 0 5 files changed, 11 insertions(+), 8 deletions(-) rename drivers/{mmc/core => tty/serial}/sdio_uart.c (100%) diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index 14d2ecbb04d3..03061b1045a8 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -64,13 +64,6 @@ config MMC_BLOCK_MINORS If unsure, say 8 here. -config SDIO_UART - tristate "SDIO UART/GPS class support" - depends on TTY - help - SDIO function driver for SDIO cards that implements the UART - class, as well as the GPS class which appears like a UART. - config MMC_TEST tristate "MMC host test driver" help diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 15b067e8b0d1..0fa4abada538 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -17,5 +17,4 @@ mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o obj-$(CONFIG_MMC_BLOCK) += mmc_block.o mmc_block-objs := block.o queue.o obj-$(CONFIG_MMC_TEST) += mmc_test.o -obj-$(CONFIG_SDIO_UART) += sdio_uart.o mmc_core-$(CONFIG_MMC_CRYPTO) += crypto.o diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index bf680d22199b..3864cef711a6 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1387,6 +1387,16 @@ config SERIAL_MEN_Z135 This driver can also be build as a module. If so, the module will be called men_z135_uart.ko +config SDIO_UART + tristate "SDIO UART/GPS class support" + depends on TTY && MMC + help + SDIO function driver for SDIO cards that implements the UART + class, as well as the GPS class which appears like a UART. + + This driver can also be built as a module. If so, the module will + be called sdio_uart. + config SERIAL_SPRD tristate "Support for Spreadtrum serial" select SERIAL_CORE diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index bba7b21a4a1d..d63b126f40ca 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -77,6 +77,7 @@ obj-$(CONFIG_SERIAL_SCCNXP) += sccnxp.o obj-$(CONFIG_SERIAL_SC16IS7XX) += sc16is7xx.o obj-$(CONFIG_SERIAL_SC16IS7XX_SPI) += sc16is7xx_spi.o obj-$(CONFIG_SERIAL_SC16IS7XX_I2C) += sc16is7xx_i2c.o +obj-$(CONFIG_SDIO_UART) += sdio_uart.o obj-$(CONFIG_SERIAL_SH_SCI) += sh-sci.o obj-$(CONFIG_SERIAL_SIFIVE) += sifive.o obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o diff --git a/drivers/mmc/core/sdio_uart.c b/drivers/tty/serial/sdio_uart.c similarity index 100% rename from drivers/mmc/core/sdio_uart.c rename to drivers/tty/serial/sdio_uart.c From 3b2de448ec0332c1cb8c38b4c5ff0c8e860befbc Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:24 +0000 Subject: [PATCH 64/91] serial: max310x: don't clobber the TX break bit in set_termios A TX break asserted with TIOCSBRK ends early, with no error reported, as soon as any termios change lands on the port: a break is a state that must hold until TIOCCBRK, but max310x_set_termios() overwrites the whole LCR register, and LCR also carries the TX break bit that break_ctl() drives. No concurrency is required - a baud or parity change from the same process ends the break just as well, and break-led protocols lose their break with nothing in the logs. Update only the LCR bits that are derived from termios and leave the TX break and RTS pin control bits untouched. Since nothing clears a break when a port is closed with the break still asserted - the tty core sends no break-off on release, and the unconditional write here was the accidental recovery - clear TXBREAK in startup(), the same way 8250 does. Fixes: f65444187a66 ("serial: New serial driver MAX310X") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-1-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 9e62778e4191..c7f091bc6e4b 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -156,6 +156,8 @@ #define MAX310X_LCR_FORCEPARITY_BIT (1 << 5) /* 9-bit multidrop parity */ #define MAX310X_LCR_TXBREAK_BIT (1 << 6) /* TX break enable */ #define MAX310X_LCR_RTS_BIT (1 << 7) /* RTS pin control */ +/* LCR bits owned by termios; TX break and RTS are driven elsewhere */ +#define MAX310X_LCR_TERMIOS_MASK GENMASK(5, 0) /* IRDA register bits */ #define MAX310X_IRDA_IRDAEN_BIT (1 << 0) /* IRDA mode enable */ @@ -967,8 +969,12 @@ static void max310x_set_termios(struct uart_port *port, if (termios->c_cflag & CSTOPB) lcr |= MAX310X_LCR_STOPLEN_BIT; /* 2 stops */ - /* Update LCR register */ - max310x_port_write(port, MAX310X_LCR_REG, lcr); + /* + * Update LCR register. Leave the TX break bit alone: it is driven by + * break_ctl(), and a whole-register write here would end a break in + * progress. + */ + max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TERMIOS_MASK, lcr); /* Set read status mask */ port->read_status_mask = MAX310X_LSR_RXOVR_BIT; @@ -1086,6 +1092,13 @@ static int max310x_startup(struct uart_port *port) max310x_power(port, 1); + /* + * Clear a latched break: nothing clears TXBREAK when a port is + * closed with a break still asserted, and set_termios() no longer + * rewrites it. + */ + max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TXBREAK_BIT, 0); + /* Configure MODE1 register */ max310x_port_update(port, MAX310X_MODE1_REG, MAX310X_MODE1_TRNSCVCTRL_BIT, 0); From 4efacb600bf98b13e0659f05afa4549795277e35 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:25 +0000 Subject: [PATCH 65/91] serial: max310x: fix breaks not reaching the wire in RS485 mode The chip's auto-RTS (MODE1.TRNSCVCTRL) asserts the RS485 transceiver only while FIFO data is shifting out, and a break is not FIFO data: on an RS485 port a requested break sets the TX break bit but the transceiver is never enabled, so the break never reaches the wire. Break-led protocols cannot work at all. Disable auto-RTS for the break duration and drive RTS manually via the LCR RTS bit, then restore auto-RTS when the break ends. Track the break in tx_break and leave MODE1 alone in the rs485-config worker while it is set - a TIOCSRS485 arriving mid-break would otherwise re-enable auto-RTS on top of the manual RTS and release the transceiver before the break ends; break_ctl() restores auto-RTS from the then-current configuration when the break completes. The worker runs under port->mutex - break_ctl() and set_termios() already do - so the tx_break test and the MODE1 write cannot straddle a break starting or ending, and startup() clears tx_break alongside the latched TXBREAK bit, since a port can be closed with a break still asserted. Only the break assertion is gated on RS485 being enabled; the restore at break end runs unconditionally and derives MODE1 from the current configuration. Returning early for a port whose RS485 was disabled mid-break - that reconfigure is deferred like any other - would leak the manually driven RTS and leave the transceiver holding the bus indefinitely after the break ends. On a non-RS485 port the restore is a no-op: nothing else writes the LCR RTS bit. Fixes: 55367c620aed ("serial: max310x: Add support for RS-485 mode") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-2-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 49 ++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index c7f091bc6e4b..dddd8ed97881 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -296,6 +296,7 @@ struct max310x_one { struct work_struct md_work; struct work_struct rs_work; struct regmap *regmap; + bool tx_break; /* break_ctl() owns the transceiver */ u8 rx_buf[MAX310X_FIFO_SIZE]; }; @@ -680,6 +681,12 @@ static void max310x_batch_read(struct uart_port *port, u8 *rxbuf, unsigned int l regmap_noinc_read(one->regmap, MAX310X_RHR_REG, rxbuf, len); } +static void max310x_rts_ctl(struct uart_port *port, bool rts_state) +{ + max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_RTS_BIT, + rts_state ? MAX310X_LCR_RTS_BIT : 0); +} + static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen) { struct max310x_one *one = to_max310x_port(port); @@ -927,9 +934,32 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl) static void max310x_break_ctl(struct uart_port *port, int break_state) { + struct max310x_one *one = to_max310x_port(port); + + one->tx_break = break_state; + max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TXBREAK_BIT, break_state ? MAX310X_LCR_TXBREAK_BIT : 0); + + /* + * The chip's auto-RTS asserts the transceiver only while FIFO data is + * shifting out, and a break is not FIFO data. Disable auto-RTS for the + * break duration and drive RTS manually so the break reaches the wire; + * restore auto-RTS when the break ends. + */ + if (break_state) { + if (!(port->rs485.flags & SER_RS485_ENABLED)) + return; + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, 0); + } else { + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, + (port->rs485.flags & SER_RS485_ENABLED) ? + MAX310X_MODE1_TRNSCVCTRL_BIT : 0); + } + max310x_rts_ctl(port, break_state); } static void max310x_set_termios(struct uart_port *port, @@ -1053,6 +1083,13 @@ static void max310x_rs_proc(struct work_struct *ws) struct max310x_one *one = container_of(ws, struct max310x_one, rs_work); unsigned int delay, mode1 = 0, mode2 = 0; + /* + * Serialize against break_ctl() and set_termios(), which run under + * port->mutex: the tx_break test below and the MODE1 write must not + * straddle a break starting or ending. + */ + guard(mutex)(&one->port.state->port.mutex); + delay = (one->port.rs485.delay_rts_before_send << 4) | one->port.rs485.delay_rts_after_send; max310x_port_write(&one->port, MAX310X_HDPIXDELAY_REG, delay); @@ -1064,8 +1101,14 @@ static void max310x_rs_proc(struct work_struct *ws) mode2 = MAX310X_MODE2_ECHOSUPR_BIT; } - max310x_port_update(&one->port, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, mode1); + /* + * A break owns the transceiver: break_ctl() disabled auto-RTS and + * drives RTS manually, and restores it when the break ends. Leave + * MODE1 alone meanwhile or the break goes undriven mid-way. + */ + if (!one->tx_break) + max310x_port_update(&one->port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, mode1); max310x_port_update(&one->port, MAX310X_MODE2_REG, MAX310X_MODE2_ECHOSUPR_BIT, mode2); } @@ -1088,6 +1131,7 @@ static int max310x_rs485_config(struct uart_port *port, struct ktermios *termios static int max310x_startup(struct uart_port *port) { + struct max310x_one *one = to_max310x_port(port); unsigned int val; max310x_power(port, 1); @@ -1097,6 +1141,7 @@ static int max310x_startup(struct uart_port *port) * closed with a break still asserted, and set_termios() no longer * rewrites it. */ + one->tx_break = false; max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TXBREAK_BIT, 0); /* Configure MODE1 register */ From 56051740bf9720335a1a56e87a99e3491c33ce54 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:26 +0000 Subject: [PATCH 66/91] serial: max310x: centralize the RS485 transceiver programming The HDPIXDELAY RTS delays and the MODE1 auto-transceiver enable are programmed in three places - the rs485-config worker, startup() and break_ctl()'s break-off restore - each with its own copy of the register sequence. Move the sequence into a helper, max310x_set_rts_ctl_params(), together with the tx_break guard that keeps the MODE1 write away from a break in progress; the break-off restore reapplies the current configuration through the same helper. No functional change intended. The copies differed only in that startup() clamped the delays to the 4-bit field while the worker wrote them unclamped; the helper clamps, and the difference is unreachable while rs485_config() still rejects delays above 15 ms with -ERANGE. Break-off additionally rewrites HDPIXDELAY with the values it already holds, a no-op on the wire. Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-3-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 85 ++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index dddd8ed97881..bf2dcf63be88 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -932,6 +932,38 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl) schedule_work(&one->md_work); } +/* + * Program the chip's RS485 RTS timing (HDPIXDELAY) and the auto + * transceiver control (MODE1) from the current configuration. The + * rs485-config worker, startup() and break_ctl()'s break-off restore + * each carried their own copy of this sequence. + */ +static void max310x_set_rts_ctl_params(struct max310x_one *one) +{ + struct uart_port *port = &one->port; + unsigned int delay; + u8 mode1 = 0; + + delay = (clamp(port->rs485.delay_rts_before_send, 0U, 15U) << 4) | + clamp(port->rs485.delay_rts_after_send, 0U, 15U); + max310x_port_write(port, MAX310X_HDPIXDELAY_REG, delay); + + if (port->rs485.flags & SER_RS485_ENABLED) + mode1 = MAX310X_MODE1_TRNSCVCTRL_BIT; + + /* + * A break owns the transceiver: break_ctl() disabled auto-RTS and + * drives RTS manually, and restores it from the current + * configuration when the break ends. Touching MODE1 here would + * release the transceiver mid-break. + */ + if (one->tx_break) + return; + + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, mode1); +} + static void max310x_break_ctl(struct uart_port *port, int break_state) { struct max310x_one *one = to_max310x_port(port); @@ -954,10 +986,7 @@ static void max310x_break_ctl(struct uart_port *port, int break_state) max310x_port_update(port, MAX310X_MODE1_REG, MAX310X_MODE1_TRNSCVCTRL_BIT, 0); } else { - max310x_port_update(port, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, - (port->rs485.flags & SER_RS485_ENABLED) ? - MAX310X_MODE1_TRNSCVCTRL_BIT : 0); + max310x_set_rts_ctl_params(one); } max310x_rts_ctl(port, break_state); } @@ -1081,36 +1110,23 @@ static void max310x_set_termios(struct uart_port *port, static void max310x_rs_proc(struct work_struct *ws) { struct max310x_one *one = container_of(ws, struct max310x_one, rs_work); - unsigned int delay, mode1 = 0, mode2 = 0; + unsigned int mode2 = 0; /* * Serialize against break_ctl() and set_termios(), which run under - * port->mutex: the tx_break test below and the MODE1 write must not + * port->mutex: the tx_break-guarded register writes must not * straddle a break starting or ending. */ guard(mutex)(&one->port.state->port.mutex); - delay = (one->port.rs485.delay_rts_before_send << 4) | - one->port.rs485.delay_rts_after_send; - max310x_port_write(&one->port, MAX310X_HDPIXDELAY_REG, delay); + max310x_set_rts_ctl_params(one); - if (one->port.rs485.flags & SER_RS485_ENABLED) { - mode1 = MAX310X_MODE1_TRNSCVCTRL_BIT; + if (one->port.rs485.flags & SER_RS485_ENABLED && + !(one->port.rs485.flags & SER_RS485_RX_DURING_TX)) + mode2 = MAX310X_MODE2_ECHOSUPR_BIT; - if (!(one->port.rs485.flags & SER_RS485_RX_DURING_TX)) - mode2 = MAX310X_MODE2_ECHOSUPR_BIT; - } - - /* - * A break owns the transceiver: break_ctl() disabled auto-RTS and - * drives RTS manually, and restores it when the break ends. Leave - * MODE1 alone meanwhile or the break goes undriven mid-way. - */ - if (!one->tx_break) - max310x_port_update(&one->port, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, mode1); max310x_port_update(&one->port, MAX310X_MODE2_REG, - MAX310X_MODE2_ECHOSUPR_BIT, mode2); + MAX310X_MODE2_ECHOSUPR_BIT, mode2); } static int max310x_rs485_config(struct uart_port *port, struct ktermios *termios, @@ -1154,21 +1170,14 @@ static int max310x_startup(struct uart_port *port) max310x_port_update(port, MAX310X_MODE2_REG, MAX310X_MODE2_FIFORST_BIT, 0); - /* Configure mode1/mode2 to have rs485/rs232 enabled at startup */ - val = (clamp(port->rs485.delay_rts_before_send, 0U, 15U) << 4) | - clamp(port->rs485.delay_rts_after_send, 0U, 15U); - max310x_port_write(port, MAX310X_HDPIXDELAY_REG, val); + /* Configure the RS485 RTS timing and the RS485/RS232 mode bits. */ + max310x_set_rts_ctl_params(one); - if (port->rs485.flags & SER_RS485_ENABLED) { - max310x_port_update(port, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, - MAX310X_MODE1_TRNSCVCTRL_BIT); - - if (!(port->rs485.flags & SER_RS485_RX_DURING_TX)) - max310x_port_update(port, MAX310X_MODE2_REG, - MAX310X_MODE2_ECHOSUPR_BIT, - MAX310X_MODE2_ECHOSUPR_BIT); - } + if (port->rs485.flags & SER_RS485_ENABLED && + !(port->rs485.flags & SER_RS485_RX_DURING_TX)) + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_ECHOSUPR_BIT, + MAX310X_MODE2_ECHOSUPR_BIT); /* * Configure flow control levels: From 7784d6aee59a142505072b3fbea97932b2fd1fa0 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:27 +0000 Subject: [PATCH 67/91] serial: max310x: fix RS485 delays being programmed as bit-times The HDPIXDELAY register counts the RTS setup and hold delays in bit-times, four bits per direction, but the driver has been writing the struct serial_rs485 delay_rts_before_send/delay_rts_after_send values into it unconverted - and the uapi expresses those in milliseconds. A requested 9 ms setup delay is programmed as 9 bit-times, which at 9600 baud is 0.94 ms, roughly a tenth of what userspace asked for; the error grows with the baud rate. Cache the baud rate in set_termios() and convert the delays to bit-times at the current rate, rounding up so the delay on the wire is never shorter than requested, and capping at the 15 bit-times the 4-bit field can hold. The conversion lives in max310x_set_rts_ctl_params(); set_termios() now calls it as well, since the conversion depends on the baud rate. The delays a 4-bit bit-time field can represent still fall well short of the milliseconds the uapi can express; requests beyond 15 bit-times are capped, and the -ERANGE rejection of values above 15 ms remains in place for now. Fixes: 55367c620aed ("serial: max310x: Add support for RS-485 mode") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-4-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 40 ++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index bf2dcf63be88..c57d6dde7fe3 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -163,6 +163,10 @@ #define MAX310X_IRDA_IRDAEN_BIT (1 << 0) /* IRDA mode enable */ #define MAX310X_IRDA_SIR_BIT (1 << 1) /* SIR mode enable */ +/* HDPIXDELAY accessor macros */ +#define MAX310X_HDPIXDELAY_SETUP(val) (((val) & 0x0f) << 4) +#define MAX310X_HDPIXDELAY_HOLD(val) ((val) & 0x0f) + /* Flow control trigger level register masks */ #define MAX310X_FLOWLVL_HALT_MASK GENMASK(3, 0) /* Flow control halt level */ #define MAX310X_FLOWLVL_RES_MASK GENMASK(7, 4) /* Flow control resume level */ @@ -296,6 +300,7 @@ struct max310x_one { struct work_struct md_work; struct work_struct rs_work; struct regmap *regmap; + unsigned int baud; bool tx_break; /* break_ctl() owns the transceiver */ u8 rx_buf[MAX310X_FIFO_SIZE]; @@ -933,23 +938,33 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl) } /* - * Program the chip's RS485 RTS timing (HDPIXDELAY) and the auto - * transceiver control (MODE1) from the current configuration. The - * rs485-config worker, startup() and break_ctl()'s break-off restore - * each carried their own copy of this sequence. + * Program the chip's RS485 RTS timing. The HDPIXDELAY setup and hold fields + * count bit-times, four bits per direction, while the uapi expresses the + * delays in milliseconds: convert at the current baud rate, rounding up, and + * cap at the field maximum. */ static void max310x_set_rts_ctl_params(struct max310x_one *one) { + const unsigned int max_bit_dly = 15; struct uart_port *port = &one->port; - unsigned int delay; + unsigned int setup = 0, hold = 0; u8 mode1 = 0; - delay = (clamp(port->rs485.delay_rts_before_send, 0U, 15U) << 4) | - clamp(port->rs485.delay_rts_after_send, 0U, 15U); - max310x_port_write(port, MAX310X_HDPIXDELAY_REG, delay); + if (port->rs485.flags & SER_RS485_ENABLED) { + /* Convert milliseconds to bit-times, rounding up. */ + setup = DIV_ROUND_UP(one->baud * port->rs485.delay_rts_before_send, + MSEC_PER_SEC); + hold = DIV_ROUND_UP(one->baud * port->rs485.delay_rts_after_send, + MSEC_PER_SEC); + setup = min(setup, max_bit_dly); + hold = min(hold, max_bit_dly); - if (port->rs485.flags & SER_RS485_ENABLED) mode1 = MAX310X_MODE1_TRNSCVCTRL_BIT; + } + + max310x_port_write(port, MAX310X_HDPIXDELAY_REG, + MAX310X_HDPIXDELAY_SETUP(setup) | + MAX310X_HDPIXDELAY_HOLD(hold)); /* * A break owns the transceiver: break_ctl() disabled auto-RTS and @@ -1105,6 +1120,13 @@ static void max310x_set_termios(struct uart_port *port, /* Update timeout according to new baud rate */ uart_update_timeout(port, termios->c_cflag, baud); + + /* + * Cache the new baud rate and reprogram the RS485 RTS delays, whose + * millisecond-to-bit-time conversion depends on it. + */ + to_max310x_port(port)->baud = baud; + max310x_set_rts_ctl_params(to_max310x_port(port)); } static void max310x_rs_proc(struct work_struct *ws) From c3ad42ccd303113a08bd0b441fb5b3380f29c5bf Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:28 +0000 Subject: [PATCH 68/91] serial: max310x: don't power down the port mid-transmission on close max310x_shutdown() powers the port down regardless of what the transmitter is doing. The power-off stops the UART clock mid-character, truncating the final frame, and on the auto-RTS path it freezes the RTS output at its current level: a close() with data still queued leaves the RS485 transceiver asserted until the next open, which also emits the interrupted character corrupted. Waiting for the data to drain is no better: the tty layer's wait-until-sent is bounded by closing_wait, configurable to none, and a hangup arrives with no wait at all, so draining a full FIFO blocks close() in uninterruptible sleep for seconds at low baud rates - and forever when CTS flow control blocks the FIFO. Instead, set MODE1 TxDisabl: the character in flight completes and the transmitter ceases with TX_ at idle. Give that character one character time (the chip has no transmitter-idle status), then reset the FIFOs so the auto-RTS engine sees the transmitter empty and releases RTS within the configured after-send hold; wait that hold plus one bit time before powering off. Abandoned data was explicitly not waited for, and startup() resets the FIFOs and TxDisabl on open anyway. Measured on a MAX14830: a truncating close() at 50 baud takes 0.23 s, RTS releases one character plus the hold after the last stop bit on both polarities, and the reopen corruption is gone. Fixes: f65444187a66 ("serial: New serial driver MAX310X") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-5-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 44 ++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index c57d6dde7fe3..86400092a901 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -300,6 +300,7 @@ struct max310x_one { struct work_struct md_work; struct work_struct rs_work; struct regmap *regmap; + unsigned int char_time_us; unsigned int baud; bool tx_break; /* break_ctl() owns the transceiver */ @@ -1010,6 +1011,7 @@ static void max310x_set_termios(struct uart_port *port, struct ktermios *termios, const struct ktermios *old) { + unsigned int frame_bits = tty_get_frame_size(termios->c_cflag); unsigned int lcr = 0, flow = 0; int baud; @@ -1122,10 +1124,13 @@ static void max310x_set_termios(struct uart_port *port, uart_update_timeout(port, termios->c_cflag, baud); /* - * Cache the new baud rate and reprogram the RS485 RTS delays, whose - * millisecond-to-bit-time conversion depends on it. + * Cache the new baud rate and the time it takes to clock out one + * character before reprogramming the RS485 RTS delays: the + * millisecond-to-bit-time conversion divides by the baud rate. */ to_max310x_port(port)->baud = baud; + to_max310x_port(port)->char_time_us = + DIV_ROUND_UP(USEC_PER_SEC * frame_bits, baud); max310x_set_rts_ctl_params(to_max310x_port(port)); } @@ -1182,9 +1187,10 @@ static int max310x_startup(struct uart_port *port) one->tx_break = false; max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TXBREAK_BIT, 0); - /* Configure MODE1 register */ + /* Configure MODE1: re-enable the transmitter shutdown() stopped */ max310x_port_update(port, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, 0); + MAX310X_MODE1_TRNSCVCTRL_BIT | + MAX310X_MODE1_TXDIS_BIT, 0); /* Configure MODE2 register & Reset FIFOs*/ val = MAX310X_MODE2_RXEMPTINV_BIT | MAX310X_MODE2_FIFORST_BIT; @@ -1221,9 +1227,39 @@ static int max310x_startup(struct uart_port *port) static void max310x_shutdown(struct uart_port *port) { + struct max310x_one *one = to_max310x_port(port); + /* Disable all interrupts */ max310x_port_write(port, MAX310X_IRQEN_REG, 0); + /* + * Stop the transmitter: the character in flight completes, data + * still queued is abandoned (the next startup() resets the FIFOs). + */ + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TXDIS_BIT, MAX310X_MODE1_TXDIS_BIT); + + /* + * Let the character in flight finish (the chip has no + * transmitter-idle status), then empty the FIFO: auto-RTS releases + * only once the transmitter is empty, and the power-off below + * would freeze an asserted pin until the next open. + */ + fsleep(one->char_time_us); + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_FIFORST_BIT, + MAX310X_MODE2_FIFORST_BIT); + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_FIFORST_BIT, 0); + if (port->rs485.flags & SER_RS485_ENABLED && one->baud) { + unsigned int hold = DIV_ROUND_UP(one->baud * + port->rs485.delay_rts_after_send, + MSEC_PER_SEC); + + fsleep(max(DIV_ROUND_UP((hold + 1) * USEC_PER_SEC, + one->baud), 100U)); + } + max310x_power(port, 0); } From 7b07356e820e9543cb5b64d009c345303aad0c65 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:29 +0000 Subject: [PATCH 69/91] serial: max310x: support active-low RTS on the hardware path The chip's auto-RTS engine asserts the RTS_ pin high while data is shifting out, so a transceiver with an active-low driver-enable could not use the hardware RS485 path at all: SER_RS485_RTS_AFTER_SEND is not in the supported flags and the core normalizes it away with "invalid RTS setting, using RTS_ON_SEND instead". The output stage is invertible: program IRDA.RTSINVERT when the requested polarity is active-low and advertise SER_RS485_RTS_AFTER_SEND in rs485_supported. A break already drives break_state onto the RTS bit unadjusted, which remains correct because RTSINVERT inverts the output stage itself, not the register value. Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-6-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 86400092a901..6c49fc09371c 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -162,6 +162,7 @@ /* IRDA register bits */ #define MAX310X_IRDA_IRDAEN_BIT (1 << 0) /* IRDA mode enable */ #define MAX310X_IRDA_SIR_BIT (1 << 1) /* SIR mode enable */ +#define MAX310X_IRDA_RTSINVERT_BIT (1 << 2) /* Invert RTS output */ /* HDPIXDELAY accessor macros */ #define MAX310X_HDPIXDELAY_SETUP(val) (((val) & 0x0f) << 4) @@ -949,7 +950,7 @@ static void max310x_set_rts_ctl_params(struct max310x_one *one) const unsigned int max_bit_dly = 15; struct uart_port *port = &one->port; unsigned int setup = 0, hold = 0; - u8 mode1 = 0; + u8 mode1 = 0, irda = 0; if (port->rs485.flags & SER_RS485_ENABLED) { /* Convert milliseconds to bit-times, rounding up. */ @@ -961,6 +962,12 @@ static void max310x_set_rts_ctl_params(struct max310x_one *one) hold = min(hold, max_bit_dly); mode1 = MAX310X_MODE1_TRNSCVCTRL_BIT; + /* + * The auto-RTS engine asserts RTS high on send; for an + * active-low RTS let IRDA.RTSINVERT invert the output stage. + */ + if (!(port->rs485.flags & SER_RS485_RTS_ON_SEND)) + irda = MAX310X_IRDA_RTSINVERT_BIT; } max310x_port_write(port, MAX310X_HDPIXDELAY_REG, @@ -978,6 +985,8 @@ static void max310x_set_rts_ctl_params(struct max310x_one *one) max310x_port_update(port, MAX310X_MODE1_REG, MAX310X_MODE1_TRNSCVCTRL_BIT, mode1); + max310x_port_update(port, MAX310X_IRDA_REG, + MAX310X_IRDA_RTSINVERT_BIT, irda); } static void max310x_break_ctl(struct uart_port *port, int break_state) @@ -994,7 +1003,9 @@ static void max310x_break_ctl(struct uart_port *port, int break_state) * The chip's auto-RTS asserts the transceiver only while FIFO data is * shifting out, and a break is not FIFO data. Disable auto-RTS for the * break duration and drive RTS manually so the break reaches the wire; - * restore auto-RTS when the break ends. + * restore auto-RTS when the break ends. For an active-low RTS, + * IRDA.RTSINVERT already inverts the RTS_ output stage, so break_state + * is driven as it is. */ if (break_state) { if (!(port->rs485.flags & SER_RS485_ENABLED)) @@ -1425,7 +1436,8 @@ static int max310x_gpio_set_config(struct gpio_chip *chip, unsigned int offset, #endif static const struct serial_rs485 max310x_rs485_supported = { - .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX, + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | + SER_RS485_RTS_AFTER_SEND | SER_RS485_RX_DURING_TX, .delay_rts_before_send = 1, .delay_rts_after_send = 1, }; From c06fcc258db0c0c7f607e64a7ce6a157bb2414b1 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:30 +0000 Subject: [PATCH 70/91] serial: max310x: schedule tx_work directly from the IRQ handler The TXEMPTY interrupt asks for a FIFO refill, and start_tx() does nothing beyond scheduling tx_work, so going through it makes no functional difference. It does conflate two distinct events, though: start_tx() is the serial core starting a new transmission, while TXEMPTY can only fire for a transmission that is already running - the bit latches on the FIFO's non-empty to empty transition, and the IRQ handler's read of IRQSTS consumes the latch, so a stale TXEMPTY cannot exist on an idle port (if the bit were level-triggered, the handler's read-until-clear loop would never terminate). Schedule tx_work directly, keeping the interrupt path a pure FIFO refill. This is preparation for a following patch that adds software-timed RS485 RTS control, where start_tx() also starts the RTS envelope and a refill must not restart it. No functional change. Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-7-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 6c49fc09371c..7fce911bea6b 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -856,8 +856,12 @@ static irqreturn_t max310x_port_irq(struct max310x_port *s, int portno) } if (rxlen) max310x_handle_rx(port, rxlen); + /* + * TXEMPTY latches on the FIFO becoming empty, so a stale + * interrupt cannot pump data during an RTS setup delay. + */ if (ists & MAX310X_IRQ_TXEMPTY_BIT) - max310x_start_tx(port); + schedule_work(&s->p[portno].tx_work); } while (1); return res; From 33632215e1bbc6109173b761e0c9bc5c4fcdbeee Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:31 +0000 Subject: [PATCH 71/91] serial: max310x: drive RTS in software when hardware delays are too short The chip's auto-RTS path can express at most 15 bit-times of RTS setup and hold delay - a fraction of a millisecond at typical baud rates - while the uapi expresses the delays in milliseconds up to the UART core's RS485_MAX_RTS_DELAY. Requests beyond the field were rejected with -ERANGE, which makes the core silently wipe port->rs485 and disable RS485; a device tree asking for a 20 ms setup delay ends up with no RS485 at all and an unusable bus. Add a software-driven RTS path that takes over whenever the hardware cannot represent the requested timing, and clamp the delays to RS485_MAX_RTS_DELAY instead of rejecting them. max310x_set_rts_ctl_params() picks the path: software if either delay exceeds what 15 bit-times encode at the current baud rate, or if the requested RTS polarity combination cannot be produced by the auto-RTS engine; hardware otherwise, as before. On the software path the RTS envelope is driven by a single hrtimer, re-used for the before- and after-send phases (tracked in tx_state), plus a single rts_work that toggles RTS. start_tx() begins the envelope; rts_work asserts RTS and only then arms the before-send timer, so data is never shifted before RTS is on the wire. The timer expiry kicks tx_work; once the chip FIFO drains, the same timer is re-armed for one character (the byte still in the shift register) plus the after-send delay, after which rts_work releases RTS. One timer and one work keep the phases mutually exclusive and the RTS toggles ordered. A start_tx() landing while the envelope is already in its send phase only pumps the new data: rewinding it to the before-send phase would insert a spurious setup delay mid-stream. Teardown is interlocked: shutdown() and an RS485-disabling TIOCSRS485 set tx_teardown under port->lock before cancelling the timer and works, and start_tx() checks it on entry and again after the hrtimer_try_to_cancel(-1) path retakes the dropped lock - otherwise a write racing the teardown could re-arm the timer or queue rts_work against a port being shut down, leaving the transceiver driving the bus after close. The rs485-disable path additionally kicks tx_work afterwards, since a racing write may have queued data with no envelope left to pump it, and shutdown() now also cancels tx_work, which was previously cancelled only in remove(). The interlock also fences the adoption path below, so a reconfigure racing a teardown cannot re-enable the cancelled timer, and an RS485 disable flushes a queued rts_work before the pin is settled: one already past its tx_state read would re-assert RTS after the settle. set_rts_ctl_params() publishes sw_rts_during_tx with a single store and settles the RTS idle level only while tx_state is off, because serial_core calls set_termios() without port->lock and rs485_config() schedules a reconfigure on every TIOCSRS485 - either could otherwise release the transceiver mid-envelope. The settle also re-checks tx_state after its write and requeues rts_work if an envelope started meanwhile: the state read and the register write are not atomic, and rts_work re-derives the level from tx_state, so this converges without locking. shutdown() gates first - the interlock is set and the interrupts are masked before any wait, so nothing can start a new envelope or requeue tx_work behind the cancels - then honours a running after-send hold, bounded by the after-send delay plus two character times (the transmitter is already stopped by this point), and cancels the timer and works unconditionally: a TIOCSRS485 can clear sw_rts_during_tx while an envelope is still in flight, and neither may outlive the port. A hold that did not complete within the bound is settled by the final release below regardless. break_ctl() on the software path applies the configured RTS polarity itself. A reconfigure can also move the port off the hardware path while the chip's auto-RTS still owns a transmission in flight - a termios change or TIOCSRS485 pushing a delay above what the hardware can time at the new rate. Such transfers are invisible to tx_state, so the idle settle would release RTS and the MODE1 write would disable auto-RTS mid-transfer, shifting the rest of the data out with the transceiver released and no error reported anywhere. Adopt the transfer instead when the chip TX FIFO or the xmit buffer is not empty: enter MAX310X_TX_SEND and drive RTS manually before auto-RTS is disabled, so the pin is never released across the handover, and the normal drain path arms the after-send hold - or the adoption arms it directly if the FIFO drained while this raced the TX-empty worker. A transfer whose last character has already left the FIFO for the transmit shift register is still invisible and keeps the old behavior: at most one character of early release. The reverse move strands state instead: a reconfigure onto the hardware path mid-envelope left tx_state set, because nothing reset it in that direction and the TX-empty hold arming only ran while the software path was selected. A later reconfigure back off the hardware path then trusted the stale tx_state, skipped both the adoption and the idle settle, and disabled auto-RTS with the RTS register released - the rest of the transfer in flight shifted out with the transceiver off, again with no error reported (hit twice in 79 in-flight reconfigures under a multi-process stress test). The TX-empty unwind now runs regardless of the selected path - it is a no-op outside an envelope - and the reconfigure asserts RTS for any transfer it believes exists, stale or live, before auto-RTS is disabled. The hold is armed only once no transmittable data remains, so that assert cannot cut a burst short when it lands in a FIFO refill window; output that is stopped still releases through the hold. rts_work also derives the level from tx_break: a break can begin while the previous envelope's after-send hold is still armed - TIOCSBRK waits for the output to drain, which lands the break exactly there - and the expiring hold would otherwise release the transceiver mid-break. With break ownership in the derivation the hold expiry becomes an idempotent re-assert, and break-off restores the idle state as it already does. shutdown() gives its final RTS release one bit time, and at least 100 us, to reach the pin before powering the port down. The power-off stops the UART channel clock, and the RTS output stage is clocked by it: a release written immediately before the clock stops reaches the register but never moves the pin, which stays asserted until the next startup restarts the clock - a close racing the after-send hold expiry left the transceiver driving the bus, with the register reading released and the pin high. The measured propagation is about one tick of the 16x oversampling clock - below 50 us at 1200 baud - so one bit time keeps a 16x margin and scales with the clock the output stage runs on; the floor covers rates where a bit is shorter than the measured bound. Handing the pin to the chip's auto-RTS engine instead was tried and rejected on the wire: the pin level is RTSINVERT xor the selected source, so for an active-low RTS every single-register step of that handover drives the pin to the asserted level, and the same clock stop can freeze it there. Fixes: 55367c620aed ("serial: max310x: Add support for RS-485 mode") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-8-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 488 +++++++++++++++++++++++++++++++---- 1 file changed, 436 insertions(+), 52 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 7fce911bea6b..520e2f11f98c 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -295,15 +296,29 @@ struct max310x_devtype { u8 power_bit; /* Bit for sleep or power-off mode (active high). */ }; +/* Software-timed RS485 RTS envelope phase */ +enum max310x_tx_state { + MAX310X_TX_OFF, /* idle, RTS released */ + MAX310X_TX_WAIT_BEFORE_SEND, /* RTS asserted, before-send delay */ + MAX310X_TX_SEND, /* data in flight, awaiting TX-empty */ + MAX310X_TX_WAIT_AFTER_SEND, /* data drained, after-send hold */ +}; + struct max310x_one { struct uart_port port; struct work_struct tx_work; struct work_struct md_work; struct work_struct rs_work; + struct work_struct rts_work; + struct hrtimer tx_delay_tmr; struct regmap *regmap; unsigned int char_time_us; unsigned int baud; + bool sw_rts_during_tx; + bool cancel_tx_delay_tmr; + bool tx_teardown; /* envelope being torn down */ bool tx_break; /* break_ctl() owns the transceiver */ + enum max310x_tx_state tx_state; u8 rx_buf[MAX310X_FIFO_SIZE]; }; @@ -694,6 +709,38 @@ static void max310x_rts_ctl(struct uart_port *port, bool rts_state) rts_state ? MAX310X_LCR_RTS_BIT : 0); } +/* RTS level for the transmitting or the idle phase of an RS485 envelope */ +static bool max310x_rts_level(struct uart_port *port, bool active) +{ + return active ? (port->rs485.flags & SER_RS485_RTS_ON_SEND) : + (port->rs485.flags & SER_RS485_RTS_AFTER_SEND); +} + +/* + * Drive the RS485 RTS line to match the current tx_state and break + * ownership. This is the only place that touches RTS, and it reads the + * state rather than a fixed assert/deassert intent, so a newer assert is + * never clobbered by a stale release and an expiring after-send hold never + * releases a break in progress. It also arms the before-send timer once the + * RTS edge is on the wire, so data is never shifted before RTS is asserted. + */ +static void max310x_rts_work_proc(struct work_struct *ws) +{ + struct max310x_one *one = container_of(ws, struct max310x_one, rts_work); + struct uart_port *port = &one->port; + bool rts_on = READ_ONCE(one->tx_state) != MAX310X_TX_OFF || + READ_ONCE(one->tx_break); + + max310x_rts_ctl(port, max310x_rts_level(port, rts_on)); + + guard(spinlock_irqsave)(&port->lock); + if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_BEFORE_SEND && + !one->cancel_tx_delay_tmr && !hrtimer_active(&one->tx_delay_tmr)) + hrtimer_start(&one->tx_delay_tmr, + ms_to_ktime(port->rs485.delay_rts_before_send), + HRTIMER_MODE_REL); +} + static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen) { struct max310x_one *one = to_max310x_port(port); @@ -790,6 +837,77 @@ static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen) tty_flip_buffer_push(&port->state->port); } +static enum hrtimer_restart max310x_tmr_tx(struct hrtimer *timer) +{ + struct max310x_one *one = container_of(timer, struct max310x_one, + tx_delay_tmr); + + guard(spinlock_irqsave)(&one->port.lock); + if (!one->cancel_tx_delay_tmr) { + if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_AFTER_SEND) { + /* After-send hold elapsed: drop RTS via the rts worker. */ + WRITE_ONCE(one->tx_state, MAX310X_TX_OFF); + schedule_work(&one->rts_work); + } else { + WRITE_ONCE(one->tx_state, MAX310X_TX_SEND); + schedule_work(&one->tx_work); + } + } + + return HRTIMER_NORESTART; +} + +static void max310x_delayed_stop_tx(struct uart_port *port) +{ + struct max310x_one *one = to_max310x_port(port); + unsigned int txlvl; + + if (READ_ONCE(one->tx_state) == MAX310X_TX_OFF) + return; + + /* + * Data still queued for transmission defers the hold: a refill is + * coming and the envelope is not over. Stopped output does not + * count - it drains nowhere, and the envelope must end. + */ + if (!kfifo_is_empty(&port->state->port.xmit_fifo) && + !uart_tx_stopped(port)) + return; + + /* + * The kfifo can be empty while the chip TX FIFO is still draining, so arm + * the after-send hold only once the chip FIFO is empty too - the TX-empty + * interrupt re-invokes us then. Otherwise the hold starts early and RTS + * drops mid-character, clipping the last byte(s). + */ + txlvl = max310x_port_read(port, MAX310X_TXFIFOLVL_REG); + if (txlvl) + return; + + /* + * Runs from tx_work without port->lock, so re-check the state under it: + * shutdown() may have cancelled the envelope meanwhile. Only + * MAX310X_TX_SEND may arm the hold. + */ + guard(spinlock_irqsave)(&one->port.lock); + if (one->cancel_tx_delay_tmr || + READ_ONCE(one->tx_state) != MAX310X_TX_SEND) + return; + + if (!hrtimer_active(&one->tx_delay_tmr)) { + /* + * Add one character for the byte still in the shift register - + * TX-empty fires as it enters, not as it leaves. + */ + ktime_t delay = us_to_ktime(one->char_time_us + + port->rs485.delay_rts_after_send * + USEC_PER_MSEC); + + WRITE_ONCE(one->tx_state, MAX310X_TX_WAIT_AFTER_SEND); + hrtimer_start(&one->tx_delay_tmr, delay, HRTIMER_MODE_REL); + } +} + static void max310x_handle_tx(struct uart_port *port) { struct tty_port *tport = &port->state->port; @@ -801,8 +919,16 @@ static void max310x_handle_tx(struct uart_port *port) return; } - if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) + if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { + /* + * Unwind the envelope even if a reconfigure has moved the + * port to the hardware path mid-envelope: a stranded + * tx_state would make a later reconfigure believe a + * software envelope still owns RTS. + */ + max310x_delayed_stop_tx(port); return; + } /* * It's a circ buffer -- wrap around. @@ -827,11 +953,64 @@ static void max310x_handle_tx(struct uart_port *port) uart_write_wakeup(port); } +/* + * Begin a software-timed RTS envelope: set the before-send phase and queue the + * rts worker to assert RTS. tx_state is set synchronously here (start_tx() holds + * port.lock) so close()/shutdown can see an envelope is in flight; rts_work then + * asserts RTS and arms the before-send timer (see there). + */ +static void max310x_delayed_start_tx(struct uart_port *port) +{ + struct max310x_one *one = to_max310x_port(port); + + WRITE_ONCE(one->tx_state, MAX310X_TX_WAIT_BEFORE_SEND); + one->cancel_tx_delay_tmr = false; + schedule_work(&one->rts_work); +} + +/* called with port.lock taken and irqs off */ static void max310x_start_tx(struct uart_port *port) { struct max310x_one *one = to_max310x_port(port); - schedule_work(&one->tx_work); + /* A teardown is in progress; nothing may start an envelope or TX. */ + if (one->tx_teardown) + return; + + if (READ_ONCE(one->sw_rts_during_tx)) { + /* + * The before- and after-send phases share one delay timer. If an + * after-send release is pending, cancel it before starting a new + * TX so the just-asserted RTS is not yanked; re-arming the timer + * for the before-send phase then supersedes the release. + */ + int res = 0; + + if (READ_ONCE(one->tx_state) == MAX310X_TX_WAIT_AFTER_SEND) + res = hrtimer_try_to_cancel(&one->tx_delay_tmr); + if (unlikely(res == -1)) { + one->cancel_tx_delay_tmr = true; + uart_port_unlock(port); + hrtimer_cancel(&one->tx_delay_tmr); + uart_port_lock(port); + /* + * The lock was dropped: a teardown may have run to + * completion meanwhile. Re-check before starting. + */ + if (one->tx_teardown) + return; + } + + /* Already sending: pump the new data, don't rewind. */ + if (READ_ONCE(one->tx_state) == MAX310X_TX_SEND) { + schedule_work(&one->tx_work); + return; + } + + max310x_delayed_start_tx(port); + } else { + schedule_work(&one->tx_work); + } } static irqreturn_t max310x_port_irq(struct max310x_port *s, int portno) @@ -944,10 +1123,39 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl) } /* - * Program the chip's RS485 RTS timing. The HDPIXDELAY setup and hold fields - * count bit-times, four bits per direction, while the uapi expresses the - * delays in milliseconds: convert at the current baud rate, rounding up, and - * cap at the field maximum. + * A reconfigure can move the port off the hardware RTS path while the chip's + * auto-RTS still owns a transmission in flight; tx_state does not track + * those. Take such a transfer over before auto-RTS is disabled: enter + * MAX310X_TX_SEND, so the caller keeps RTS driven across the handover and + * the normal drain path arms the after-send hold. Returns true when a + * transmission owns RTS - adopted here or started concurrently - and false + * when the line is really idle. + */ +static bool max310x_adopt_hw_tx(struct max310x_one *one) +{ + struct uart_port *port = &one->port; + + if (!max310x_port_read(port, MAX310X_TXFIFOLVL_REG) && + kfifo_is_empty(&port->state->port.xmit_fifo)) + return false; + + scoped_guard(spinlock_irqsave, &port->lock) { + if (one->tx_teardown) + return false; + if (READ_ONCE(one->tx_state) != MAX310X_TX_OFF) + return true; + WRITE_ONCE(one->tx_state, MAX310X_TX_SEND); + one->cancel_tx_delay_tmr = false; + } + + return true; +} + +/* + * Pick hardware or software RTS timing for the current port. The chip can + * deliver up to 15 bit-times of setup/hold delay via HDPIXDELAY; anything + * longer (or any RTS polarity the chip cannot produce automatically) must + * be driven by software via tx_delay_tmr and rts_work. */ static void max310x_set_rts_ctl_params(struct max310x_one *one) { @@ -955,25 +1163,35 @@ static void max310x_set_rts_ctl_params(struct max310x_one *one) struct uart_port *port = &one->port; unsigned int setup = 0, hold = 0; u8 mode1 = 0, irda = 0; + bool sw_rts = false; - if (port->rs485.flags & SER_RS485_ENABLED) { - /* Convert milliseconds to bit-times, rounding up. */ - setup = DIV_ROUND_UP(one->baud * port->rs485.delay_rts_before_send, - MSEC_PER_SEC); - hold = DIV_ROUND_UP(one->baud * port->rs485.delay_rts_after_send, - MSEC_PER_SEC); - setup = min(setup, max_bit_dly); - hold = min(hold, max_bit_dly); + if (!(port->rs485.flags & SER_RS485_ENABLED)) + goto out; - mode1 = MAX310X_MODE1_TRNSCVCTRL_BIT; - /* - * The auto-RTS engine asserts RTS high on send; for an - * active-low RTS let IRDA.RTSINVERT invert the output stage. - */ - if (!(port->rs485.flags & SER_RS485_RTS_ON_SEND)) - irda = MAX310X_IRDA_RTSINVERT_BIT; + /* Convert milliseconds to bit-times, rounding up. */ + setup = DIV_ROUND_UP(one->baud * port->rs485.delay_rts_before_send, + MSEC_PER_SEC); + hold = DIV_ROUND_UP(one->baud * port->rs485.delay_rts_after_send, + MSEC_PER_SEC); + + /* + * Compare in bit-times: a delay of exactly max_bit_dly bit-times + * must stay on the hardware path, which a nanosecond ceiling + * computed from the truncated per-bit time would reject. Without + * a baud rate the conversion is meaningless - use software. + */ + if (!one->baud || setup > max_bit_dly || hold > max_bit_dly || + !!(port->rs485.flags & SER_RS485_RTS_ON_SEND) == + !!(port->rs485.flags & SER_RS485_RTS_AFTER_SEND)) { + sw_rts = true; + setup = 0; + hold = 0; } +out: + /* Assign once; a transient false would be seen by other readers. */ + WRITE_ONCE(one->sw_rts_during_tx, sw_rts); + max310x_port_write(port, MAX310X_HDPIXDELAY_REG, MAX310X_HDPIXDELAY_SETUP(setup) | MAX310X_HDPIXDELAY_HOLD(hold)); @@ -981,12 +1199,66 @@ static void max310x_set_rts_ctl_params(struct max310x_one *one) /* * A break owns the transceiver: break_ctl() disabled auto-RTS and * drives RTS manually, and restores it from the current - * configuration when the break ends. Touching MODE1 here would + * configuration when the break ends. Touching them here would * release the transceiver mid-break. */ if (one->tx_break) return; + if (port->rs485.flags & SER_RS485_ENABLED) { + if (sw_rts) { + /* + * Only settle RTS at idle when no transmission owns it. + * A reconfigure while one is in flight - rs_work runs + * on every TIOCSRS485 - would otherwise release the + * transceiver mid-character. A transfer the hardware + * path owns is invisible to tx_state - adopt it + * instead of settling. The reverse strand also + * exists: an envelope the hardware path inherited + * mid-envelope leaves tx_state set with nothing + * driving the pin. + */ + if (READ_ONCE(one->tx_state) != MAX310X_TX_OFF || + max310x_adopt_hw_tx(one)) { + /* + * The transfer may be running under auto-RTS: + * assert before auto-RTS is disabled below so + * the pin is never released under it, and arm + * the after-send hold - with the chip FIFO + * already drained no TX-empty interrupt + * arrives to arm it. + */ + max310x_rts_ctl(port, + max310x_rts_level(port, true)); + max310x_delayed_stop_tx(port); + } else { + max310x_rts_ctl(port, + max310x_rts_level(port, false)); + /* + * serial_core calls set_termios() without + * port->lock, so an envelope may have started + * while the idle level was written and the + * settle can land after its RTS assert. + * rts_work re-derives the level from + * tx_state; requeue it to converge. + */ + if (READ_ONCE(one->tx_state) != MAX310X_TX_OFF) + schedule_work(&one->rts_work); + } + } else { + mode1 = MAX310X_MODE1_TRNSCVCTRL_BIT; + /* + * The auto-RTS engine asserts RTS high on send; for an + * active-low RTS let IRDA.RTSINVERT invert the output + * stage. + */ + if (!(port->rs485.flags & SER_RS485_RTS_ON_SEND)) + irda = MAX310X_IRDA_RTSINVERT_BIT; + } + } else { + max310x_rts_ctl(port, 0); + } + max310x_port_update(port, MAX310X_MODE1_REG, MAX310X_MODE1_TRNSCVCTRL_BIT, mode1); max310x_port_update(port, MAX310X_IRDA_REG, @@ -1014,12 +1286,27 @@ static void max310x_break_ctl(struct uart_port *port, int break_state) if (break_state) { if (!(port->rs485.flags & SER_RS485_ENABLED)) return; - max310x_port_update(port, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, 0); + if (READ_ONCE(one->sw_rts_during_tx)) { + max310x_rts_ctl(port, max310x_rts_level(port, 1)); + } else { + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, 0); + max310x_rts_ctl(port, 1); + } } else { + /* + * Reapply the current configuration: a reconfigure that + * arrived during the break was deferred by the tx_break + * guard, and RS485 may have been disabled outright - the + * manually driven RTS must not leak past the break. On the + * software path the helper also settles the idle level; on + * the hardware path release the manual RTS - auto-RTS owns + * the pin again. + */ max310x_set_rts_ctl_params(one); + if (!READ_ONCE(one->sw_rts_during_tx)) + max310x_rts_ctl(port, 0); } - max310x_rts_ctl(port, break_state); } static void max310x_set_termios(struct uart_port *port, @@ -1061,9 +1348,10 @@ static void max310x_set_termios(struct uart_port *port, lcr |= MAX310X_LCR_STOPLEN_BIT; /* 2 stops */ /* - * Update LCR register. Leave the TX break bit alone: it is driven by - * break_ctl(), and a whole-register write here would end a break in - * progress. + * Update LCR register. Leave the TX break and RTS bits alone: they are + * driven by break_ctl() and by the software-timed RS485 RTS, and a + * whole-register write here would end a break in progress or release + * the transceiver mid-character. */ max310x_port_update(port, MAX310X_LCR_REG, MAX310X_LCR_TERMIOS_MASK, lcr); @@ -1141,7 +1429,9 @@ static void max310x_set_termios(struct uart_port *port, /* * Cache the new baud rate and the time it takes to clock out one * character before reprogramming the RS485 RTS delays: the - * millisecond-to-bit-time conversion divides by the baud rate. + * millisecond-to-bit-time conversion divides by the baud rate, and + * taking over an in-flight transfer arms the after-send hold from + * the character time. */ to_max310x_port(port)->baud = baud; to_max310x_port(port)->char_time_us = @@ -1161,6 +1451,10 @@ static void max310x_rs_proc(struct work_struct *ws) */ guard(mutex)(&one->port.state->port.mutex); + /* Flush an rts_work that read tx_state before the disable cleared it. */ + if (!(one->port.rs485.flags & SER_RS485_ENABLED)) + cancel_work_sync(&one->rts_work); + max310x_set_rts_ctl_params(one); if (one->port.rs485.flags & SER_RS485_ENABLED && @@ -1171,14 +1465,37 @@ static void max310x_rs_proc(struct work_struct *ws) MAX310X_MODE2_ECHOSUPR_BIT, mode2); } +/* called with port.lock taken and irqs off */ static int max310x_rs485_config(struct uart_port *port, struct ktermios *termios, struct serial_rs485 *rs485) { struct max310x_one *one = to_max310x_port(port); - if ((rs485->delay_rts_before_send > 0x0f) || - (rs485->delay_rts_after_send > 0x0f)) - return -ERANGE; + rs485->delay_rts_before_send = min(rs485->delay_rts_before_send, 100U); + rs485->delay_rts_after_send = min(rs485->delay_rts_after_send, 100U); + + /* + * Make sure no SW-timed RTS toggle survives an RS485 disable, even + * if the delay timer happens to be running right now. + */ + if (!(rs485->flags & SER_RS485_ENABLED)) { + one->tx_teardown = true; + one->cancel_tx_delay_tmr = true; + if (hrtimer_try_to_cancel(&one->tx_delay_tmr) == -1) { + uart_port_unlock(port); + hrtimer_cancel(&one->tx_delay_tmr); + uart_port_lock(port); + } + WRITE_ONCE(one->tx_state, MAX310X_TX_OFF); + one->tx_teardown = false; + /* + * The port stays alive, and a write that raced the teardown + * may have left data queued with no envelope left to pump it. + * Kick tx_work; RS485 is disabled, so the plain path is right. + */ + if (!kfifo_is_empty(&port->state->port.xmit_fifo)) + schedule_work(&one->tx_work); + } port->rs485 = *rs485; @@ -1213,7 +1530,15 @@ static int max310x_startup(struct uart_port *port) max310x_port_update(port, MAX310X_MODE2_REG, MAX310X_MODE2_FIFORST_BIT, 0); - /* Configure the RS485 RTS timing and the RS485/RS232 mode bits. */ + one->tx_teardown = false; + + /* + * Configure the RS485 RTS timing (HW auto-RTS vs software-driven) and + * the RS485/RS232 mode bits. Don't hardcode HW auto-RTS here - let + * max310x_set_rts_ctl_params() pick HW or SW per the configured + * delays, otherwise the chip's auto-RTS would override the software + * RTS hold and the after-send delay is lost. + */ max310x_set_rts_ctl_params(one); if (port->rs485.flags & SER_RS485_ENABLED && @@ -1244,7 +1569,15 @@ static void max310x_shutdown(struct uart_port *port) { struct max310x_one *one = to_max310x_port(port); - /* Disable all interrupts */ + /* + * Gate new transmissions and interrupts first: a concurrent + * start_tx() either sees the interlock or happens-before it, and + * nothing below may start a new envelope. The timer and the works + * stay live for now - they are what completes a running after-send + * hold. + */ + scoped_guard(spinlock_irqsave, &port->lock) + one->tx_teardown = true; max310x_port_write(port, MAX310X_IRQEN_REG, 0); /* @@ -1254,25 +1587,69 @@ static void max310x_shutdown(struct uart_port *port) max310x_port_update(port, MAX310X_MODE1_REG, MAX310X_MODE1_TXDIS_BIT, MAX310X_MODE1_TXDIS_BIT); - /* - * Let the character in flight finish (the chip has no - * transmitter-idle status), then empty the FIFO: auto-RTS releases - * only once the transmitter is empty, and the power-off below - * would freeze an asserted pin until the next open. - */ - fsleep(one->char_time_us); - max310x_port_update(port, MAX310X_MODE2_REG, - MAX310X_MODE2_FIFORST_BIT, - MAX310X_MODE2_FIFORST_BIT); - max310x_port_update(port, MAX310X_MODE2_REG, - MAX310X_MODE2_FIFORST_BIT, 0); - if (port->rs485.flags & SER_RS485_ENABLED && one->baud) { - unsigned int hold = DIV_ROUND_UP(one->baud * - port->rs485.delay_rts_after_send, - MSEC_PER_SEC); + if (READ_ONCE(one->sw_rts_during_tx)) { + /* + * Honour a running after-send hold before the port is + * powered down. The bound covers the final character - + * tx_empty() cannot see the transmit shift register - plus + * the hold itself. Data beyond that is abandoned: it is + * only still queued when the tty layer was told not to + * wait, and the release below drives RTS to idle + * regardless. + */ + unsigned int tries = port->rs485.delay_rts_after_send + + 2 * DIV_ROUND_UP(one->char_time_us, USEC_PER_MSEC); - fsleep(max(DIV_ROUND_UP((hold + 1) * USEC_PER_SEC, - one->baud), 100U)); + while (READ_ONCE(one->tx_state) != MAX310X_TX_OFF && tries-- > 0) + fsleep(USEC_PER_MSEC); + } else { + /* + * Let the character in flight finish (the chip has no + * transmitter-idle status), then empty the FIFO: auto-RTS + * releases only once the transmitter is empty, and the + * power-off below would freeze an asserted pin until the + * next open. The hold is at most 15 bit-times. + */ + fsleep(one->char_time_us); + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_FIFORST_BIT, + MAX310X_MODE2_FIFORST_BIT); + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_FIFORST_BIT, 0); + if (port->rs485.flags & SER_RS485_ENABLED && one->baud) { + unsigned int hold = DIV_ROUND_UP(one->baud * + port->rs485.delay_rts_after_send, + MSEC_PER_SEC); + + fsleep(max(DIV_ROUND_UP((hold + 1) * USEC_PER_SEC, + one->baud), 100U)); + } + } + + /* + * The wind-down is over: cancel unconditionally. The SW/HW decision + * is recomputed on every reconfigure, so a TIOCSRS485 can clear + * sw_rts_during_tx while an envelope is still in flight, and + * neither the timer nor the works may outlive the port. + */ + scoped_guard(spinlock_irqsave, &port->lock) + one->cancel_tx_delay_tmr = true; + cancel_work_sync(&one->tx_work); + hrtimer_cancel(&one->tx_delay_tmr); + cancel_work_sync(&one->rts_work); + WRITE_ONCE(one->tx_state, MAX310X_TX_OFF); + + if (READ_ONCE(one->sw_rts_during_tx)) { + max310x_rts_ctl(port, max310x_rts_level(port, false)); + /* + * The power-off below stops the UART clock that moves the + * RTS pin: released too late, the write reaches the register + * but the pin stays at its old level until the next startup + * restarts the clock. Give the release one bit time (at + * least 100 us) to reach the pin - the measured propagation + * is about one tick of the 16x oversampling clock. + */ + fsleep(max(one->char_time_us / 10, 100U)); } max310x_power(port, 0); @@ -1568,6 +1945,11 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty INIT_WORK(&s->p[i].md_work, max310x_md_proc); /* Initialize queue for changing RS485 mode */ INIT_WORK(&s->p[i].rs_work, max310x_rs_proc); + /* Initialize queue for software-driven RTS toggling */ + INIT_WORK(&s->p[i].rts_work, max310x_rts_work_proc); + hrtimer_setup(&s->p[i].tx_delay_tmr, max310x_tmr_tx, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); + s->p[i].tx_state = MAX310X_TX_OFF; } #ifdef CONFIG_GPIOLIB @@ -1679,6 +2061,8 @@ static void max310x_remove(struct device *dev) int i; for (i = 0; i < s->devtype->nr; i++) { + hrtimer_cancel(&s->p[i].tx_delay_tmr); + cancel_work_sync(&s->p[i].rts_work); cancel_work_sync(&s->p[i].tx_work); cancel_work_sync(&s->p[i].md_work); cancel_work_sync(&s->p[i].rs_work); From ae588038e31c9dbd6836cb12bbdd82af0271c373 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Thu, 8 Oct 2026 09:48:32 +0000 Subject: [PATCH 72/91] serial: max310x: don't transmit while an RS485 reconfigure is pending TIOCSRS485 applies its register changes asynchronously: rs485_config() stores the new configuration and schedules rs_work, which programs HDPIXDELAY, MODE1.TRNSCVCTRL and the RTS path. A write() issued right after the ioctl therefore transmits against the old, half-switched state. On a single core the ordering is even deterministic: start_tx() picks the stale path first, then rs_work reprograms the chip, then tx_work pumps the data - with the transceiver already released. A TIOCSRS485 switching from the hardware to the software RTS path followed immediately by a write puts the whole transfer on the wire with the transceiver disabled: nothing reaches the bus and no error is reported anywhere. The inverse direction is as old as the asynchronous reconfigure itself: enabling RS485 and writing immediately shifts the first bytes out before rs_work has enabled the chip's auto-RTS. Defer instead: rs485_config() marks the reconfigure pending under port->lock, start_tx() leaves the data in the kfifo while the mark is set, and rs_work restarts the transmission itself once the new configuration is fully applied. The rs485-disable path's direct tx_work kick is replaced by the same mechanism, which also orders that flush after the reconfigure instead of before it. The restart kick also checks port->x_char: uart_send_xchar() reaches start_tx() too, and a lone x_char deferred by the pending gate leaves the kfifo empty, so the kick would otherwise skip it and nothing else would ever send it - an idle chip FIFO raises no TXEMPTY interrupt. And start_tx() re-checks the pending mark after the hrtimer_try_to_cancel(-1) path retakes the dropped lock, mirroring the tx_teardown re-check there: a TIOCSRS485 posted inside that window would otherwise let the transmission proceed on the stale path. rs_work takes port->lock for that restart through uart_port_lock_irqsave(): start_tx() may drop and retake the lock through the uart_port API on its hrtimer_try_to_cancel(-1) path, and a raw spinlock guard around the call would unbalance the nbcon console handling underneath. The restart also rewinds a send state adopted for the deferred write itself: set_rts_ctl_params() cannot tell data the pending gate deferred apart from an in-flight transfer's refill by the xmit buffer alone, and a deferred write pumped in the adopted send phase would skip the configured before-send delay. With the chip FIFO empty the adopted state can only be the deferred write: rewind it and let the restart run the full envelope. The chip-FIFO check for that rewind is deliberately lock-free, so a TX-empty refill can land between the read and the lock and the rewind can hit a live envelope. The consequence is bounded: the rewind runs under port->lock in a state the RTS worker reads as asserted, so the envelope pauses one before-send delay with the transceiver held - the same pause the unconditional rewind produced before patch 8's guard. Fixes: 5bdb48b501e8 ("serial: max310x: Fix RS485 handling") Assisted-by: Claude:claude-fable-5 Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20261008-max310x-rs485-sw-delay-v8-9-6ba881f81e1f@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 51 +++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 520e2f11f98c..a0ed70185555 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -317,6 +317,7 @@ struct max310x_one { bool sw_rts_during_tx; bool cancel_tx_delay_tmr; bool tx_teardown; /* envelope being torn down */ + bool rs485_pending; /* rs_work not yet applied */ bool tx_break; /* break_ctl() owns the transceiver */ enum max310x_tx_state tx_state; @@ -977,6 +978,16 @@ static void max310x_start_tx(struct uart_port *port) if (one->tx_teardown) return; + /* + * An RS485 reconfigure is scheduled but not applied yet: transmitting + * now would use the old path against half-programmed registers - a + * TIOCSRS485 switching paths followed immediately by a write puts the + * data on the wire with the transceiver released. Leave the data in + * the kfifo; rs_work restarts TX once the configuration is applied. + */ + if (one->rs485_pending) + return; + if (READ_ONCE(one->sw_rts_during_tx)) { /* * The before- and after-send phases share one delay timer. If an @@ -995,9 +1006,10 @@ static void max310x_start_tx(struct uart_port *port) uart_port_lock(port); /* * The lock was dropped: a teardown may have run to - * completion meanwhile. Re-check before starting. + * completion or a reconfigure may have been posted + * meanwhile. Re-check before starting. */ - if (one->tx_teardown) + if (one->tx_teardown || one->rs485_pending) return; } @@ -1443,6 +1455,8 @@ static void max310x_rs_proc(struct work_struct *ws) { struct max310x_one *one = container_of(ws, struct max310x_one, rs_work); unsigned int mode2 = 0; + unsigned long flags; + bool chip_tx_empty; /* * Serialize against break_ctl() and set_termios(), which run under @@ -1463,6 +1477,31 @@ static void max310x_rs_proc(struct work_struct *ws) max310x_port_update(&one->port, MAX310X_MODE2_REG, MAX310X_MODE2_ECHOSUPR_BIT, mode2); + + /* + * The configuration is applied: release any TX that start_tx() + * deferred while the reconfigure was pending, now on the right + * path. start_tx() can drop and retake the lock through the + * uart_port API - a raw spinlock guard here would unbalance it. + */ + chip_tx_empty = !max310x_port_read(&one->port, + MAX310X_TXFIFOLVL_REG); + + uart_port_lock_irqsave(&one->port, &flags); + one->rs485_pending = false; + if (one->port.x_char || + !kfifo_is_empty(&one->port.state->port.xmit_fifo)) { + /* + * A send state adopted without chip data is the deferred + * write itself: restart it as a fresh envelope so the + * before-send delay is honoured. + */ + if (chip_tx_empty && + READ_ONCE(one->tx_state) == MAX310X_TX_SEND) + WRITE_ONCE(one->tx_state, MAX310X_TX_OFF); + max310x_start_tx(&one->port); + } + uart_port_unlock_irqrestore(&one->port, flags); } /* called with port.lock taken and irqs off */ @@ -1488,17 +1527,11 @@ static int max310x_rs485_config(struct uart_port *port, struct ktermios *termios } WRITE_ONCE(one->tx_state, MAX310X_TX_OFF); one->tx_teardown = false; - /* - * The port stays alive, and a write that raced the teardown - * may have left data queued with no envelope left to pump it. - * Kick tx_work; RS485 is disabled, so the plain path is right. - */ - if (!kfifo_is_empty(&port->state->port.xmit_fifo)) - schedule_work(&one->tx_work); } port->rs485 = *rs485; + one->rs485_pending = true; schedule_work(&one->rs_work); return 0; From e1f46a09df93650288babb23de6e34bff73e80e6 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Fri, 9 Oct 2026 23:46:16 -0400 Subject: [PATCH 73/91] serial: core: fix TIOCSSERIAL and TIOCSERCONFIG on uninitialised ports Commit 8ca59f7baee7 ("serial: fix ioctl hangup race") made uart_set_info() and uart_do_autoconfig() return -EIO when TTY_IO_ERROR is set, so that they do not run on a port that has been hung up. However, TTY_IO_ERROR is also set on a port that could not be started, for example because its type is unknown, and by uart_shutdown(), which both functions call. As of now, setserial cannot configure a port that the kernel did not detect, and an autoconfig that follows a port, irq or type change in the same setserial call fails on any port. The issue can be reproduced with setserial (busybox) on a port without a detected UART, for example ttyS3 of a QEMU guest: setserial /dev/ttyS3 uart 16550A setserial -g /dev/ttyS3 Before commit 8ca59f7baee7 ("serial: fix ioctl hangup race"), the result is: /dev/ttyS3, UART: 16550A, Port: 0x02e8, IRQ: 3 After that commit, the result is: setserial: can't set serial info: Input/output error /dev/ttyS3, UART: unknown, Port: 0x02e8, IRQ: 3 This patch will check tty_port_active() in these two functions instead. uart_hangup() clears it under the port mutex, and it stays set on an open port that could not be started. Fixes: 8ca59f7baee7 ("serial: fix ioctl hangup race") Cc: stable@kernel.org Signed-off-by: Hengyu Liang Tested-by: Johan Hovold Reviewed-by: Johan Hovold Link: https://patch.msgid.link/20261010034616.3915864-1-hengyul@cs.unc.edu Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 05450529871e..9eea8d1a0e8a 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -895,7 +895,11 @@ static int uart_set_info(struct tty_struct *tty, struct tty_port *port, upf_t old_flags, new_flags; int retval; - if (!uport || tty_io_error(tty)) + /* + * TTY_IO_ERROR is also set on a port that could not be started, and + * this is how such a port is configured. Only refuse a hung up port. + */ + if (!uport || !tty_port_active(port)) return -EIO; new_port = new_info->port; @@ -1143,7 +1147,8 @@ static int uart_do_autoconfig(struct tty_struct *tty, struct uart_state *state) */ scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &port->mutex) { uport = uart_port_check(state); - if (!uport || tty_io_error(tty)) + /* As in uart_set_info(), only refuse a hung up port. */ + if (!uport || !tty_port_active(port)) return -EIO; if (tty_port_users(port) != 1) From 106eec1cdb616f475183db60c5f396202ad1332d Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 13 Oct 2026 08:48:46 +0200 Subject: [PATCH 74/91] serial: 8250: drop err label from serial8250_register_8250_port() serial8250_register_8250_port() holds serial_mutex using guard(), but still uses goto-based error handling. Mixing the two is discouraged by the cleanup guidelines. Move the part of the function which configures the found port into a new helper: __serial8250_register_8250_port(). It returns an int and uses plain returns on failures. The caller now resets uart->port.dev on failure at a single place, so the err label can go. While at it, simplify the mctrl_gpio_init() error handling: return PTR_ERR() directly and drop the else branch. No functional change intended. Signed-off-by: Jiri Slaby (SUSE) Assisted-by: LLM Link: https://patch.msgid.link/baf799ad88f9ef43093ae96731f48a5e88d726dd.1791873876.git.jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 98 +++++++++++++++-------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 11424fef7c08..7ff307ce6aa8 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -743,46 +743,12 @@ static void serial_8250_overrun_backoff_work(struct work_struct *work) serial_out(up, UART_IER, up->ier); } -/** - * serial8250_register_8250_port - register a serial port - * @up: serial port template - * - * Configure the serial port specified by the request. If the - * port exists and is in use, it is hung up and unregistered - * first. - * - * The port is then probed and if necessary the IRQ is autodetected - * If this fails an error is returned. - * - * On success the port is ready to use and the line number is returned. - */ -int serial8250_register_8250_port(const struct uart_8250_port *up) +static int __serial8250_register_8250_port(struct uart_8250_port *uart, + const struct uart_8250_port *up) { - struct uart_8250_port *uart; bool cons_flow; int ret; - if (up->port.uartclk == 0) - return -EINVAL; - - guard(mutex)(&serial_mutex); - - uart = serial8250_find_match_or_unused(&up->port); - if (!uart) { - /* - * If the port is past the initial isa ports, initialize a new - * port and increment nr_uarts accordingly. - */ - uart = serial8250_setup_port(nr_uarts); - if (!uart) - return -ENOSPC; - nr_uarts++; - } - - /* Check if it is CIR already. We check this below again, see there why. */ - if (uart->port.type == PORT_8250_CIR) - return -ENODEV; - /* Preserve specified console flow control. */ cons_flow = uart_cons_flow_enabled(&uart->port); @@ -826,7 +792,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) uart->port.dev = up->port.dev; ret = uart_get_rs485_mode(&uart->port); if (ret) - goto err; + return ret; } if (up->port.flags & UPF_FIXED_TYPE) @@ -838,12 +804,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) */ if (uart->port.dev && !has_acpi_companion(uart->port.dev)) { struct mctrl_gpios *gpios = mctrl_gpio_init(&uart->port, 0); - if (IS_ERR(gpios)) { - ret = PTR_ERR(gpios); - goto err; - } else { - uart->gpios = gpios; - } + if (IS_ERR(gpios)) + return PTR_ERR(gpios); + + uart->gpios = gpios; } serial8250_set_defaults(uart); @@ -900,7 +864,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) ret = uart_add_one_port(&serial8250_reg, &uart->port); if (ret) - goto err; + return ret; ret = uart->port.line; } else { @@ -927,9 +891,51 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) } return ret; +} + +/** + * serial8250_register_8250_port - register a serial port + * @up: serial port template + * + * Configure the serial port specified by the request. If the + * port exists and is in use, it is hung up and unregistered + * first. + * + * The port is then probed and if necessary the IRQ is autodetected + * If this fails an error is returned. + * + * On success the port is ready to use and the line number is returned. + */ +int serial8250_register_8250_port(const struct uart_8250_port *up) +{ + struct uart_8250_port *uart; + int ret; + + if (up->port.uartclk == 0) + return -EINVAL; + + guard(mutex)(&serial_mutex); + + uart = serial8250_find_match_or_unused(&up->port); + if (!uart) { + /* + * If the port is past the initial isa ports, initialize a new + * port and increment nr_uarts accordingly. + */ + uart = serial8250_setup_port(nr_uarts); + if (!uart) + return -ENOSPC; + nr_uarts++; + } + + /* Check if it is CIR already. We check this below again, see there why. */ + if (uart->port.type == PORT_8250_CIR) + return -ENODEV; + + ret = __serial8250_register_8250_port(uart, up); + if (ret < 0) + uart->port.dev = NULL; -err: - uart->port.dev = NULL; return ret; } EXPORT_SYMBOL(serial8250_register_8250_port); From 4f28e10f18d6a30652ebd64589d35be515006141 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 13 Oct 2026 08:48:47 +0200 Subject: [PATCH 75/91] serial: 8250_exar: remove useless void * casts port->private_data is a void pointer. There is no need to cast it when assigning to or from struct exar8250 pointers. So drop the casts. Signed-off-by: Jiri Slaby (SUSE) Assisted-by: LLM Link: https://patch.msgid.link/c9c3f096b01befa764c3ba75457a76a0afa5800d.1791873876.git.jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_exar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_exar.c b/drivers/tty/serial/8250/8250_exar.c index 836792e861bf..ad2e5841fbf6 100644 --- a/drivers/tty/serial/8250/8250_exar.c +++ b/drivers/tty/serial/8250/8250_exar.c @@ -799,7 +799,7 @@ static int cti_rs485_config_mpio_tristate(struct uart_port *port, struct ktermios *termios, struct serial_rs485 *rs485) { - struct exar8250 *priv = (struct exar8250 *)port->private_data; + struct exar8250 *priv = port->private_data; int ret; ret = generic_rs485_config(port, termios, rs485); @@ -837,7 +837,7 @@ static int cti_port_setup_common(struct exar8250 *priv, if (ret) return ret; - port->port.private_data = (void *)priv; + port->port.private_data = priv; port->port.pm = exar_pm; port->port.shutdown = exar_shutdown; From 33d337816e00994daaf670243186d9e537ee090d Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 13 Oct 2026 08:48:48 +0200 Subject: [PATCH 76/91] serial: 8250_exar: store the GPIO platform device in struct exar8250 The GPIO platform device is registered via platform->register_gpio() when port 0 is being set up. It is stored to port->port.private_data of the on-stack uart template (and from there it is copied into every registered port). pci_xr17v35x_exit() then fetches it back by serial8250_get_port(priv->line[0]). But port 0 does not have to exist at that point. If serial8250_register_8250_port() fails for port 0, priv->line[0] holds a negative error code. The probe still succeeds (it only breaks the loop), so on unbind, priv->line[0] (a negative value) is passed to serial8250_get_port(). The GPIO device is never unregistered and a bogus port is accessed. The same problem would also occur after the planned conversion of serial8250_register_8250_port() to return a uart port instead of a line. As Sashiko pointed out in that case: If board->setup() fails for the first port (i=0) in exar_pci_probe(), priv->uport[0] remains NULL. If serial8250_register_8250_port() fails, it returns an ERR_PTR. In either case, the loop breaks and probe returns success. When the device is later unbound, pci_xr17v35x_exit() unconditionally passes priv->uport[0] to platform->unregister_gpio(), which will dereference it without checking for validity. The GPIO device belongs to the PCI device, not to any particular port. So store it in a new struct exar8250::gpio member. Pass priv to both ->register_gpio() and ->unregister_gpio(), so that pci_xr17v35x_exit() does not need to look up any port at all. Signed-off-by: Jiri Slaby (SUSE) Fixes: d0aeaa83f0b0 ("serial: exar: split out the exar code from 8250_pci") Link: https://lore.kernel.org/all/sashiko-outbox-166269@kernel.org/ Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/03a8b5c71614fc672fef1e55c083bda6c73b5bf3.1791873876.git.jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_exar.c | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/tty/serial/8250/8250_exar.c b/drivers/tty/serial/8250/8250_exar.c index ad2e5841fbf6..29b2a29ab6f3 100644 --- a/drivers/tty/serial/8250/8250_exar.c +++ b/drivers/tty/serial/8250/8250_exar.c @@ -231,8 +231,9 @@ struct exar8250_platform { int (*rs485_config)(struct uart_port *port, struct ktermios *termios, struct serial_rs485 *rs485); const struct serial_rs485 *rs485_supported; - int (*register_gpio)(struct pci_dev *pcidev, struct uart_8250_port *port); - void (*unregister_gpio)(struct uart_8250_port *port); + int (*register_gpio)(struct exar8250 *priv, struct pci_dev *pcidev, + struct uart_8250_port *port); + void (*unregister_gpio)(struct exar8250 *priv); }; /** @@ -256,6 +257,7 @@ struct exar8250 { struct exar8250_board *board; struct eeprom_93cx6 eeprom; void __iomem *virt; + struct platform_device *gpio; int line[]; }; @@ -1167,22 +1169,21 @@ static const struct software_node exar_gpio_node = { .properties = exar_gpio_properties, }; -static int xr17v35x_register_gpio(struct pci_dev *pcidev, struct uart_8250_port *port) +static int xr17v35x_register_gpio(struct exar8250 *priv, struct pci_dev *pcidev, + struct uart_8250_port *port) { if (pcidev->vendor == PCI_VENDOR_ID_EXAR) - port->port.private_data = - __xr17v35x_register_gpio(pcidev, &exar_gpio_node); + priv->gpio = __xr17v35x_register_gpio(pcidev, &exar_gpio_node); return 0; } -static void xr17v35x_unregister_gpio(struct uart_8250_port *port) +static void xr17v35x_unregister_gpio(struct exar8250 *priv) { - if (!port->port.private_data) - return; - - __xr17v35x_unregister_gpio(port->port.private_data); - port->port.private_data = NULL; + if (priv->gpio) { + __xr17v35x_unregister_gpio(priv->gpio); + priv->gpio = NULL; + } } static int sealevel_rs485_config(struct uart_port *port, struct ktermios *termios, @@ -1279,8 +1280,9 @@ static const struct software_node iot2040_gpio_node = { .properties = iot2040_gpio_properties, }; -static int iot2040_register_gpio(struct pci_dev *pcidev, - struct uart_8250_port *port) +static int iot2040_register_gpio(struct exar8250 *priv, + struct pci_dev *pcidev, + struct uart_8250_port *port) { u8 __iomem *p = port->port.membase; @@ -1289,8 +1291,7 @@ static int iot2040_register_gpio(struct pci_dev *pcidev, writeb(IOT2040_UARTS_ENABLE, p + UART_EXAR_MPIOLVL_15_8); writeb(IOT2040_UARTS_GPIO_HI_MODE, p + UART_EXAR_MPIOSEL_15_8); - port->port.private_data = - __xr17v35x_register_gpio(pcidev, &iot2040_gpio_node); + priv->gpio = __xr17v35x_register_gpio(pcidev, &iot2040_gpio_node); return 0; } @@ -1367,7 +1368,7 @@ pci_xr17v35x_setup(struct exar8250 *priv, struct pci_dev *pcidev, /* Setup Multipurpose Input/Output pins. */ setup_gpio(pcidev, p); - ret = platform->register_gpio(pcidev, port); + ret = platform->register_gpio(priv, pcidev, port); } return ret; @@ -1377,9 +1378,8 @@ static void pci_xr17v35x_exit(struct pci_dev *pcidev) { const struct exar8250_platform *platform = exar_get_platform(); struct exar8250 *priv = pci_get_drvdata(pcidev); - struct uart_8250_port *port = serial8250_get_port(priv->line[0]); - platform->unregister_gpio(port); + platform->unregister_gpio(priv); } static inline void exar_misc_clear(struct exar8250 *priv) From 3e3ae95a3d84a6028f37a07ab7ad78bb14689f2e Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 13 Oct 2026 08:48:49 +0200 Subject: [PATCH 77/91] serial: 8250_lpss: do not touch uninitialized line in ehl_serial_exit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lpss8250_probe() calls lpss->board->exit() on its error path. For the EHL board, that is ehl_serial_exit() and it does: serial8250_get_port(lpss->data.line)->dma = NULL; But when lpss8250_dma_setup() or serial8250_register_8250_port() fails, lpss->data.line was never assigned. As lpss is kzalloc()ed, line is 0 and ehl_serial_exit() clears the dma pointer of an unrelated port -- serial8250_ports[0], i.e. ttyS0, which is often the console. Initialize lpss->data.line to -1 in probe and skip the clearing in ehl_serial_exit() if no port was registered. Fixes: 5318f70da7e8 ("serial: 8250_lpss: Balance reference count for PCI DMA device") Signed-off-by: Jiri Slaby (SUSE) Assisted-by: LLM Cc: "Ilpo Järvinen" Cc: stable@kernel.org Cc: Andy Shevchenko Cc: linux-serial@vger.kernel.org Link: https://patch.msgid.link/f389df34aaa538270313ebd4cea75c2baaf46b95.1791873876.git.jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_lpss.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/8250/8250_lpss.c b/drivers/tty/serial/8250/8250_lpss.c index 776ec1ef29d6..325e587ae2c4 100644 --- a/drivers/tty/serial/8250/8250_lpss.c +++ b/drivers/tty/serial/8250/8250_lpss.c @@ -183,9 +183,8 @@ static int ehl_serial_setup(struct lpss8250 *lpss, struct uart_port *port) static void ehl_serial_exit(struct lpss8250 *lpss) { - struct uart_8250_port *up = serial8250_get_port(lpss->data.line); - - up->dma = NULL; + if (lpss->data.line >= 0) + serial8250_get_port(lpss->data.line)->dma = NULL; } #ifdef CONFIG_SERIAL_8250_DMA @@ -329,6 +328,8 @@ static int lpss8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) return ret; lpss->board = (struct lpss8250_board *)id->driver_data; + /* for tests in lpss->board->exit() */ + lpss->data.line = -1; memset(&uart, 0, sizeof(struct uart_8250_port)); From 8276a44f0a0b68c07b2331cc0058036ac4c4190d Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 13 Oct 2026 08:48:50 +0200 Subject: [PATCH 78/91] serial: 8250_omap: set priv->line before enabling the IRQ omap8250_irq() is requested in omap8250_probe() with IRQ_NOAUTOEN and enabled only in omap_8250_startup(). The handler obtains the port by serial8250_get_port(priv->line), but probe stores priv->line only after serial8250_register_8250_port() returns. The port becomes visible to userspace already inside serial8250_register_8250_port() (uart_add_one_port()). So it can be opened -- and the IRQ enabled -- before probe stores the line. An interrupt in that window makes the handler call serial8250_get_port(-ENODEV) and dereference a pointer outside of serial8250_ports[]. Store the line in omap_8250_startup() before enable_irq(), so the handler never runs with an unset line. Keep the assignment in probe too, as remove and the system PM callbacks rely on it even when the port was never opened. Fixes: fef4f600319e ("serial: 8250: omap: Fix life cycle issues for interrupt handlers") Signed-off-by: Jiri Slaby (SUSE) Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/3b3a642f7df2285f49a7bbbe5d1c23fa442c7d94.1791873876.git.jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_omap.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 3a1473bf54d0..137175aa4183 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -711,6 +711,9 @@ static int omap_8250_startup(struct uart_port *port) struct uart_8250_dma *dma = &priv->omap8250_dma; int ret; + /* ISR and runtime PM need the line; probe sets it only after registration */ + priv->line = port->line; + #ifdef CONFIG_PM up->capabilities |= UART_CAP_RPM; #endif From d6a0791098bedea41e813db646d21c9ba30004ec Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Tue, 13 Oct 2026 08:48:51 +0200 Subject: [PATCH 79/91] serial: 8250: make port operations work on struct uart_8250_port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit serial8250_register_8250_port() returns a line number and the other port operations (serial8250_unregister_port(), serial8250_suspend_port(), serial8250_resume_port()) accept it. Many drivers then convert the line back to the port using serial8250_get_port() anyway. Switch the interface to struct uart_8250_port: * serial8250_register_8250_port() now returns the registered port, or an ERR_PTR() on failure. * serial8250_unregister_port(), serial8250_suspend_port() and serial8250_resume_port() accept the port. Convert all users accordingly. Drivers store the port pointer instead of the line number. Several drivers kept a private struct only to hold the line (airoha, dfl, em, fsl, ioc3); these are dropped and the port is stored as drvdata directly. This also fixes two latent issues: * For a CIR port, serial8250_register_8250_port() used to return 0. Callers stored this as the line, so a later unregister/suspend/resume operated on ttyS0. Now the CIR port itself is returned. * 8250_acorn checked ports[i] > 0 on removal, so a port registered as line 0 was never unregistered. There are no more users of serial8250_get_port() outside of the 8250 core, so drop its EXPORT. Signed-off-by: Jiri Slaby (SUSE) Cc: "Ilpo Järvinen" Assisted-by: LLM Link: https://patch.msgid.link/c59fa203f325e0fe7601ea0fbc0d222c8b37f9eb.1791873876.git.jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/ptp/ptp_ocp.c | 36 +++++++++--------- drivers/tty/serial/8250/8250_acorn.c | 5 ++- drivers/tty/serial/8250/8250_airoha.c | 23 ++++------- drivers/tty/serial/8250/8250_aspeed_vuart.c | 13 ++++--- drivers/tty/serial/8250/8250_bcm2835aux.c | 20 +++++----- drivers/tty/serial/8250/8250_bcm7271.c | 33 ++++++++-------- drivers/tty/serial/8250/8250_core.c | 40 +++++++++----------- drivers/tty/serial/8250/8250_dfl.c | 22 ++++------- drivers/tty/serial/8250/8250_dw.c | 13 ++++--- drivers/tty/serial/8250/8250_dwlib.h | 2 +- drivers/tty/serial/8250/8250_em.c | 26 ++++--------- drivers/tty/serial/8250/8250_exar.c | 19 +++++----- drivers/tty/serial/8250/8250_fsl.c | 24 ++++-------- drivers/tty/serial/8250/8250_hp300.c | 27 ++++++------- drivers/tty/serial/8250/8250_ingenic.c | 11 +++--- drivers/tty/serial/8250/8250_ioc3.c | 26 +++++-------- drivers/tty/serial/8250/8250_keba.c | 13 +++---- drivers/tty/serial/8250/8250_loongson.c | 20 +++++----- drivers/tty/serial/8250/8250_lpc18xx.c | 11 +++--- drivers/tty/serial/8250/8250_lpss.c | 18 ++++----- drivers/tty/serial/8250/8250_men_mcb.c | 14 +++---- drivers/tty/serial/8250/8250_mid.c | 13 ++++--- drivers/tty/serial/8250/8250_mtk.c | 19 +++++----- drivers/tty/serial/8250/8250_mxpcie.c | 13 ++++--- drivers/tty/serial/8250/8250_ni.c | 12 +++--- drivers/tty/serial/8250/8250_of.c | 25 ++++++------ drivers/tty/serial/8250/8250_omap.c | 42 ++++++++++----------- drivers/tty/serial/8250/8250_parisc.c | 14 +++---- drivers/tty/serial/8250/8250_pci.c | 21 +++++------ drivers/tty/serial/8250/8250_pci1xxxx.c | 41 ++++++++++---------- drivers/tty/serial/8250/8250_pericom.c | 15 ++++---- drivers/tty/serial/8250/8250_platform.c | 23 +++++------ drivers/tty/serial/8250/8250_pnp.c | 21 +++++------ drivers/tty/serial/8250/8250_pxa.c | 17 +++++---- drivers/tty/serial/8250/8250_tegra.c | 20 +++++----- drivers/tty/serial/8250/8250_uniphier.c | 21 +++++------ drivers/tty/serial/8250/serial_cs.c | 15 ++++---- include/linux/serial_8250.h | 8 ++-- 38 files changed, 349 insertions(+), 407 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..da4b58e90b59 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -337,7 +337,7 @@ struct ptp_ocp_signal { }; struct ptp_ocp_serial_port { - int line; + struct uart_8250_port *uport; int baud; }; @@ -2538,7 +2538,7 @@ ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r) return err; } -static int +static struct uart_8250_port * ptp_ocp_serial_line(struct ptp_ocp *bp, struct ocp_resource *r) { struct pci_dev *pdev = bp->pdev; @@ -2566,9 +2566,11 @@ ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r) struct ptp_ocp_serial_port *p = (struct ptp_ocp_serial_port *)r->extra; struct ptp_ocp_serial_port port = {}; - port.line = ptp_ocp_serial_line(bp, r); - if (port.line < 0) - return port.line; + struct uart_8250_port *uport = ptp_ocp_serial_line(bp, r); + if (IS_ERR(uport)) + return PTR_ERR(uport); + + port.uport = uport; if (p) port.baud = p->baud; @@ -3711,7 +3713,7 @@ ptp_ocp_tty_show(struct device *dev, struct device_attribute *attr, char *buf) * as a device path (e.g., "/dev/ttyS4"), and adding a newline would * break those applications. Do not add a newline to this output. */ - return sysfs_emit(buf, "ttyS%d", bp->port[(uintptr_t)ea->var].line); + return sysfs_emit(buf, "ttyS%d", bp->port[(uintptr_t)ea->var].uport->port.line); } static umode_t @@ -3728,7 +3730,7 @@ ptp_ocp_timecard_tty_is_visible(struct kobject *kobj, struct attribute *attr, in dattr = container_of(attr, struct device_attribute, attr); ea = container_of(dattr, struct dev_ext_attribute, attr); port = &bp->port[(uintptr_t)ea->var]; - return port->line == -1 ? 0 : 0444; + return port->uport ? 0444 : 0; } #define EXT_TTY_ATTR_RO(_name, _val) \ @@ -4405,9 +4407,9 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp)); for (i = 0; i < __PORT_COUNT; i++) { - if (bp->port[i].line != -1) + if (bp->port[i].uport) seq_printf(s, "%7s: /dev/ttyS%d\n", ptp_ocp_tty_port_name(i), - bp->port[i].line); + bp->port[i].uport->port.line); } memset(sma_val, 0xff, sizeof(sma_val)); @@ -4731,7 +4733,7 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev) spin_lock_init(&bp->lock); for (i = 0; i < __PORT_COUNT; i++) - bp->port[i].line = -1; + bp->port[i].uport = NULL; bp->pdev = pdev; @@ -4820,10 +4822,10 @@ ptp_ocp_phc_info(struct ptp_ocp *bp) } static void -ptp_ocp_serial_info(struct device *dev, const char *name, int port, int baud) +ptp_ocp_serial_info(struct device *dev, const char *name, struct uart_8250_port *port, int baud) { - if (port != -1) - dev_info(dev, "%5s: /dev/ttyS%-2d @ %6d\n", name, port, baud); + if (port) + dev_info(dev, "%5s: /dev/ttyS%-2d @ %6d\n", name, port->port.line, baud); } static void @@ -4841,14 +4843,14 @@ ptp_ocp_info(struct ptp_ocp *bp) ptp_ocp_phc_info(bp); for (i = 0; i < __PORT_COUNT; i++) { - if (i == PORT_NMEA && bp->nmea_out && bp->port[PORT_NMEA].line != -1) { + if (i == PORT_NMEA && bp->nmea_out && bp->port[PORT_NMEA].uport) { bp->port[PORT_NMEA].baud = -1; reg = ioread32(&bp->nmea_out->uart_baud); if (reg < ARRAY_SIZE(nmea_baud)) bp->port[PORT_NMEA].baud = nmea_baud[reg]; } - ptp_ocp_serial_info(dev, ptp_ocp_tty_port_name(i), bp->port[i].line, + ptp_ocp_serial_info(dev, ptp_ocp_tty_port_name(i), bp->port[i].uport, bp->port[i].baud); } } @@ -4896,8 +4898,8 @@ ptp_ocp_detach(struct ptp_ocp *bp) for (i = 0; i < 4; i++) ptp_ocp_unregister_ext(bp->signal_out[i]); for (i = 0; i < __PORT_COUNT; i++) - if (bp->port[i].line != -1) - serial8250_unregister_port(bp->port[i].line); + if (bp->port[i].uport) + serial8250_unregister_port(bp->port[i].uport); platform_device_unregister(bp->spi_flash); platform_device_unregister(bp->i2c_ctrl); if (bp->i2c_clk) diff --git a/drivers/tty/serial/8250/8250_acorn.c b/drivers/tty/serial/8250/8250_acorn.c index fb596262b9c7..d0696668493a 100644 --- a/drivers/tty/serial/8250/8250_acorn.c +++ b/drivers/tty/serial/8250/8250_acorn.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,7 @@ struct serial_card_type { struct serial_card_info { unsigned int num_ports; - int ports[MAX_PORTS]; + struct uart_8250_port *ports[MAX_PORTS]; void __iomem *vaddr; }; @@ -85,7 +86,7 @@ static void serial_card_remove(struct expansion_card *ec) ecard_set_drvdata(ec, NULL); for (i = 0; i < info->num_ports; i++) - if (info->ports[i] > 0) + if (!IS_ERR_OR_NULL(info->ports[i])) serial8250_unregister_port(info->ports[i]); kfree(info); diff --git a/drivers/tty/serial/8250/8250_airoha.c b/drivers/tty/serial/8250/8250_airoha.c index fe72a39561c2..21f248e20771 100644 --- a/drivers/tty/serial/8250/8250_airoha.c +++ b/drivers/tty/serial/8250/8250_airoha.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -28,10 +29,6 @@ #define UART_AIROHA_XYD_X GENMASK(31, 16) #define UART_AIROHA_XYD_Y GENMASK(15, 0) -struct airoha_8250_priv { - int line; -}; - #define UART_BRD_20M 0x0001 #define XINDIV_CLOCK (20 * HZ_PER_MHZ) @@ -119,7 +116,6 @@ static int airoha_8250_probe(struct platform_device *pdev) { struct uart_8250_port uart = { }; struct device *dev = &pdev->dev; - struct airoha_8250_priv *priv; struct resource *res; int ret; @@ -127,10 +123,6 @@ static int airoha_8250_probe(struct platform_device *pdev) if (!res) return dev_err_probe(dev, -EINVAL, "invalid address\n"); - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - uart.port.dev = dev; if (device_is_compatible(dev, "airoha,an7581-hsuart")) uart.port.type = UART_PORT_AIROHA_HS; @@ -147,21 +139,20 @@ static int airoha_8250_probe(struct platform_device *pdev) if (ret) return ret; - ret = serial8250_register_8250_port(&uart); - if (ret < 0) - return ret; + struct uart_8250_port *uport = serial8250_register_8250_port(&uart); + if (IS_ERR(uport)) + return PTR_ERR(uport); - priv->line = ret; - platform_set_drvdata(pdev, priv); + platform_set_drvdata(pdev, uport); return 0; } static void airoha_8250_remove(struct platform_device *ofdev) { - struct airoha_8250_priv *priv = platform_get_drvdata(ofdev); + struct uart_8250_port *uport = platform_get_drvdata(ofdev); - serial8250_unregister_port(priv->line); + serial8250_unregister_port(uport); } static const struct of_device_id airoha_8250_dt_ids[] = { diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c index 26fc0464f1cc..c8dc87f6886c 100644 --- a/drivers/tty/serial/8250/8250_aspeed_vuart.c +++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c @@ -6,6 +6,7 @@ * Copyright (C) 2006 Arnd Bergmann , IBM Corp. */ #include +#include #include #include #include @@ -34,7 +35,6 @@ struct aspeed_vuart { struct device *dev; - int line; struct timer_list unthrottle_timer; struct uart_8250_port *port; }; @@ -484,12 +484,13 @@ static int aspeed_vuart_probe(struct platform_device *pdev) if (of_property_read_bool(np, "auto-flow-control")) port.capabilities |= UART_CAP_AFE; - rc = serial8250_register_8250_port(&port); - if (rc < 0) + struct uart_8250_port *uport = serial8250_register_8250_port(&port); + if (IS_ERR(uport)) { + rc = PTR_ERR(uport); goto err_sysfs_remove; + } - vuart->line = rc; - vuart->port = serial8250_get_port(vuart->line); + vuart->port = uport; rc = of_parse_phandle_with_fixed_args( np, "aspeed,sirq-polarity-sense", 2, 0, @@ -553,7 +554,7 @@ static void aspeed_vuart_remove(struct platform_device *pdev) timer_delete_sync(&vuart->unthrottle_timer); aspeed_vuart_set_enabled(vuart, false); - serial8250_unregister_port(vuart->line); + serial8250_unregister_port(vuart->port); sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group); } diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c index 626e2e61ef41..b842dba3059d 100644 --- a/drivers/tty/serial/8250/8250_bcm2835aux.c +++ b/drivers/tty/serial/8250/8250_bcm2835aux.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -50,12 +51,12 @@ /** * struct bcm2835aux_data - driver private data of BCM2835 auxiliary UART * @clk: clock producer of the port's uartclk - * @line: index of the port's serial8250_ports[] entry + * @uport: the underlying uart_8250_port * @cntl: cached copy of CNTL register */ struct bcm2835aux_data { struct clk *clk; - int line; + struct uart_8250_port *uport; u32 cntl; }; @@ -316,12 +317,12 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev) up.port.uartclk *= 2; /* register the port */ - ret = serial8250_register_8250_port(&up); - if (ret < 0) { + data->uport = serial8250_register_8250_port(&up); + if (IS_ERR(data->uport)) { + ret = PTR_ERR(data->uport); dev_err_probe(&pdev->dev, ret, "unable to register 8250 port\n"); goto dis_clk; } - data->line = ret; return 0; @@ -336,7 +337,7 @@ static void bcm2835aux_serial_remove(struct platform_device *pdev) { struct bcm2835aux_data *data = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(data->uport); clk_disable_unprepare(data->clk); device_remove_software_node(&pdev->dev); } @@ -376,12 +377,11 @@ MODULE_DEVICE_TABLE(acpi, bcm2835aux_serial_acpi_match); static bool bcm2835aux_can_disable_clock(struct device *dev) { struct bcm2835aux_data *data = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(data->line); if (device_may_wakeup(dev)) return false; - if (uart_console(&up->port) && !console_suspend_enabled) + if (uart_console(&data->uport->port) && !console_suspend_enabled) return false; return true; @@ -391,7 +391,7 @@ static int bcm2835aux_suspend(struct device *dev) { struct bcm2835aux_data *data = dev_get_drvdata(dev); - serial8250_suspend_port(data->line); + serial8250_suspend_port(data->uport); if (!bcm2835aux_can_disable_clock(dev)) return 0; @@ -411,7 +411,7 @@ static int bcm2835aux_resume(struct device *dev) return ret; } - serial8250_resume_port(data->line); + serial8250_resume_port(data->uport); return 0; } diff --git a/drivers/tty/serial/8250/8250_bcm7271.c b/drivers/tty/serial/8250/8250_bcm7271.c index 5f1140f05150..974d9af146a1 100644 --- a/drivers/tty/serial/8250/8250_bcm7271.c +++ b/drivers/tty/serial/8250/8250_bcm7271.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -204,13 +205,12 @@ static const u32 brcmstb_rate_table_7278[] = { }; struct brcmuart_priv { - int line; struct clk *baud_mux_clk; unsigned long default_mux_rate; u32 real_rates[ARRAY_SIZE(brcmstb_rate_table)]; const u32 *rate_table; ktime_t char_wait; - struct uart_port *up; + struct uart_8250_port *uport; struct hrtimer hrt; bool shutdown; bool dma_enabled; @@ -839,8 +839,8 @@ static int brcmuart_handle_irq(struct uart_port *p) static enum hrtimer_restart brcmuart_hrtimer_func(struct hrtimer *t) { struct brcmuart_priv *priv = container_of(t, struct brcmuart_priv, hrt); - struct uart_port *p = priv->up; - struct uart_8250_port *up = up_to_u8250p(p); + struct uart_8250_port *up = priv->uport; + struct uart_port *p = &up->port; unsigned int status; unsigned long flags; @@ -953,7 +953,6 @@ static int brcmuart_probe(struct platform_device *pdev) { struct resource *regs; const struct of_device_id *of_id = NULL; - struct uart_8250_port *new_port; struct device *dev = &pdev->dev; struct brcmuart_priv *priv; struct clk *baud_mux_clk; @@ -1083,14 +1082,12 @@ static int brcmuart_probe(struct platform_device *pdev) } } - ret = serial8250_register_8250_port(&up); - if (ret < 0) { + priv->uport = serial8250_register_8250_port(&up); + if (IS_ERR(priv->uport)) { + ret = PTR_ERR(priv->uport); dev_err_probe(dev, ret, "unable to register 8250 port\n"); goto err; } - priv->line = ret; - new_port = serial8250_get_port(ret); - priv->up = &new_port->port; if (priv->dma_enabled) { dma_irq = platform_get_irq_byname(pdev, "dma"); if (dma_irq < 0) { @@ -1098,7 +1095,7 @@ static int brcmuart_probe(struct platform_device *pdev) goto err1; } ret = devm_request_irq(dev, dma_irq, brcmuart_isr, - IRQF_SHARED, "uart DMA irq", &new_port->port); + IRQF_SHARED, "uart DMA irq", &priv->uport->port); if (ret) goto err1; } @@ -1107,7 +1104,7 @@ static int brcmuart_probe(struct platform_device *pdev) return 0; err1: - serial8250_unregister_port(priv->line); + serial8250_unregister_port(priv->uport); err: brcmuart_free_bufs(dev, priv); release_dma: @@ -1121,7 +1118,7 @@ static void brcmuart_remove(struct platform_device *pdev) struct brcmuart_priv *priv = platform_get_drvdata(pdev); debugfs_remove_recursive(priv->debugfs_dir); - serial8250_unregister_port(priv->line); + serial8250_unregister_port(priv->uport); hrtimer_cancel(&priv->hrt); brcmuart_free_bufs(&pdev->dev, priv); if (priv->dma_enabled) @@ -1131,7 +1128,7 @@ static void brcmuart_remove(struct platform_device *pdev) static int __maybe_unused brcmuart_suspend(struct device *dev) { struct brcmuart_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; struct uart_port *port = &up->port; unsigned long flags; @@ -1144,7 +1141,7 @@ static int __maybe_unused brcmuart_suspend(struct device *dev) port->mctrl &= ~TIOCM_RTS; uart_port_unlock_irqrestore(port, flags); - serial8250_suspend_port(priv->line); + serial8250_suspend_port(up); clk_disable_unprepare(priv->baud_mux_clk); return 0; @@ -1153,7 +1150,7 @@ static int __maybe_unused brcmuart_suspend(struct device *dev) static int __maybe_unused brcmuart_resume(struct device *dev) { struct brcmuart_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; struct uart_port *port = &up->port; unsigned long flags; int ret; @@ -1175,9 +1172,9 @@ static int __maybe_unused brcmuart_resume(struct device *dev) return(-EBUSY); } brcmuart_init_dma_hardware(priv); - start_rx_dma(serial8250_get_port(priv->line)); + start_rx_dma(up); } - serial8250_resume_port(priv->line); + serial8250_resume_port(up); if (priv->saved_mctrl & TIOCM_RTS) { /* Restore RTS */ diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 7ff307ce6aa8..42319843cb5b 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -333,7 +334,6 @@ struct uart_8250_port *serial8250_get_port(int line) { return &serial8250_ports[line]; } -EXPORT_SYMBOL_GPL(serial8250_get_port); static inline void serial8250_apply_quirks(struct uart_8250_port *up) { @@ -600,13 +600,12 @@ int __init early_serial_setup(struct uart_port *port) /** * serial8250_suspend_port - suspend one serial port - * @line: serial line number + * @up: serial port * * Suspend one serial port. */ -void serial8250_suspend_port(int line) +void serial8250_suspend_port(struct uart_8250_port *up) { - struct uart_8250_port *up = &serial8250_ports[line]; struct uart_port *port = &up->port; if (uart_console(port)) { @@ -630,13 +629,12 @@ EXPORT_SYMBOL(serial8250_suspend_port); /** * serial8250_resume_port - resume one serial port - * @line: serial line number + * @up: serial port * * Resume one serial port. */ -void serial8250_resume_port(int line) +void serial8250_resume_port(struct uart_8250_port *up) { - struct uart_8250_port *up = &serial8250_ports[line]; struct uart_port *port = &up->port; up->canary = 0; @@ -865,16 +863,12 @@ static int __serial8250_register_8250_port(struct uart_8250_port *uart, &uart->port); if (ret) return ret; - - ret = uart->port.line; } else { dev_info(uart->port.dev, "skipping CIR port at 0x%lx / 0x%llx, IRQ %d\n", uart->port.iobase, (unsigned long long)uart->port.mapbase, uart->port.irq); - - ret = 0; } if (!uart->lsr_save_mask) @@ -890,7 +884,7 @@ static int __serial8250_register_8250_port(struct uart_8250_port *uart, uart->overrun_backoff_time_ms = 0; } - return ret; + return 0; } /** @@ -906,13 +900,13 @@ static int __serial8250_register_8250_port(struct uart_8250_port *uart, * * On success the port is ready to use and the line number is returned. */ -int serial8250_register_8250_port(const struct uart_8250_port *up) +struct uart_8250_port *serial8250_register_8250_port(const struct uart_8250_port *up) { struct uart_8250_port *uart; int ret; if (up->port.uartclk == 0) - return -EINVAL; + return ERR_PTR(-EINVAL); guard(mutex)(&serial_mutex); @@ -924,33 +918,33 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) */ uart = serial8250_setup_port(nr_uarts); if (!uart) - return -ENOSPC; + return ERR_PTR(-ENOSPC); nr_uarts++; } /* Check if it is CIR already. We check this below again, see there why. */ if (uart->port.type == PORT_8250_CIR) - return -ENODEV; + return ERR_PTR(-ENODEV); ret = __serial8250_register_8250_port(uart, up); - if (ret < 0) + if (ret < 0) { uart->port.dev = NULL; + return ERR_PTR(ret); + } - return ret; + return uart; } EXPORT_SYMBOL(serial8250_register_8250_port); /** * serial8250_unregister_port - remove a 16x50 serial port at runtime - * @line: serial line number + * @uart: port * * Remove one serial port. This may not be called from interrupt * context. We hand the port back to the our control. */ -void serial8250_unregister_port(int line) +void serial8250_unregister_port(struct uart_8250_port *uart) { - struct uart_8250_port *uart = &serial8250_ports[line]; - guard(mutex)(&serial_mutex); if (uart->em485) { @@ -963,7 +957,7 @@ void serial8250_unregister_port(int line) uart->port.flags &= ~UPF_BOOT_AUTOCONF; uart->port.type = PORT_UNKNOWN; uart->port.dev = &serial8250_isa_devs->dev; - uart->port.port_id = line; + uart->port.port_id = uart->port.line; uart->capabilities = 0; serial8250_init_port(uart); serial8250_apply_quirks(uart); diff --git a/drivers/tty/serial/8250/8250_dfl.c b/drivers/tty/serial/8250/8250_dfl.c index 475ca340948c..1343375897a8 100644 --- a/drivers/tty/serial/8250/8250_dfl.c +++ b/drivers/tty/serial/8250/8250_dfl.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,10 +28,6 @@ #define DFHv1_PARAM_REG_LAYOUT_WIDTH GENMASK_ULL(63, 32) #define DFHv1_PARAM_REG_LAYOUT_SHIFT GENMASK_ULL(31, 0) -struct dfl_uart { - int line; -}; - static int dfh_get_u64_param_val(struct dfl_device *dfl_dev, int param_id, u64 *pval) { size_t psize; @@ -109,7 +106,6 @@ static int dfl_uart_probe(struct dfl_device *dfl_dev) { struct device *dev = &dfl_dev->dev; struct uart_8250_port uart = { }; - struct dfl_uart *dfluart; int ret; uart.port.flags = UPF_IOREMAP; @@ -123,24 +119,20 @@ static int dfl_uart_probe(struct dfl_device *dfl_dev) if (dfl_dev->num_irqs == 1) uart.port.irq = dfl_dev->irqs[0]; - dfluart = devm_kzalloc(dev, sizeof(*dfluart), GFP_KERNEL); - if (!dfluart) - return -ENOMEM; + struct uart_8250_port *uport = serial8250_register_8250_port(&uart); + if (IS_ERR(uport)) + return dev_err_probe(dev, PTR_ERR(uport), "unable to register 8250 port.\n"); - dfluart->line = serial8250_register_8250_port(&uart); - if (dfluart->line < 0) - return dev_err_probe(dev, dfluart->line, "unable to register 8250 port.\n"); - - dev_set_drvdata(dev, dfluart); + dev_set_drvdata(dev, uport); return 0; } static void dfl_uart_remove(struct dfl_device *dfl_dev) { - struct dfl_uart *dfluart = dev_get_drvdata(&dfl_dev->dev); + struct uart_8250_port *uport = dev_get_drvdata(&dfl_dev->dev); - serial8250_unregister_port(dfluart->line); + serial8250_unregister_port(uport); } #define FME_FEATURE_ID_UART 0x24 diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 0ab558230370..eca02e8bd6d1 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -774,9 +775,9 @@ static int dw8250_probe(struct platform_device *pdev) up->dma = &data->data.dma; } - data->data.line = serial8250_register_8250_port(up); - if (data->data.line < 0) - return data->data.line; + data->data.uport = serial8250_register_8250_port(up); + if (IS_ERR(data->data.uport)) + return PTR_ERR(data->data.uport); platform_set_drvdata(pdev, data); @@ -792,7 +793,7 @@ static void dw8250_remove(struct platform_device *pdev) pm_runtime_get_sync(dev); - serial8250_unregister_port(data->data.line); + serial8250_unregister_port(data->data.uport); pm_runtime_disable(dev); pm_runtime_put_noidle(dev); @@ -802,7 +803,7 @@ static int dw8250_suspend(struct device *dev) { struct dw8250_data *data = dev_get_drvdata(dev); - serial8250_suspend_port(data->data.line); + serial8250_suspend_port(data->data.uport); return 0; } @@ -811,7 +812,7 @@ static int dw8250_resume(struct device *dev) { struct dw8250_data *data = dev_get_drvdata(dev); - serial8250_resume_port(data->data.line); + serial8250_resume_port(data->data.uport); return 0; } diff --git a/drivers/tty/serial/8250/8250_dwlib.h b/drivers/tty/serial/8250/8250_dwlib.h index ee7a07fac0f6..39fcb1b323ad 100644 --- a/drivers/tty/serial/8250/8250_dwlib.h +++ b/drivers/tty/serial/8250/8250_dwlib.h @@ -80,7 +80,7 @@ struct dw8250_port_data { /* Port properties */ - int line; + struct uart_8250_port *uport; /* DMA operations */ struct uart_8250_dma dma; diff --git a/drivers/tty/serial/8250/8250_em.c b/drivers/tty/serial/8250/8250_em.c index 32d077da076a..bcb71ff8ee8b 100644 --- a/drivers/tty/serial/8250/8250_em.c +++ b/drivers/tty/serial/8250/8250_em.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -28,10 +29,6 @@ #define UART_HCR0_EM_SW_RESET BIT(7) /* SW Reset */ -struct serial8250_em_priv { - int line; -}; - static void serial8250_em_serial_out_helper(struct uart_port *p, int offset, int value) { @@ -151,12 +148,11 @@ static void serial8250_em_serial_dl_write(struct uart_8250_port *up, u32 value) static int serial8250_em_probe(struct platform_device *pdev) { - struct serial8250_em_priv *priv; struct device *dev = &pdev->dev; struct uart_8250_port up; struct resource *regs; struct clk *sclk; - int irq, ret; + int irq; irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -166,10 +162,6 @@ static int serial8250_em_probe(struct platform_device *pdev) if (!regs) return dev_err_probe(dev, -EINVAL, "missing registers\n"); - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - sclk = devm_clk_get_enabled(dev, "sclk"); if (IS_ERR(sclk)) return dev_err_probe(dev, PTR_ERR(sclk), "unable to get clock\n"); @@ -180,7 +172,6 @@ static int serial8250_em_probe(struct platform_device *pdev) up.port.type = PORT_16750; up.port.flags = UPF_FIXED_PORT | UPF_IOREMAP | UPF_FIXED_TYPE; up.port.dev = dev; - up.port.private_data = priv; up.port.uartclk = clk_get_rate(sclk); @@ -190,20 +181,19 @@ static int serial8250_em_probe(struct platform_device *pdev) up.dl_read = serial8250_em_serial_dl_read; up.dl_write = serial8250_em_serial_dl_write; - ret = serial8250_register_8250_port(&up); - if (ret < 0) - return dev_err_probe(dev, ret, "unable to register 8250 port\n"); + struct uart_8250_port *uport = serial8250_register_8250_port(&up); + if (IS_ERR(uport)) + return dev_err_probe(dev, PTR_ERR(uport), "unable to register 8250 port\n"); - priv->line = ret; - platform_set_drvdata(pdev, priv); + platform_set_drvdata(pdev, uport); return 0; } static void serial8250_em_remove(struct platform_device *pdev) { - struct serial8250_em_priv *priv = platform_get_drvdata(pdev); + struct uart_8250_port *up = platform_get_drvdata(pdev); - serial8250_unregister_port(priv->line); + serial8250_unregister_port(up); } static const struct of_device_id serial8250_em_dt_ids[] = { diff --git a/drivers/tty/serial/8250/8250_exar.c b/drivers/tty/serial/8250/8250_exar.c index 29b2a29ab6f3..57847c5bd2fc 100644 --- a/drivers/tty/serial/8250/8250_exar.c +++ b/drivers/tty/serial/8250/8250_exar.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -258,7 +259,7 @@ struct exar8250 { struct eeprom_93cx6 eeprom; void __iomem *virt; struct platform_device *gpio; - int line[]; + struct uart_8250_port *uport[]; }; static inline void exar_write_reg(struct exar8250 *priv, @@ -1460,7 +1461,7 @@ exar_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *ent) if (nr_ports == 0) return dev_err_probe(&pcidev->dev, -ENODEV, "failed to get number of ports\n"); - priv = devm_kzalloc(&pcidev->dev, struct_size(priv, line, nr_ports), GFP_KERNEL); + priv = devm_kzalloc(&pcidev->dev, struct_size(priv, uport, nr_ports), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -1500,9 +1501,9 @@ exar_pci_probe(struct pci_dev *pcidev, const struct pci_device_id *ent) dev_dbg(&pcidev->dev, "Setup PCI port: port %lx, irq %d, type %d\n", uart.port.iobase, uart.port.irq, uart.port.iotype); - priv->line[i] = serial8250_register_8250_port(&uart); - if (priv->line[i] < 0) { - dev_err_probe(&pcidev->dev, priv->line[i], + priv->uport[i] = serial8250_register_8250_port(&uart); + if (IS_ERR(priv->uport[i])) { + dev_err_probe(&pcidev->dev, PTR_ERR(priv->uport[i]), "Couldn't register serial port %lx, type %d, irq %d\n", uart.port.iobase, uart.port.iotype, uart.port.irq); break; @@ -1519,7 +1520,7 @@ static void exar_pci_remove(struct pci_dev *pcidev) unsigned int i; for (i = 0; i < priv->nr; i++) - serial8250_unregister_port(priv->line[i]); + serial8250_unregister_port(priv->uport[i]); /* Ensure that every init quirk is properly torn down */ if (priv->board->exit) @@ -1532,8 +1533,7 @@ static int exar_suspend(struct device *dev) unsigned int i; for (i = 0; i < priv->nr; i++) - if (priv->line[i] >= 0) - serial8250_suspend_port(priv->line[i]); + serial8250_suspend_port(priv->uport[i]); return 0; } @@ -1546,8 +1546,7 @@ static int exar_resume(struct device *dev) exar_misc_clear(priv); for (i = 0; i < priv->nr; i++) - if (priv->line[i] >= 0) - serial8250_resume_port(priv->line[i]); + serial8250_resume_port(priv->uport[i]); return 0; } diff --git a/drivers/tty/serial/8250/8250_fsl.c b/drivers/tty/serial/8250/8250_fsl.c index 649ae5c8304d..3578d70594ed 100644 --- a/drivers/tty/serial/8250/8250_fsl.c +++ b/drivers/tty/serial/8250/8250_fsl.c @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -99,14 +100,9 @@ int fsl8250_handle_irq(struct uart_port *port) EXPORT_SYMBOL_GPL(fsl8250_handle_irq); #ifdef CONFIG_ACPI -struct fsl8250_data { - int line; -}; - static int fsl8250_acpi_probe(struct platform_device *pdev) { - struct fsl8250_data *data; - struct uart_8250_port port8250; + struct uart_8250_port *uport, port8250; struct device *dev = &pdev->dev; struct resource *regs; @@ -148,23 +144,19 @@ static int fsl8250_acpi_probe(struct platform_device *pdev) if (!port8250.port.membase) return -ENOMEM; - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; + uport = serial8250_register_8250_port(&port8250); + if (IS_ERR(uport)) + return PTR_ERR(uport); - data->line = serial8250_register_8250_port(&port8250); - if (data->line < 0) - return data->line; - - platform_set_drvdata(pdev, data); + platform_set_drvdata(pdev, uport); return 0; } static void fsl8250_acpi_remove(struct platform_device *pdev) { - struct fsl8250_data *data = platform_get_drvdata(pdev); + struct uart_8250_port *uport = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(uport); } static const struct acpi_device_id fsl_8250_acpi_id[] = { diff --git a/drivers/tty/serial/8250/8250_hp300.c b/drivers/tty/serial/8250/8250_hp300.c index 59ec0c7377a1..e9599c5c2fea 100644 --- a/drivers/tty/serial/8250/8250_hp300.c +++ b/drivers/tty/serial/8250/8250_hp300.c @@ -7,6 +7,7 @@ * by Kars de Jong , May 2004. */ #include +#include #include #include #include @@ -27,7 +28,7 @@ #ifdef CONFIG_HPAPCI struct hp300_port { struct hp300_port *next; /* next port */ - int line; /* line (tty) number */ + struct uart_8250_port *uport; }; static struct hp300_port *hp300_ports; @@ -157,8 +158,7 @@ int __init hp300_setup_serial_console(void) static int hpdca_init_one(struct dio_dev *d, const struct dio_device_id *ent) { - struct uart_8250_port uart; - int line; + struct uart_8250_port *uport, uart; #ifdef CONFIG_SERIAL_8250_CONSOLE if (hp300_uart_scode == d->scode) { @@ -177,9 +177,8 @@ static int hpdca_init_one(struct dio_dev *d, uart.port.membase = (char *)(uart.port.mapbase + DIO_VIRADDRBASE); uart.port.regshift = 1; uart.port.dev = &d->dev; - line = serial8250_register_8250_port(&uart); - - if (line < 0) { + uport = serial8250_register_8250_port(&uart); + if (IS_ERR(uport)) { dev_notice(&d->dev, "8250_hp300: register_serial() DCA scode %d irq %d failed\n", d->scode, uart.port.irq); @@ -188,7 +187,7 @@ static int hpdca_init_one(struct dio_dev *d, /* Enable board-interrupts */ out_8(d->resource.start + DIO_VIRADDRBASE + DCA_IC, DCA_IC_IE); - dio_set_drvdata(d, (void *)line); + dio_set_drvdata(d, uport); /* Reset the DCA */ out_8(d->resource.start + DIO_VIRADDRBASE + DCA_ID, 0xff); @@ -204,7 +203,6 @@ static int __init hp300_8250_init(void) { static int called; #ifdef CONFIG_HPAPCI - int line; unsigned long base; struct uart_8250_port uart; struct hp300_port *port; @@ -259,9 +257,8 @@ static int __init hp300_8250_init(void) uart.port.membase = (char *)(base + DIO_VIRADDRBASE); uart.port.regshift = 2; - line = serial8250_register_8250_port(&uart); - - if (line < 0) { + port->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(port->uport)) { dev_notice(uart.port.dev, "8250_hp300: register_serial() APCI %d irq %d failed\n", i, uart.port.irq); @@ -269,7 +266,6 @@ static int __init hp300_8250_init(void) continue; } - port->line = line; port->next = hp300_ports; hp300_ports = port; @@ -287,14 +283,13 @@ static int __init hp300_8250_init(void) #ifdef CONFIG_HPDCA static void hpdca_remove_one(struct dio_dev *d) { - int line; + struct uart_8250_port *uport = dio_get_drvdata(d); - line = (int) dio_get_drvdata(d); if (d->resource.start) { /* Disable board-interrupts */ out_8(d->resource.start + DIO_VIRADDRBASE + DCA_IC, 0); } - serial8250_unregister_port(line); + serial8250_unregister_port(uport); } #endif @@ -304,7 +299,7 @@ static void __exit hp300_8250_exit(void) struct hp300_port *port, *to_free; for (port = hp300_ports; port; ) { - serial8250_unregister_port(port->line); + serial8250_unregister_port(port->uport); to_free = port; port = port->next; kfree(to_free); diff --git a/drivers/tty/serial/8250/8250_ingenic.c b/drivers/tty/serial/8250/8250_ingenic.c index 94542fc143c2..0cdadacd7019 100644 --- a/drivers/tty/serial/8250/8250_ingenic.c +++ b/drivers/tty/serial/8250/8250_ingenic.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -29,7 +30,7 @@ struct ingenic_uart_config { struct ingenic_uart_data { struct clk *clk_module; struct clk *clk_baud; - int line; + struct uart_8250_port *uport; }; static const struct of_device_id of_match[]; @@ -297,9 +298,9 @@ static int ingenic_uart_probe(struct platform_device *pdev) } uart.port.uartclk = clk_get_rate(data->clk_baud); - data->line = serial8250_register_8250_port(&uart); - if (data->line < 0) { - err = data->line; + data->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(data->uport)) { + err = PTR_ERR(data->uport); goto out_disable_baudclk; } @@ -318,7 +319,7 @@ static void ingenic_uart_remove(struct platform_device *pdev) { struct ingenic_uart_data *data = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(data->uport); clk_disable_unprepare(data->clk_module); clk_disable_unprepare(data->clk_baud); } diff --git a/drivers/tty/serial/8250/8250_ioc3.c b/drivers/tty/serial/8250/8250_ioc3.c index 28e28076782b..088242de2eff 100644 --- a/drivers/tty/serial/8250/8250_ioc3.c +++ b/drivers/tty/serial/8250/8250_ioc3.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -17,10 +18,6 @@ #define IOC3_UARTCLK (22000000 / 3) -struct ioc3_8250_data { - int line; -}; - static u32 ioc3_serial_in(struct uart_port *p, unsigned int offset) { return readb(p->membase + (offset ^ 3)); @@ -33,20 +30,15 @@ static void ioc3_serial_out(struct uart_port *p, unsigned int offset, u32 value) static int serial8250_ioc3_probe(struct platform_device *pdev) { - struct ioc3_8250_data *data; - struct uart_8250_port up; + struct uart_8250_port *uport, up; struct resource *r; void __iomem *membase; - int irq, line; + int irq; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r) return -ENODEV; - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - membase = devm_ioremap(&pdev->dev, r->start, resource_size(r)); if (!membase) return -ENOMEM; @@ -67,19 +59,19 @@ static int serial8250_ioc3_probe(struct platform_device *pdev) up.port.mapbase = r->start; up.port.serial_in = ioc3_serial_in; up.port.serial_out = ioc3_serial_out; - line = serial8250_register_8250_port(&up); - if (line < 0) - return line; + uport = serial8250_register_8250_port(&up); + if (IS_ERR(uport)) + return PTR_ERR(uport); - platform_set_drvdata(pdev, data); + platform_set_drvdata(pdev, uport); return 0; } static void serial8250_ioc3_remove(struct platform_device *pdev) { - struct ioc3_8250_data *data = platform_get_drvdata(pdev); + struct uart_8250_port *uport = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(uport); } static struct platform_driver serial8250_ioc3_driver = { diff --git a/drivers/tty/serial/8250/8250_keba.c b/drivers/tty/serial/8250/8250_keba.c index 5b791b6eefd0..c53ca05a57d7 100644 --- a/drivers/tty/serial/8250/8250_keba.c +++ b/drivers/tty/serial/8250/8250_keba.c @@ -62,7 +62,7 @@ enum kuart_mode { struct kuart { struct keba_uart_auxdev *auxdev; void __iomem *base; - unsigned int line; + struct uart_8250_port *uport; unsigned int flags; u8 capability; @@ -174,7 +174,6 @@ static int kuart_probe(struct auxiliary_device *auxdev, struct uart_8250_port uart = {}; struct resource res; struct kuart *kuart; - int retval; kuart = devm_kzalloc(dev, sizeof(*kuart), GFP_KERNEL); if (!kuart) @@ -250,12 +249,10 @@ static int kuart_probe(struct auxiliary_device *auxdev, } } - retval = serial8250_register_8250_port(&uart); - if (retval < 0) - return dev_err_probe(&auxdev->dev, retval, + kuart->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(kuart->uport)) + return dev_err_probe(&auxdev->dev, PTR_ERR(kuart->uport), "UART registration failed!\n"); - kuart->line = retval; - return 0; } @@ -266,7 +263,7 @@ static void kuart_remove(struct auxiliary_device *auxdev) if (kuart->flags & KUART_USE_CAPABILITY) kuart_set_phy_mode(kuart, KUART_MODE_NONE); - serial8250_unregister_port(kuart->line); + serial8250_unregister_port(kuart->uport); } static const struct auxiliary_device_id kuart_devtype_aux[] = { diff --git a/drivers/tty/serial/8250/8250_loongson.c b/drivers/tty/serial/8250/8250_loongson.c index 19acb2c6b611..6d06ed28382d 100644 --- a/drivers/tty/serial/8250/8250_loongson.c +++ b/drivers/tty/serial/8250/8250_loongson.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,7 @@ static const struct loongson_uart_ddata ls2k1500_uart_data = { }; struct loongson_uart_priv { - int line; + struct uart_8250_port *uport; struct clk *clk; struct resource *res; struct reset_control *rst; @@ -160,13 +161,12 @@ static int loongson_uart_probe(struct platform_device *pdev) if (ret) return ret; - ret = serial8250_register_8250_port(&uart); - if (ret < 0) { + priv->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(priv->uport)) { reset_control_assert(priv->rst); - return ret; + return PTR_ERR(priv->uport); } - priv->line = ret; platform_set_drvdata(pdev, priv); return 0; @@ -176,16 +176,16 @@ static void loongson_uart_remove(struct platform_device *pdev) { struct loongson_uart_priv *priv = platform_get_drvdata(pdev); - serial8250_unregister_port(priv->line); + serial8250_unregister_port(priv->uport); 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); + struct uart_8250_port *up = priv->uport; - serial8250_suspend_port(priv->line); + serial8250_suspend_port(up); if (!uart_console(&up->port) || console_suspend_enabled) clk_disable_unprepare(priv->clk); @@ -196,7 +196,7 @@ static int loongson_uart_suspend(struct device *dev) 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); + struct uart_8250_port *up = priv->uport; int ret; if (!uart_console(&up->port) || console_suspend_enabled) { @@ -205,7 +205,7 @@ static int loongson_uart_resume(struct device *dev) return ret; } - serial8250_resume_port(priv->line); + serial8250_resume_port(up); return 0; } diff --git a/drivers/tty/serial/8250/8250_lpc18xx.c b/drivers/tty/serial/8250/8250_lpc18xx.c index 6c0489c9c253..5d4d4366e711 100644 --- a/drivers/tty/serial/8250/8250_lpc18xx.c +++ b/drivers/tty/serial/8250/8250_lpc18xx.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include @@ -29,7 +30,7 @@ struct lpc18xx_uart_data { struct uart_8250_dma dma; struct clk *clk_uart; struct clk *clk_reg; - int line; + struct uart_8250_port *uport; }; static int lpc18xx_rs485_config(struct uart_port *port, struct ktermios *termios, @@ -160,13 +161,13 @@ static int lpc18xx_serial_probe(struct platform_device *pdev) uart.dma->rxconf.src_maxburst = 1; uart.dma->txconf.dst_maxburst = 1; - ret = serial8250_register_8250_port(&uart); - if (ret < 0) { + data->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(data->uport)) { + ret = PTR_ERR(data->uport); dev_err(&pdev->dev, "unable to register 8250 port\n"); goto dis_uart_clk; } - data->line = ret; platform_set_drvdata(pdev, data); return 0; @@ -182,7 +183,7 @@ static void lpc18xx_serial_remove(struct platform_device *pdev) { struct lpc18xx_uart_data *data = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(data->uport); clk_disable_unprepare(data->clk_uart); clk_disable_unprepare(data->clk_reg); } diff --git a/drivers/tty/serial/8250/8250_lpss.c b/drivers/tty/serial/8250/8250_lpss.c index 325e587ae2c4..f1b4b975571a 100644 --- a/drivers/tty/serial/8250/8250_lpss.c +++ b/drivers/tty/serial/8250/8250_lpss.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -183,8 +184,8 @@ static int ehl_serial_setup(struct lpss8250 *lpss, struct uart_port *port) static void ehl_serial_exit(struct lpss8250 *lpss) { - if (lpss->data.line >= 0) - serial8250_get_port(lpss->data.line)->dma = NULL; + if (lpss->data.uport) + lpss->data.uport->dma = NULL; } #ifdef CONFIG_SERIAL_8250_DMA @@ -328,8 +329,6 @@ static int lpss8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) return ret; lpss->board = (struct lpss8250_board *)id->driver_data; - /* for tests in lpss->board->exit() */ - lpss->data.line = -1; memset(&uart, 0, sizeof(struct uart_8250_port)); @@ -357,11 +356,12 @@ static int lpss8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (ret) goto err_exit; - ret = serial8250_register_8250_port(&uart); - if (ret < 0) + struct uart_8250_port *uport = serial8250_register_8250_port(&uart); + if (IS_ERR(uport)) { + ret = PTR_ERR(uport); goto err_exit; - - lpss->data.line = ret; + } + lpss->data.uport = uport; pci_set_drvdata(pdev, lpss); return 0; @@ -376,7 +376,7 @@ static void lpss8250_remove(struct pci_dev *pdev) { struct lpss8250 *lpss = pci_get_drvdata(pdev); - serial8250_unregister_port(lpss->data.line); + serial8250_unregister_port(lpss->data.uport); lpss->board->exit(lpss); pci_free_irq_vectors(pdev); diff --git a/drivers/tty/serial/8250/8250_men_mcb.c b/drivers/tty/serial/8250/8250_men_mcb.c index 6373234da03d..1f34e58aaeaf 100644 --- a/drivers/tty/serial/8250/8250_men_mcb.c +++ b/drivers/tty/serial/8250/8250_men_mcb.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -42,7 +43,7 @@ struct serial_8250_men_mcb_data { int num_ports; - int line[MEN_Z025_MAX_UARTS]; + struct uart_8250_port *uport[MEN_Z025_MAX_UARTS]; unsigned int offset[MEN_Z025_MAX_UARTS]; }; @@ -218,14 +219,13 @@ static int serial_8250_men_mcb_probe(struct mcb_device *mdev, + data->offset[i]; /* ok, register the port */ - res = serial8250_register_8250_port(&uart); - if (res < 0) { + data->uport[i] = serial8250_register_8250_port(&uart); + if (IS_ERR(data->uport[i])) { dev_err(&mdev->dev, "unable to register UART port\n"); - return res; + return PTR_ERR(data->uport[i]); } - data->line[i] = res; - dev_info(&mdev->dev, "found MCB UART: ttyS%d\n", data->line[i]); + dev_info(&mdev->dev, "found MCB UART: ttyS%d\n", data->uport[i]->port.line); } return 0; @@ -240,7 +240,7 @@ static void serial_8250_men_mcb_remove(struct mcb_device *mdev) return; for (i = 0; i < data->num_ports; i++) - serial8250_unregister_port(data->line[i]); + serial8250_unregister_port(data->uport[i]); } static const struct mcb_device_id serial_8250_men_mcb_ids[] = { diff --git a/drivers/tty/serial/8250/8250_mid.c b/drivers/tty/serial/8250/8250_mid.c index 47f820fcd1a4..44b43163df51 100644 --- a/drivers/tty/serial/8250/8250_mid.c +++ b/drivers/tty/serial/8250/8250_mid.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -41,7 +42,7 @@ struct mid8250_board { }; struct mid8250 { - int line; + struct uart_8250_port *uport; int dma_index; struct pci_dev *dma_dev; struct uart_8250_dma dma; @@ -335,11 +336,11 @@ static int mid8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (ret) goto err; - ret = serial8250_register_8250_port(&uart); - if (ret < 0) + mid->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(mid->uport)) { + ret = PTR_ERR(mid->uport); goto err; - - mid->line = ret; + } pci_set_drvdata(pdev, mid); return 0; @@ -354,7 +355,7 @@ static void mid8250_remove(struct pci_dev *pdev) { struct mid8250 *mid = pci_get_drvdata(pdev); - serial8250_unregister_port(mid->line); + serial8250_unregister_port(mid->uport); if (mid->board->exit) mid->board->exit(mid); diff --git a/drivers/tty/serial/8250/8250_mtk.c b/drivers/tty/serial/8250/8250_mtk.c index 841d64f6a707..d1fb917b762a 100644 --- a/drivers/tty/serial/8250/8250_mtk.c +++ b/drivers/tty/serial/8250/8250_mtk.c @@ -6,6 +6,7 @@ * Author: Matthias Brugger */ #include +#include #include #include #include @@ -68,7 +69,7 @@ enum dma_rx_status { #endif struct mtk8250_data { - int line; + struct uart_8250_port *uport; unsigned int rx_pos; unsigned int clk_count; struct clk *uart_clk; @@ -431,7 +432,7 @@ mtk8250_set_termios(struct uart_port *port, struct ktermios *termios, static int __maybe_unused mtk8250_runtime_suspend(struct device *dev) { struct mtk8250_data *data = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(data->line); + struct uart_8250_port *up = data->uport; /* wait until UART in idle status */ while @@ -587,9 +588,9 @@ static int mtk8250_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - data->line = serial8250_register_8250_port(&uart); - if (data->line < 0) - return data->line; + data->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(data->uport)) + return PTR_ERR(data->uport); data->rx_wakeup_irq = platform_get_irq_optional(pdev, 1); @@ -605,7 +606,7 @@ static void mtk8250_remove(struct platform_device *pdev) pm_runtime_get_sync(&pdev->dev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(data->uport); pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); @@ -617,7 +618,7 @@ static int __maybe_unused mtk8250_suspend(struct device *dev) int irq = data->rx_wakeup_irq; int err; - serial8250_suspend_port(data->line); + serial8250_suspend_port(data->uport); pinctrl_pm_select_sleep_state(dev); if (irq >= 0) { @@ -627,7 +628,7 @@ static int __maybe_unused mtk8250_suspend(struct device *dev) "failed to enable irq wake on IRQ %d: %d\n", irq, err); pinctrl_pm_select_default_state(dev); - serial8250_resume_port(data->line); + serial8250_resume_port(data->uport); return err; } } @@ -644,7 +645,7 @@ static int __maybe_unused mtk8250_resume(struct device *dev) disable_irq_wake(irq); pinctrl_pm_select_default_state(dev); - serial8250_resume_port(data->line); + serial8250_resume_port(data->uport); return 0; } diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 19f1f51eec47..4935a5d1c1a3 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -102,7 +103,7 @@ #define MOXA_ODD_RS_MASK GENMASK(7, 4) struct mxpcie8250_port { - int line; + struct uart_8250_port *uport; u8 rx_trig_level; }; @@ -572,12 +573,12 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id dev_dbg(dev, "Setup PCI port: port %lx, irq %d, type %d\n", up.port.iobase, up.port.irq, up.port.iotype); - priv->port[i].line = serial8250_register_8250_port(&up); - if (priv->port[i].line < 0) { + priv->port[i].uport = serial8250_register_8250_port(&up); + if (IS_ERR(priv->port[i].uport)) { dev_err(dev, - "Couldn't register serial port %lx, irq %d, type %d, error %d\n", + "Couldn't register serial port %lx, irq %d, type %d, error %pe\n", up.port.iobase, up.port.irq, - up.port.iotype, priv->port[i].line); + up.port.iotype, priv->port[i].uport); break; } priv->port[i].rx_trig_level = MOXA_PUART_RX_TRIG_DEFAULT; @@ -592,7 +593,7 @@ static void mxpcie8250_remove(struct pci_dev *pdev) struct mxpcie8250 *priv = pci_get_drvdata(pdev); for (unsigned int i = 0; i < priv->nr; i++) - serial8250_unregister_port(priv->port[i].line); + serial8250_unregister_port(priv->port[i].uport); } static const struct pci_device_id mxpcie8250_pci_ids[] = { diff --git a/drivers/tty/serial/8250/8250_ni.c b/drivers/tty/serial/8250/8250_ni.c index 9f945f8ed1de..509c1814cc87 100644 --- a/drivers/tty/serial/8250/8250_ni.c +++ b/drivers/tty/serial/8250/8250_ni.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -73,7 +74,7 @@ struct ni16550_device_info { }; struct ni16550_data { - int line; + struct uart_8250_port *uport; struct clk *clk; }; @@ -383,10 +384,9 @@ static int ni16550_probe(struct platform_device *pdev) ni16550_rs485_setup(&uart->port); } - ret = serial8250_register_8250_port(uart); - if (ret < 0) - return ret; - data->line = ret; + data->uport = serial8250_register_8250_port(uart); + if (IS_ERR(data->uport)) + return PTR_ERR(data->uport); platform_set_drvdata(pdev, data); return 0; @@ -396,7 +396,7 @@ static void ni16550_remove(struct platform_device *pdev) { struct ni16550_data *data = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(data->uport); } /* NI 16550 RS-485 Interface */ diff --git a/drivers/tty/serial/8250/8250_of.c b/drivers/tty/serial/8250/8250_of.c index b1561c499acd..b41ca604083f 100644 --- a/drivers/tty/serial/8250/8250_of.c +++ b/drivers/tty/serial/8250/8250_of.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,7 @@ struct of_serial_info { struct clk *bus_clk; struct reset_control *rst; int type; - int line; + struct uart_8250_port *uport; struct notifier_block clk_notifier; }; @@ -71,11 +72,10 @@ static int of_platform_serial_clk_notifier_cb(struct notifier_block *nb, unsigne void *data) { struct of_serial_info *info = clk_nb_to_info(nb); - struct uart_8250_port *port8250 = serial8250_get_port(info->line); struct clk_notifier_data *ndata = data; if (event == POST_RATE_CHANGE) { - serial8250_update_uartclk(&port8250->port, ndata->new_rate); + serial8250_update_uartclk(&info->uport->port, ndata->new_rate); return NOTIFY_OK; } @@ -292,12 +292,13 @@ static int of_platform_serial_probe(struct platform_device *ofdev) &port8250.overrun_backoff_time_ms) != 0) port8250.overrun_backoff_time_ms = 0; - ret = serial8250_register_8250_port(&port8250); - if (ret < 0) + info->uport = serial8250_register_8250_port(&port8250); + if (IS_ERR(info->uport)) { + ret = PTR_ERR(info->uport); goto err_dispose; + } info->type = port_type; - info->line = ret; platform_set_drvdata(ofdev, info); if (info->clk) { @@ -311,7 +312,7 @@ static int of_platform_serial_probe(struct platform_device *ofdev) return 0; err_unregister: - serial8250_unregister_port(info->line); + serial8250_unregister_port(info->uport); err_dispose: pm_runtime_put_sync(&ofdev->dev); pm_runtime_disable(&ofdev->dev); @@ -330,7 +331,7 @@ static void of_platform_serial_remove(struct platform_device *ofdev) if (info->clk) clk_notifier_unregister(info->clk, &info->clk_notifier); - serial8250_unregister_port(info->line); + serial8250_unregister_port(info->uport); reset_control_assert(info->rst); pm_runtime_put_sync(&ofdev->dev); @@ -342,10 +343,10 @@ static void of_platform_serial_remove(struct platform_device *ofdev) static int of_serial_suspend(struct device *dev) { struct of_serial_info *info = dev_get_drvdata(dev); - struct uart_8250_port *port8250 = serial8250_get_port(info->line); + struct uart_8250_port *port8250 = info->uport; struct uart_port *port = &port8250->port; - serial8250_suspend_port(info->line); + serial8250_suspend_port(port8250); if (!uart_console(port) || console_suspend_enabled) { pm_runtime_put_sync(dev); @@ -358,7 +359,7 @@ static int of_serial_suspend(struct device *dev) static int of_serial_resume(struct device *dev) { struct of_serial_info *info = dev_get_drvdata(dev); - struct uart_8250_port *port8250 = serial8250_get_port(info->line); + struct uart_8250_port *port8250 = info->uport; struct uart_port *port = &port8250->port; if (!uart_console(port) || console_suspend_enabled) { @@ -367,7 +368,7 @@ static int of_serial_resume(struct device *dev) clk_prepare_enable(info->clk); } - serial8250_resume_port(info->line); + serial8250_resume_port(port8250); return 0; } diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c index 137175aa4183..29a203ed93fd 100644 --- a/drivers/tty/serial/8250/8250_omap.c +++ b/drivers/tty/serial/8250/8250_omap.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -124,7 +125,7 @@ #define UART_OMAP_TO_H 0x27 struct omap8250_priv { void __iomem *membase; - int line; + struct uart_8250_port *uport; u8 habit; u8 mdr1; u8 mdr3; @@ -628,7 +629,7 @@ static int omap_8250_dma_handle_irq(struct uart_port *port); static irqreturn_t omap8250_irq(int irq, void *dev_id) { struct omap8250_priv *priv = dev_id; - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; struct uart_port *port = &up->port; unsigned int iir, lsr; int ret; @@ -711,8 +712,8 @@ static int omap_8250_startup(struct uart_port *port) struct uart_8250_dma *dma = &priv->omap8250_dma; int ret; - /* ISR and runtime PM need the line; probe sets it only after registration */ - priv->line = port->line; + /* ISR and runtime PM need the uport; probe sets it only after registration */ + priv->uport = up; #ifdef CONFIG_PM up->capabilities |= UART_CAP_RPM; @@ -1509,7 +1510,7 @@ static int omap8250_probe(struct platform_device *pdev) } priv->membase = membase; - priv->line = -ENODEV; + priv->uport = NULL; priv->latency = PM_QOS_CPU_LATENCY_DEFAULT_VALUE; priv->calc_latency = PM_QOS_CPU_LATENCY_DEFAULT_VALUE; cpu_latency_qos_add_request(&priv->pm_qos_request, priv->latency); @@ -1590,12 +1591,13 @@ static int omap8250_probe(struct platform_device *pdev) goto err; } - ret = serial8250_register_8250_port(&up); - if (ret < 0) { + struct uart_8250_port *uport = serial8250_register_8250_port(&up); + if (IS_ERR(uport)) { + ret = PTR_ERR(uport); dev_err(&pdev->dev, "unable to register 8250 port\n"); goto err; } - priv->line = ret; + priv->uport = uport; pm_runtime_mark_last_busy(&pdev->dev); pm_runtime_put_autosuspend(&pdev->dev); @@ -1624,10 +1626,10 @@ static void omap8250_remove(struct platform_device *pdev) if (err) dev_err(&pdev->dev, "Failed to resume hardware\n"); - up = serial8250_get_port(priv->line); + up = priv->uport; omap_8250_shutdown(&up->port); - serial8250_unregister_port(priv->line); - priv->line = -ENODEV; + serial8250_unregister_port(up); + priv->uport = NULL; dev_pm_clear_wake_irq(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); @@ -1659,7 +1661,7 @@ static void omap8250_complete(struct device *dev) static int omap8250_suspend(struct device *dev) { struct omap8250_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; int err = 0; err = omap8250_select_wakeup_pinctrl(dev, priv); @@ -1669,7 +1671,7 @@ static int omap8250_suspend(struct device *dev) return err; } - serial8250_suspend_port(priv->line); + serial8250_suspend_port(up); err = pm_runtime_resume_and_get(dev); if (err) @@ -1687,7 +1689,7 @@ static int omap8250_suspend(struct device *dev) static int omap8250_resume(struct device *dev) { struct omap8250_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; int err; err = pinctrl_select_default_state(dev); @@ -1703,7 +1705,7 @@ static int omap8250_resume(struct device *dev) return err; } - serial8250_resume_port(priv->line); + serial8250_resume_port(up); /* Paired with pm_runtime_resume_and_get() in omap8250_suspend() */ pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); @@ -1775,10 +1777,7 @@ static int omap8250_soft_reset(struct device *dev) static int omap8250_runtime_suspend(struct device *dev) { struct omap8250_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = NULL; - - if (priv->line >= 0) - up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; if (priv->habit & UART_ERRATA_CLOCK_DISABLE) { int ret; @@ -1808,15 +1807,12 @@ static int omap8250_runtime_suspend(struct device *dev) static int omap8250_runtime_resume(struct device *dev) { struct omap8250_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = NULL; + struct uart_8250_port *up = priv->uport; /* Did the hardware wake to a device IO interrupt before a wakeirq? */ if (atomic_read(&priv->active)) return 0; - if (priv->line >= 0) - up = serial8250_get_port(priv->line); - if (up && omap8250_lost_context(up)) { guard(uart_port_lock_irq)(&up->port); omap8250_restore_regs(up); diff --git a/drivers/tty/serial/8250/8250_parisc.c b/drivers/tty/serial/8250/8250_parisc.c index 4ba05a98791c..1bb15cbde655 100644 --- a/drivers/tty/serial/8250/8250_parisc.c +++ b/drivers/tty/serial/8250/8250_parisc.c @@ -5,6 +5,7 @@ * (c) Copyright Matthew Wilcox 2001-2002 */ +#include #include #include #include @@ -22,9 +23,8 @@ static int __init serial_init_chip(struct parisc_device *dev) { - struct uart_8250_port uart; + struct uart_8250_port *uport, uart; unsigned long address; - int err; #if defined(CONFIG_64BIT) && defined(CONFIG_IOSAPIC) if (!dev->irq && (dev->id.sversion == 0xad)) @@ -64,13 +64,13 @@ static int __init serial_init_chip(struct parisc_device *dev) uart.port.flags = UPF_BOOT_AUTOCONF; uart.port.dev = &dev->dev; - err = serial8250_register_8250_port(&uart); - if (err < 0) { + uport = serial8250_register_8250_port(&uart); + if (IS_ERR(uport)) { dev_warn(&dev->dev, - "serial8250_register_8250_port returned error %d\n", - err); + "serial8250_register_8250_port returned error %pe\n", + uport); iounmap(uart.port.membase); - return err; + return PTR_ERR(uport); } return 0; diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 6e53d6d72a8e..1dda63be6b89 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -116,7 +117,7 @@ struct serial_private { unsigned int nr; struct pci_serial_quirk *quirk; const struct pciserial_board *board; - int line[]; + struct uart_8250_port *uport[]; }; #define PCI_DEVICE_ID_HPE_PCI_SERIAL 0x37e @@ -4028,7 +4029,7 @@ pciserial_init_ports(struct pci_dev *dev, const struct pciserial_board *board) nr_ports = rc; } - priv = kzalloc_flex(*priv, line, nr_ports); + priv = kzalloc_flex(*priv, uport, nr_ports); if (!priv) { priv = ERR_PTR(-ENOMEM); goto err_deinit; @@ -4071,12 +4072,12 @@ pciserial_init_ports(struct pci_dev *dev, const struct pciserial_board *board) pci_dbg(dev, "Setup PCI port: port %lx, irq %d, type %d\n", uart.port.iobase, uart.port.irq, uart.port.iotype); - priv->line[i] = serial8250_register_8250_port(&uart); - if (priv->line[i] < 0) { + priv->uport[i] = serial8250_register_8250_port(&uart); + if (IS_ERR(priv->uport[i])) { pci_err(dev, - "Couldn't register serial port %lx, irq %d, type %d, error %d\n", + "Couldn't register serial port %lx, irq %d, type %d, error %pe\n", uart.port.iobase, uart.port.irq, - uart.port.iotype, priv->line[i]); + uart.port.iotype, priv->uport[i]); break; } } @@ -4098,7 +4099,7 @@ static void pciserial_detach_ports(struct serial_private *priv) int i; for (i = 0; i < priv->nr; i++) - serial8250_unregister_port(priv->line[i]); + serial8250_unregister_port(priv->uport[i]); /* * Find the exit quirks. @@ -4120,8 +4121,7 @@ void pciserial_suspend_ports(struct serial_private *priv) int i; for (i = 0; i < priv->nr; i++) - if (priv->line[i] >= 0) - serial8250_suspend_port(priv->line[i]); + serial8250_suspend_port(priv->uport[i]); /* * Ensure that every init quirk is properly torn down @@ -4142,8 +4142,7 @@ void pciserial_resume_ports(struct serial_private *priv) priv->quirk->init(priv->dev); for (i = 0; i < priv->nr; i++) - if (priv->line[i] >= 0) - serial8250_resume_port(priv->line[i]); + serial8250_resume_port(priv->uport[i]); } EXPORT_SYMBOL_GPL(pciserial_resume_ports); diff --git a/drivers/tty/serial/8250/8250_pci1xxxx.c b/drivers/tty/serial/8250/8250_pci1xxxx.c index feeede164886..af4c5c745fa9 100644 --- a/drivers/tty/serial/8250/8250_pci1xxxx.c +++ b/drivers/tty/serial/8250/8250_pci1xxxx.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -152,7 +153,7 @@ struct pci1xxxx_8250 { u8 dev_rev; u8 pad[3]; void __iomem *membase; - int line[] __counted_by(nr); + struct uart_8250_port *uport[] __counted_by(nr); }; static const struct serial_rs485 pci1xxxx_rs485_supported = { @@ -551,9 +552,8 @@ static int pci1xxxx_handle_irq(struct uart_port *port) return 1; } -static bool pci1xxxx_port_suspend(int line) +static bool pci1xxxx_port_suspend(struct uart_8250_port *up) { - struct uart_8250_port *up = serial8250_get_port(line); struct uart_port *port = &up->port; struct tty_port *tport = &port->state->port; unsigned long flags; @@ -578,9 +578,8 @@ static bool pci1xxxx_port_suspend(int line) return ret; } -static void pci1xxxx_port_resume(int line) +static void pci1xxxx_port_resume(struct uart_8250_port *up) { - struct uart_8250_port *up = serial8250_get_port(line); struct uart_port *port = &up->port; struct tty_port *tport = &port->state->port; unsigned long flags; @@ -608,9 +607,9 @@ static int pci1xxxx_suspend(struct device *dev) int i; for (i = 0; i < priv->nr; i++) { - if (priv->line[i] >= 0) { - serial8250_suspend_port(priv->line[i]); - wakeup |= pci1xxxx_port_suspend(priv->line[i]); + if (priv->uport[i]) { + serial8250_suspend_port(priv->uport[i]); + wakeup |= pci1xxxx_port_suspend(priv->uport[i]); } } @@ -661,9 +660,9 @@ static int pci1xxxx_resume(struct device *dev) iounmap(p); for (i = 0; i < priv->nr; i++) { - if (priv->line[i] >= 0) { - pci1xxxx_port_resume(priv->line[i]); - serial8250_resume_port(priv->line[i]); + if (priv->uport[i]) { + pci1xxxx_port_resume(priv->uport[i]); + serial8250_resume_port(priv->uport[i]); } } @@ -779,7 +778,7 @@ static int pci1xxxx_serial_probe(struct pci_dev *pdev, nr_ports = pci1xxxx_get_num_ports(pdev); - priv = devm_kzalloc(dev, struct_size(priv, line, nr_ports), GFP_KERNEL); + priv = devm_kzalloc(dev, struct_size(priv, uport, nr_ports), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -812,8 +811,6 @@ static int pci1xxxx_serial_probe(struct pci_dev *pdev, writeb(UART_PCI_CTRL_SET_MULTIPLE_MSI, priv->membase + UART_PCI_CTRL_REG); for (i = 0; i < nr_ports; i++) { - priv->line[i] = -ENODEV; - port_idx = pci1xxxx_logical_to_physical_port_translate(subsys_dev, i); if (num_vectors == max_vec_reqd) @@ -827,13 +824,15 @@ static int pci1xxxx_serial_probe(struct pci_dev *pdev, continue; } - priv->line[i] = serial8250_register_8250_port(&uart); - if (priv->line[i] < 0) { + struct uart_8250_port *uport = serial8250_register_8250_port(&uart); + if (IS_ERR(uport)) { dev_warn(dev, - "Couldn't register serial port %lx, irq %d, type %d, error %d\n", - uart.port.iobase, uart.port.irq, uart.port.iotype, - priv->line[i]); + "Couldn't register serial port %lx, irq %d, type %d, error %pe\n", + uart.port.iobase, uart.port.irq, uart.port.iotype, uport); + continue; } + + priv->uport[i] = uport; } pci_set_drvdata(pdev, priv); @@ -847,8 +846,8 @@ static void pci1xxxx_serial_remove(struct pci_dev *dev) unsigned int i; for (i = 0; i < priv->nr; i++) { - if (priv->line[i] >= 0) - serial8250_unregister_port(priv->line[i]); + if (priv->uport[i]) + serial8250_unregister_port(priv->uport[i]); } pci_free_irq_vectors(dev); diff --git a/drivers/tty/serial/8250/8250_pericom.c b/drivers/tty/serial/8250/8250_pericom.c index b8d5b7714a9d..bbbf48c6d07d 100644 --- a/drivers/tty/serial/8250/8250_pericom.c +++ b/drivers/tty/serial/8250/8250_pericom.c @@ -2,6 +2,7 @@ /* Driver for Pericom UART */ #include +#include #include #include #include @@ -45,7 +46,7 @@ struct pericom8250 { void __iomem *virt; unsigned int nr; - int line[]; + struct uart_8250_port *uport[]; }; static void pericom_do_set_divisor(struct uart_port *port, unsigned int baud, @@ -102,7 +103,7 @@ static int pericom8250_probe(struct pci_dev *pdev, const struct pci_device_id *i else nr = 1; - pericom = devm_kzalloc(&pdev->dev, struct_size(pericom, line, nr), GFP_KERNEL); + pericom = devm_kzalloc(&pdev->dev, struct_size(pericom, uport, nr), GFP_KERNEL); if (!pericom) return -ENOMEM; @@ -127,12 +128,12 @@ static int pericom8250_probe(struct pci_dev *pdev, const struct pci_device_id *i dev_dbg(&pdev->dev, "Setup PCI port: port %lx, irq %d, type %d\n", uart.port.iobase, uart.port.irq, uart.port.iotype); - pericom->line[i] = serial8250_register_8250_port(&uart); - if (pericom->line[i] < 0) { + pericom->uport[i] = serial8250_register_8250_port(&uart); + if (IS_ERR(pericom->uport[i])) { dev_err(&pdev->dev, - "Couldn't register serial port %lx, irq %d, type %d, error %d\n", + "Couldn't register serial port %lx, irq %d, type %d, error %pe\n", uart.port.iobase, uart.port.irq, - uart.port.iotype, pericom->line[i]); + uart.port.iotype, pericom->uport[i]); break; } } @@ -148,7 +149,7 @@ static void pericom8250_remove(struct pci_dev *pdev) unsigned int i; for (i = 0; i < pericom->nr; i++) - serial8250_unregister_port(pericom->line[i]); + serial8250_unregister_port(pericom->uport[i]); } static const struct pci_device_id pericom8250_pci_ids[] = { diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index 0570366a71d0..9d9dba4e0439 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -109,7 +110,7 @@ static int serial8250_probe_acpi(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *regs; - int ret, line; + int ret; struct uart_8250_port *uart __free(kfree) = kzalloc_obj(*uart); if (!uart) @@ -145,16 +146,12 @@ static int serial8250_probe_acpi(struct platform_device *pdev) if (ret) return ret; - line = serial8250_register_8250_port(uart); - if (line < 0) - return line; - - return 0; + return PTR_ERR_OR_ZERO(serial8250_register_8250_port(uart)); } static int serial8250_probe_platform(struct platform_device *dev, struct plat_serial8250_port *p) { - int ret, i; + int i; struct uart_8250_port *uart __free(kfree) = kzalloc_obj(*uart); if (!uart) @@ -191,12 +188,12 @@ static int serial8250_probe_platform(struct platform_device *dev, struct plat_se if (share_irqs) uart->port.irqflags |= IRQF_SHARED; - ret = serial8250_register_8250_port(uart); - if (ret < 0) { + struct uart_8250_port *uport = serial8250_register_8250_port(uart); + if (IS_ERR(uport)) { dev_err(&dev->dev, "unable to register port at index %d " - "(IO%lx MEM%llx IRQ%d): %d\n", i, + "(IO%lx MEM%llx IRQ%d): %pe\n", i, p->iobase, (unsigned long long)p->mapbase, - p->irq, ret); + p->irq, uport); } } return 0; @@ -238,7 +235,7 @@ static void serial8250_remove(struct platform_device *dev) struct uart_8250_port *up = serial8250_get_port(i); if (up->port.dev == &dev->dev) - serial8250_unregister_port(i); + serial8250_unregister_port(up); } } @@ -264,7 +261,7 @@ static int serial8250_resume(struct platform_device *dev) struct uart_8250_port *up = serial8250_get_port(i); if (up->port.type != PORT_UNKNOWN && up->port.dev == &dev->dev) - serial8250_resume_port(i); + serial8250_resume_port(up); } return 0; diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index a6257cd6e246..f9558df37108 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -8,6 +8,7 @@ * * Ported to the Linux PnP Layer - (C) Adam Belay. */ +#include #include #include #include @@ -436,7 +437,6 @@ serial_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) { struct uart_8250_port uart, *port; int ret, flags = dev_id->driver_data; - long line; if (flags & UNKNOWN_DEV) { ret = serial_pnp_guess_board(dev); @@ -477,39 +477,38 @@ serial_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id) uart.port.iobase, (unsigned long long)uart.port.mapbase, (unsigned long long)uart.port.mapsize, uart.port.irq, uart.port.iotype); - line = serial8250_register_8250_port(&uart); - if (line < 0 || (flags & CIR_PORT)) + port = serial8250_register_8250_port(&uart); + if (IS_ERR(port) || (flags & CIR_PORT)) return -ENODEV; - port = serial8250_get_port(line); if (uart_console(&port->port)) dev->capabilities |= PNP_CONSOLE; - pnp_set_drvdata(dev, (void *)line); + pnp_set_drvdata(dev, port); return 0; } static void serial_pnp_remove(struct pnp_dev *dev) { - long line = (long)pnp_get_drvdata(dev); + struct uart_8250_port *uport = pnp_get_drvdata(dev); dev->capabilities &= ~PNP_CONSOLE; - serial8250_unregister_port(line); + serial8250_unregister_port(uport); } static int serial_pnp_suspend(struct device *dev) { - long line = (long)dev_get_drvdata(dev); + struct uart_8250_port *uport = dev_get_drvdata(dev); - serial8250_suspend_port(line); + serial8250_suspend_port(uport); return 0; } static int serial_pnp_resume(struct device *dev) { - long line = (long)dev_get_drvdata(dev); + struct uart_8250_port *uport = dev_get_drvdata(dev); - serial8250_resume_port(line); + serial8250_resume_port(uport); return 0; } diff --git a/drivers/tty/serial/8250/8250_pxa.c b/drivers/tty/serial/8250/8250_pxa.c index 6dd0190b4843..a56aa5fec563 100644 --- a/drivers/tty/serial/8250/8250_pxa.c +++ b/drivers/tty/serial/8250/8250_pxa.c @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -26,7 +27,7 @@ #include "8250.h" struct pxa8250_data { - int line; + struct uart_8250_port *uport; struct clk *clk; }; @@ -34,7 +35,7 @@ static int __maybe_unused serial_pxa_suspend(struct device *dev) { struct pxa8250_data *data = dev_get_drvdata(dev); - serial8250_suspend_port(data->line); + serial8250_suspend_port(data->uport); return 0; } @@ -43,7 +44,7 @@ static int __maybe_unused serial_pxa_resume(struct device *dev) { struct pxa8250_data *data = dev_get_drvdata(dev); - serial8250_resume_port(data->line); + serial8250_resume_port(data->uport); return 0; } @@ -128,11 +129,11 @@ static int serial_pxa_probe(struct platform_device *pdev) uart.tx_loadsz = 32; uart.dl_write = serial_pxa_dl_write; - ret = serial8250_register_8250_port(&uart); - if (ret < 0) + data->uport = serial8250_register_8250_port(&uart); + if (IS_ERR(data->uport)) { + ret = PTR_ERR(data->uport); goto err_clk; - - data->line = ret; + } platform_set_drvdata(pdev, data); @@ -147,7 +148,7 @@ static void serial_pxa_remove(struct platform_device *pdev) { struct pxa8250_data *data = platform_get_drvdata(pdev); - serial8250_unregister_port(data->line); + serial8250_unregister_port(data->uport); clk_unprepare(data->clk); } diff --git a/drivers/tty/serial/8250/8250_tegra.c b/drivers/tty/serial/8250/8250_tegra.c index 2f3b0075763f..2f6b822f79a8 100644 --- a/drivers/tty/serial/8250/8250_tegra.c +++ b/drivers/tty/serial/8250/8250_tegra.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -20,7 +21,7 @@ struct tegra_uart { struct clk *clk; struct reset_control *rst; - int line; + struct uart_8250_port *uport; }; static void tegra_uart_handle_break(struct uart_port *p) @@ -103,12 +104,13 @@ static int tegra_uart_probe(struct platform_device *pdev) if (ret) goto err_clkdisable; - ret = serial8250_register_8250_port(&port8250); - if (ret < 0) + uart->uport = serial8250_register_8250_port(&port8250); + if (IS_ERR(uart->uport)) { + ret = PTR_ERR(uart->uport); goto err_ctrl_assert; + } platform_set_drvdata(pdev, uart); - uart->line = ret; return 0; @@ -124,7 +126,7 @@ static void tegra_uart_remove(struct platform_device *pdev) { struct tegra_uart *uart = platform_get_drvdata(pdev); - serial8250_unregister_port(uart->line); + serial8250_unregister_port(uart->uport); reset_control_assert(uart->rst); clk_disable_unprepare(uart->clk); } @@ -133,10 +135,10 @@ static void tegra_uart_remove(struct platform_device *pdev) static int tegra_uart_suspend(struct device *dev) { struct tegra_uart *uart = dev_get_drvdata(dev); - struct uart_8250_port *port8250 = serial8250_get_port(uart->line); + struct uart_8250_port *port8250 = uart->uport; struct uart_port *port = &port8250->port; - serial8250_suspend_port(uart->line); + serial8250_suspend_port(port8250); if (!uart_console(port) || console_suspend_enabled) clk_disable_unprepare(uart->clk); @@ -147,13 +149,13 @@ static int tegra_uart_suspend(struct device *dev) static int tegra_uart_resume(struct device *dev) { struct tegra_uart *uart = dev_get_drvdata(dev); - struct uart_8250_port *port8250 = serial8250_get_port(uart->line); + struct uart_8250_port *port8250 = uart->uport; struct uart_port *port = &port8250->port; if (!uart_console(port) || console_suspend_enabled) clk_prepare_enable(uart->clk); - serial8250_resume_port(uart->line); + serial8250_resume_port(port8250); return 0; } diff --git a/drivers/tty/serial/8250/8250_uniphier.c b/drivers/tty/serial/8250/8250_uniphier.c index 7d558b750f83..a8ee45a0d41e 100644 --- a/drivers/tty/serial/8250/8250_uniphier.c +++ b/drivers/tty/serial/8250/8250_uniphier.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,7 @@ #define UNIPHIER_UART_DLR (9 << (UNIPHIER_UART_REGSHIFT)) struct uniphier8250_priv { - int line; + struct uart_8250_port *uport; struct clk *clk; spinlock_t atomic_write_lock; }; @@ -212,11 +213,9 @@ static int uniphier_uart_probe(struct platform_device *pdev) up.dl_read = uniphier_serial_dl_read; up.dl_write = uniphier_serial_dl_write; - ret = serial8250_register_8250_port(&up); - if (ret < 0) - return dev_err_probe(dev, ret, "failed to register 8250 port\n"); - - priv->line = ret; + priv->uport = serial8250_register_8250_port(&up); + if (IS_ERR(priv->uport)) + return dev_err_probe(dev, PTR_ERR(priv->uport), "failed to register 8250 port\n"); platform_set_drvdata(pdev, priv); @@ -227,15 +226,15 @@ static void uniphier_uart_remove(struct platform_device *pdev) { struct uniphier8250_priv *priv = platform_get_drvdata(pdev); - serial8250_unregister_port(priv->line); + serial8250_unregister_port(priv->uport); } static int __maybe_unused uniphier_uart_suspend(struct device *dev) { struct uniphier8250_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; - serial8250_suspend_port(priv->line); + serial8250_suspend_port(up); if (!uart_console(&up->port) || console_suspend_enabled) clk_disable_unprepare(priv->clk); @@ -246,7 +245,7 @@ static int __maybe_unused uniphier_uart_suspend(struct device *dev) static int __maybe_unused uniphier_uart_resume(struct device *dev) { struct uniphier8250_priv *priv = dev_get_drvdata(dev); - struct uart_8250_port *up = serial8250_get_port(priv->line); + struct uart_8250_port *up = priv->uport; int ret; if (!uart_console(&up->port) || console_suspend_enabled) { @@ -255,7 +254,7 @@ static int __maybe_unused uniphier_uart_resume(struct device *dev) return ret; } - serial8250_resume_port(priv->line); + serial8250_resume_port(up); return 0; } diff --git a/drivers/tty/serial/8250/serial_cs.c b/drivers/tty/serial/8250/serial_cs.c index 80483fc7388d..5dcd1544e478 100644 --- a/drivers/tty/serial/8250/serial_cs.c +++ b/drivers/tty/serial/8250/serial_cs.c @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -86,7 +87,7 @@ struct serial_info { int manfid; int prodid; int c950ctrl; - int line[4]; + struct uart_8250_port *uport[4]; const struct serial_quirk *quirk; }; @@ -266,7 +267,7 @@ static void serial_remove(struct pcmcia_device *link) * Recheck to see if the device is still configured. */ for (i = 0; i < info->ndev; i++) - serial8250_unregister_port(info->line[i]); + serial8250_unregister_port(info->uport[i]); if (!info->slave) pcmcia_disable_device(link); @@ -278,7 +279,7 @@ static int serial_suspend(struct pcmcia_device *link) int i; for (i = 0; i < info->ndev; i++) - serial8250_suspend_port(info->line[i]); + serial8250_suspend_port(info->uport[i]); return 0; } @@ -289,7 +290,7 @@ static int serial_resume(struct pcmcia_device *link) int i; for (i = 0; i < info->ndev; i++) - serial8250_resume_port(info->line[i]); + serial8250_resume_port(info->uport[i]); if (info->quirk && info->quirk->wakeup) info->quirk->wakeup(link); @@ -347,7 +348,6 @@ static int setup_serial(struct pcmcia_device *handle, struct serial_info *info, unsigned int iobase, int irq) { struct uart_8250_port uart; - int line; memset(&uart, 0, sizeof(uart)); uart.port.iobase = iobase; @@ -361,14 +361,13 @@ static int setup_serial(struct pcmcia_device *handle, struct serial_info *info, if (info->quirk && info->quirk->setup) info->quirk->setup(handle, &uart); - line = serial8250_register_8250_port(&uart); - if (line < 0) { + info->uport[info->ndev] = serial8250_register_8250_port(&uart); + if (IS_ERR(info->uport[info->ndev])) { pr_err("serial_cs: serial8250_register_8250_port() at 0x%04lx, irq %d failed\n", (unsigned long)iobase, irq); return -EINVAL; } - info->line[info->ndev] = line; info->ndev++; return 0; diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index eeb868e93e15..d91c0477be9d 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -186,10 +186,10 @@ static inline struct uart_8250_port *up_to_u8250p(struct uart_port *up) return container_of(up, struct uart_8250_port, port); } -int serial8250_register_8250_port(const struct uart_8250_port *); -void serial8250_unregister_port(int line); -void serial8250_suspend_port(int line); -void serial8250_resume_port(int line); +struct uart_8250_port *serial8250_register_8250_port(const struct uart_8250_port *); +void serial8250_unregister_port(struct uart_8250_port *); +void serial8250_suspend_port(struct uart_8250_port *); +void serial8250_resume_port(struct uart_8250_port *); int early_serial_setup(struct uart_port *port); int early_serial8250_setup(struct earlycon_device *device, const char *options); From 6ae994d513a6ace5462137e1025a4e7086b87348 Mon Sep 17 00:00:00 2001 From: Ferry Toth Date: Sat, 10 Oct 2026 21:37:56 +0200 Subject: [PATCH 80/91] Revert "serial: 8250_port: Drop duplicate NULL check" This reverts commit 7885af04df6e4efd72910200c1bfc079d61202e4. Commit 7885af04df6e ("serial: 8250_port: Drop duplicate NULL check") added `up->dma = NULL;` to serial8250_release_dma(), with the stated intent of making sure DMA would not be used again after release. However, up->dma is assigned once at probe time. serial8250_do_startup() only calls serial8250_request_dma() when up->dma is non-NULL, so after the first close the pointer stays NULL and DMA is never re-acquired. On Intel Tangier (Edison) with HSU DMA this causes a regression: the first port open uses TX DMA, the second and later opens transmit in PIO mode, producing 64-byte FIFO bursts with ~50us gaps between them. Fixes: 7885af04df6e ("serial: 8250_port: Drop duplicate NULL check") Cc: stable@kernel.org Signed-off-by: Ferry Toth Acked-by: Andy Shevchenko Link: https://patch.msgid.link/20261010193755.187706-2-ftoth@exalondelft.nl Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 09e2c24de695..db066a42f61d 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -2452,8 +2452,8 @@ void serial8250_do_shutdown(struct uart_port *port) synchronize_irq(port->irq); - serial8250_release_dma(up); - up->dma = NULL; + if (up->dma) + serial8250_release_dma(up); scoped_guard(uart_port_lock_irqsave, port) { if (port->flags & UPF_FOURPORT) { From 8a9750209596be35d109de5aa89623b049855c67 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Sun, 11 Oct 2026 22:23:44 +0200 Subject: [PATCH 81/91] serial: amba-pl011: honour delay_rts_before_send for every write While the driver waits out delay_rts_before_send, another write calls pl011_start_tx() again. pl011_rs485_tx_start() then switches straight to SEND, and the data goes out before the delay has passed. Leave the start to the trigger_start_tx timer while it is pending. Fixes: 2c1fd53af21b ("serial: amba-pl011: Fix RTS handling in RS485 mode") Cc: stable@vger.kernel.org Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261011-fix-pl011-rs485-rts-delay-v1-1-5078e61bc408@tipi-net.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index fcfa9f00aec6..cd8cbc967281 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1419,10 +1419,6 @@ static void pl011_rs485_tx_start(struct uart_amba_port *uap) struct uart_port *port = &uap->port; u32 cr; - if (uap->rs485_tx_state == WAIT_AFTER_RTS) { - uap->rs485_tx_state = SEND; - return; - } if (uap->rs485_tx_state == WAIT_AFTER_SEND || uap->rs485_tx_state == WAIT_AFTER_SEND_DELAY) { hrtimer_try_to_cancel(&uap->trigger_stop_tx); @@ -1461,6 +1457,10 @@ static void pl011_start_tx(struct uart_port *port) if ((uap->port.rs485.flags & SER_RS485_ENABLED) && uap->rs485_tx_state != SEND) { + /* trigger_start_tx starts TX once delay_rts_before_send passed */ + if (uap->rs485_tx_state == WAIT_AFTER_RTS) + return; + pl011_rs485_tx_start(uap); if (uap->rs485_tx_state == WAIT_AFTER_RTS) return; @@ -1477,8 +1477,10 @@ static enum hrtimer_restart pl011_trigger_start_tx(struct hrtimer *t) unsigned long flags; uart_port_lock_irqsave(&uap->port, &flags); - if (uap->rs485_tx_state == WAIT_AFTER_RTS) + if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + uap->rs485_tx_state = SEND; pl011_start_tx(&uap->port); + } uart_port_unlock_irqrestore(&uap->port, flags); return HRTIMER_NORESTART; From 171bb8a67cc1af7c88a65961adfb028430e0b783 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 12 Oct 2026 07:53:40 +0200 Subject: [PATCH 82/91] serial: ma35d1: Do not force-enable an unselected console When another console is selected and there is no matching ttyNVT console argument, the core accepts the MA35D1 console without calling its setup function because it has CON_ENABLED set before registration. Its index remains -1 and printk repeatedly reports an invalid port, flooding the console and preventing boot. Let the console core enable it after successful setup. Fixes: 930cbf92db01 ("tty: serial: Add Nuvoton ma35d1 serial driver support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20261012055340.16719-1-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/ma35d1_serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c index 0809655f4395..3be30fb05513 100644 --- a/drivers/tty/serial/ma35d1_serial.c +++ b/drivers/tty/serial/ma35d1_serial.c @@ -639,7 +639,7 @@ static struct console ma35d1serial_console = { .write = ma35d1serial_console_write, .device = uart_console_device, .setup = ma35d1serial_console_setup, - .flags = CON_PRINTBUFFER | CON_ENABLED, + .flags = CON_PRINTBUFFER, .index = -1, .data = &ma35d1serial_reg, }; From 97f85204275ae4d7fb530e451f3acc65c1ec1155 Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Mon, 12 Oct 2026 16:23:50 +0200 Subject: [PATCH 83/91] serial: amba-pl011: keep transmitter enabled when disabling RS485 Disabling RS485 on an open port calls pl011_rs485_tx_stop(), which turns off the transmitter. Only the RS485 TX path turns it back on, so any later output stays queued and tcdrain() hangs until the port is closed. pl011_rs485_tx_stop() also checks only the hardware FIFO. If a DMA transfer has started but has not filled the FIFO yet, the transmitter is turned off under the transfer and the DMA channel never recovers. When leaving RS485 mode, cancel pending deferred starts and stops, leave the transmitter enabled and start any TX that was still waiting for delay_rts_before_send. Fixes: 8d479237727c ("serial: amba-pl011: add RS485 support") Cc: stable@kernel.org Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261012-fix-pl011-rs485-txe-v2-1-e9bf7d218389@tipi-net.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index cd8cbc967281..3edd83ec73de 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2398,8 +2398,31 @@ static int pl011_rs485_config(struct uart_port *port, struct ktermios *termios, struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); - if (port->rs485.flags & SER_RS485_ENABLED) + if ((port->rs485.flags & SER_RS485_ENABLED) && + !(rs485->flags & SER_RS485_ENABLED)) { + bool tx_pending = uap->rs485_tx_state == WAIT_AFTER_RTS; + u32 cr = pl011_read(uap, REG_CR); + + /* + * Leaving RS485 mode, which only enables the transmitter while + * sending. Don't stop it, as that stalls a DMA transfer still + * in flight. Drop pending deferred starts and stops and keep + * it enabled. + */ + hrtimer_try_to_cancel(&uap->trigger_start_tx); + hrtimer_try_to_cancel(&uap->trigger_stop_tx); + uap->rs485_tx_state = OFF; + + cr |= UART011_CR_TXE | UART011_CR_RXE; + pl011_write(cr, uap, REG_CR); + + /* Start TX that was still waiting for delay_rts_before_send */ + if (tx_pending && !uart_tx_stopped(port) && + !pl011_dma_tx_start(uap)) + pl011_start_tx_pio(uap); + } else if (port->rs485.flags & SER_RS485_ENABLED) { pl011_rs485_tx_stop(uap); + } /* Make sure auto RTS is disabled */ if (rs485->flags & SER_RS485_ENABLED) { From f3dc727da3af64abc12b625bbf1eb68d55d882cc Mon Sep 17 00:00:00 2001 From: Tim Douglas Date: Mon, 12 Oct 2026 21:35:40 -0400 Subject: [PATCH 84/91] serial: pmac_zilog: push received data to the port that received it pmz_interrupt() drains both channels but, since commit 2e124b4a390c ("TTY: switch tty_flip_buffer_push"), pushes the flip buffer of the port whose handler is running rather than the port that was drained. With both ports open, data received on the other port can be left in its flip buffer until something else pushes it, so its reader may not see it. Fixes: 2e124b4a390c ("TTY: switch tty_flip_buffer_push") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Tim Douglas Link: https://patch.msgid.link/20261013013541.4535-2-me@timdoug.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/pmac_zilog.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/pmac_zilog.c b/drivers/tty/serial/pmac_zilog.c index e3a919328695..f49d6052d11b 100644 --- a/drivers/tty/serial/pmac_zilog.c +++ b/drivers/tty/serial/pmac_zilog.c @@ -444,7 +444,7 @@ static irqreturn_t pmz_interrupt(int irq, void *dev_id) skip_a: uart_port_unlock(&uap_a->port); if (push) - tty_flip_buffer_push(&uap->port.state->port); + tty_flip_buffer_push(&uap_a->port.state->port); if (!uap_b) goto out; @@ -469,7 +469,7 @@ static irqreturn_t pmz_interrupt(int irq, void *dev_id) skip_b: uart_port_unlock(&uap_b->port); if (push) - tty_flip_buffer_push(&uap->port.state->port); + tty_flip_buffer_push(&uap_b->port.state->port); out: return rc; From 8db79ea07a67b126c773da6efdc00066000daeb8 Mon Sep 17 00:00:00 2001 From: Tim Douglas Date: Mon, 12 Oct 2026 21:35:41 -0400 Subject: [PATCH 85/91] serial: pmac_zilog: report received breaks pmz_status_handle() sets PMACZILOG_FLAG_BREAK from RR0, and pmz_receive_chars() clears it on the null character that ends the break without marking that character, then tests BRK_ABRT in RR1, where the SCC does not set it in asynchronous mode. Breaks therefore arrive as plain NULs: brk is never counted and BRKINT, PARMRK and SysRq never see them. Set BRK_ABRT in r1 for that character, as sunzilog does. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: LLM Signed-off-by: Tim Douglas Link: https://patch.msgid.link/20261013013541.4535-3-me@timdoug.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/pmac_zilog.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/pmac_zilog.c b/drivers/tty/serial/pmac_zilog.c index f49d6052d11b..a612f1db9f18 100644 --- a/drivers/tty/serial/pmac_zilog.c +++ b/drivers/tty/serial/pmac_zilog.c @@ -233,6 +233,7 @@ static bool pmz_receive_chars(struct uart_pmac_port *uap) ch &= uap->parity_mask; if (ch == 0 && uap->flags & PMACZILOG_FLAG_BREAK) { uap->flags &= ~PMACZILOG_FLAG_BREAK; + r1 |= BRK_ABRT; } #if defined(CONFIG_MAGIC_SYSRQ) && defined(CONFIG_SERIAL_CORE_CONSOLE) From 1b60b18a0174c4a14b2efc196c231ea1e6bc26c5 Mon Sep 17 00:00:00 2001 From: Greg Ungerer Date: Tue, 13 Oct 2026 22:06:59 +1000 Subject: [PATCH 86/91] serial: mcf: update author email address The copyright and author email address of gerg@uclinux.org is no longer valid. Change to a current valid email address, gerg@linux-m68k.org. Signed-off-by: Greg Ungerer Link: https://patch.msgid.link/20261013120831.1046590-2-gerg@linux-m68k.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mcf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/mcf.c b/drivers/tty/serial/mcf.c index 93e7dda4d39a..2b862dfe39ab 100644 --- a/drivers/tty/serial/mcf.c +++ b/drivers/tty/serial/mcf.c @@ -4,7 +4,7 @@ /* * mcf.c -- Freescale ColdFire UART driver * - * (C) Copyright 2003-2007, Greg Ungerer + * (C) Copyright 2003-2007, Greg Ungerer */ /****************************************************************************/ @@ -654,7 +654,7 @@ static void __exit mcf_exit(void) module_init(mcf_init); module_exit(mcf_exit); -MODULE_AUTHOR("Greg Ungerer "); +MODULE_AUTHOR("Greg Ungerer "); MODULE_DESCRIPTION("Freescale ColdFire UART driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:mcfuart"); From 1b6d660e038b6ba0db07e0f3413c1857073adbba Mon Sep 17 00:00:00 2001 From: Nicolai Buchwitz Date: Tue, 13 Oct 2026 15:28:44 +0200 Subject: [PATCH 87/91] serial: amba-pl011: drive the bus during break in RS485 mode In RS485 mode the transceiver is enabled only while data is sent. A break set with TIOCSBRK therefore never reaches the bus, and protocols that start each frame with a break, like DMX512, do not work. Handle an active break like a transmission. Enable the transceiver before setting the break, after delay_rts_before_send if one is configured. When the break ends, stop transmitting unless the TX path is still sending data. Signed-off-by: Nicolai Buchwitz Link: https://patch.msgid.link/20261013-devel-pl011-rs485-break-v2-1-31c9f80d3e3d@tipi-net.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 65 ++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 3edd83ec73de..79776a882ae3 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -327,6 +327,7 @@ struct uart_amba_port { char type[12]; ktime_t rs485_tx_drain_interval; /* nano */ enum pl011_rs485_tx_state rs485_tx_state; + bool rs485_break; struct hrtimer trigger_start_tx; struct hrtimer trigger_stop_tx; bool console_line_ended; @@ -1279,6 +1280,11 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) return uap->using_rx_dma && uap->dmarx.running; } +static inline bool pl011_dma_tx_queued(struct uart_amba_port *uap) +{ + return uap->dmatx.queued; +} + #else /* Blank functions if the DMA engine is not available */ static inline void pl011_dma_remove(struct uart_amba_port *uap) @@ -1330,6 +1336,11 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap) return false; } +static inline bool pl011_dma_tx_queued(struct uart_amba_port *uap) +{ + return false; +} + #define pl011_dma_flush_buffer NULL #endif @@ -1357,6 +1368,10 @@ static void pl011_rs485_tx_stop(struct uart_amba_port *uap) { struct uart_port *port = &uap->port; + /* pl011_break_ctl() stops the transmission when the break ends */ + if (uap->rs485_break) + return; + if (uap->rs485_tx_state == SEND) uap->rs485_tx_state = WAIT_AFTER_SEND; @@ -1479,7 +1494,9 @@ static enum hrtimer_restart pl011_trigger_start_tx(struct hrtimer *t) uart_port_lock_irqsave(&uap->port, &flags); if (uap->rs485_tx_state == WAIT_AFTER_RTS) { uap->rs485_tx_state = SEND; - pl011_start_tx(&uap->port); + /* A break can arm the timer while TX is stopped */ + if (!uart_tx_stopped(&uap->port)) + pl011_start_tx(&uap->port); } uart_port_unlock_irqrestore(&uap->port, flags); @@ -1772,16 +1789,60 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) { struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); + struct tty_port *tport = &port->state->port; + bool rs485 = port->rs485.flags & SER_RS485_ENABLED; unsigned long flags; unsigned int lcr_h; uart_port_lock_irqsave(&uap->port, &flags); + + /* + * In RS485 mode the transceiver only drives the bus while + * transmitting. Treat a break as a transmission. + */ + if (rs485 && break_state == -1 && !uap->rs485_break) { + uap->rs485_break = true; + + /* Pending data already enabled the transceiver */ + if (uap->rs485_tx_state != SEND && + uap->rs485_tx_state != WAIT_AFTER_RTS) + pl011_rs485_tx_start(uap); + + /* + * Wait out delay_rts_before_send before starting the break. + * The state stays WAIT_AFTER_RTS, so the TX path behaves as + * without a break. Start it here if trigger_start_tx is late. + */ + if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + uart_port_unlock_irqrestore(&uap->port, flags); + msleep(port->rs485.delay_rts_before_send); + uart_port_lock_irqsave(&uap->port, &flags); + + if (uap->rs485_tx_state == WAIT_AFTER_RTS) { + hrtimer_try_to_cancel(&uap->trigger_start_tx); + uap->rs485_tx_state = SEND; + if (!uart_tx_stopped(port)) + pl011_start_tx(port); + } + } + } + lcr_h = pl011_read(uap, REG_LCRH_TX); if (break_state == -1) lcr_h |= UART01x_LCRH_BRK; else lcr_h &= ~UART01x_LCRH_BRK; pl011_write(lcr_h, uap, REG_LCRH_TX); + + if (break_state == 0 && uap->rs485_break) { + uap->rs485_break = false; + /* An active TX path stops the transmission when it is done */ + if (rs485 && (uart_tx_stopped(port) || + (!pl011_dma_tx_queued(uap) && + kfifo_is_empty(&tport->xmit_fifo)))) + pl011_rs485_tx_stop(uap); + } + uart_port_unlock_irqrestore(&uap->port, flags); } @@ -2102,6 +2163,7 @@ static void pl011_shutdown(struct uart_port *port) hrtimer_cancel(&uap->trigger_stop_tx); uart_port_lock_irq(port); + uap->rs485_break = false; if (uap->rs485_tx_state != OFF) pl011_rs485_tx_stop_now(uap); uart_port_unlock_irq(port); @@ -2398,6 +2460,7 @@ static int pl011_rs485_config(struct uart_port *port, struct ktermios *termios, struct uart_amba_port *uap = container_of(port, struct uart_amba_port, port); + uap->rs485_break = false; if ((port->rs485.flags & SER_RS485_ENABLED) && !(rs485->flags & SER_RS485_ENABLED)) { bool tx_pending = uap->rs485_tx_state == WAIT_AFTER_RTS; From ac04a6daa030f68cf14d485407cbab1d59b5c6fd Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 14 Oct 2026 19:05:46 +0800 Subject: [PATCH 88/91] dt-bindings: serial: snps-dw-apb-uart: Add Synaptics sl2610 uart Add the compatible for the UARTs of the Synaptics SL2610, which are compatible with the Synopsys DesignWare APB UART. Signed-off-by: Jisheng Zhang Acked-by: Conor Dooley Link: https://patch.msgid.link/20261014110546.415-1-jszhang@kernel.org Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/serial/snps-dw-apb-uart.yaml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml index 86dc25919311..e2ca126d58ba 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -34,7 +34,16 @@ allOf: properties: resets: minItems: 2 - else: + + - if: + not: + properties: + compatible: + contains: + enum: + - starfive,jh7110-uart + - syna,sl2610-uart + then: properties: resets: maxItems: 1 @@ -80,6 +89,7 @@ properties: - starfive,jh7100-hsuart - starfive,jh7100-uart - starfive,jh7110-uart + - syna,sl2610-uart - ti,tda54-uart - ultrarisc,dp1000-uart - const: snps,dw-apb-uart From 25f657f7dd36656321c1d769befdad7265f22026 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 14 Oct 2026 16:30:32 +0200 Subject: [PATCH 89/91] serial: core: Use isdigit() in uart_parse_options() Use isdigit() to replace the open-coded implementation. Signed-off-by: Thorsten Blum Reviewed-by: John Ogness Link: https://patch.msgid.link/20261014143034.104950-3-blum@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 9eea8d1a0e8a..c0deec0e6af4 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -2219,7 +2220,7 @@ uart_parse_options(const char *options, int *baud, int *parity, const char *s = options; *baud = simple_strtoul(s, NULL, 10); - while (*s >= '0' && *s <= '9') + while (isdigit(*s)) s++; if (*s) *parity = *s++; From 9105207b0287b5024bd53041ab1f700e152289c6 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 14 Oct 2026 16:30:33 +0200 Subject: [PATCH 90/91] serial: earlycon: Use isdigit() in earlycon_init() Use isdigit() and a while loop to simplify the backward scan. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261014143034.104950-4-blum@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/earlycon.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c index ce740cdc7ceb..05d99ec6e709 100644 --- a/drivers/tty/serial/earlycon.c +++ b/drivers/tty/serial/earlycon.c @@ -11,6 +11,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -59,11 +60,10 @@ static void __init earlycon_init(struct earlycon_device *device, const char *s; size_t len; - /* scan backwards from end of string for first non-numeral */ - for (s = name + strlen(name); - s > name && s[-1] >= '0' && s[-1] <= '9'; - s--) - ; + /* Scan backwards to split the trailing index from the console name. */ + s = name + strlen(name); + while (s > name && isdigit(s[-1])) + s--; if (*s) earlycon->index = simple_strtoul(s, NULL, 10); len = s - name; From 1184f87e2790e8af9be3116f608d2e86b4792d7d Mon Sep 17 00:00:00 2001 From: Jiannan Du Date: Wed, 14 Oct 2026 22:37:23 +0800 Subject: [PATCH 91/91] tty: hvc: dcc: Detect JTAG connection only on CPU core 0 On SMP, migration can occur inside hvc_dcc_check() during device_initcall, so the CPU writing the probe character and the CPU reading the TXfull status may not be the same, which can hang the system. Commit 4419da5d5d4b1788568b ("tty: hvc: dcc: Bind driver to CPU core0 for reads and writes") cached the DCC availability detected on core 0 during console_initcall. However, this is not sufficient: if core 0 does not detect DCC as available and another CPU later calls hvc_dcc_check(), the check is not actually performed on core 0. Since JTAG is not expected to be plugged or unplugged during kernel boot, especially between console_initcall and device_initcall, checking DCC availability multiple times is unnecessary. Cache the return value from hvc_dcc_console_init and reuse it in hvc_dcc_init. This avoids complex SMP-related code and ensures the result is always from core 0. Signed-off-by: Jiannan Du Link: https://patch.msgid.link/tencent_77968AD30F111278CBAE934BCFD0AF00E807@qq.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/hvc/hvc_dcc.c | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/drivers/tty/hvc/hvc_dcc.c b/drivers/tty/hvc/hvc_dcc.c index dfc5c9c38f07..0223b26b5b28 100644 --- a/drivers/tty/hvc/hvc_dcc.c +++ b/drivers/tty/hvc/hvc_dcc.c @@ -29,6 +29,9 @@ static DEFINE_SPINLOCK(dcc_lock); static DEFINE_KFIFO(inbuf, u8, DCC_INBUF_SIZE); static DEFINE_KFIFO(outbuf, u8, DCC_OUTBUF_SIZE); +/* Cached result of hvc_dcc_check() on core 0 */ +static bool dcc_core0_available; + static void dcc_uart_console_putchar(struct uart_port *port, u8 ch) { while (__dcc_getstatus() & DCC_STATUS_TX) @@ -90,36 +93,18 @@ static ssize_t hvc_dcc_get_chars(uint32_t vt, u8 *buf, size_t count) } /* - * Check if the DCC is enabled. If CONFIG_HVC_DCC_SERIALIZE_SMP is enabled, - * then we assume then this function will be called first on core0. That way, - * dcc_core0_available will be true only if it's available on core0. + * Check if the DCC is enabled. This function will be called once on core 0. */ static bool hvc_dcc_check(void) { unsigned long time = jiffies + (HZ / 10); - static bool dcc_core0_available; - - /* - * If we're not on core 0, but we previously confirmed that DCC is - * active, then just return true. - */ - int cpu = get_cpu(); - - if (IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP) && cpu && dcc_core0_available) { - put_cpu(); - return true; - } - - put_cpu(); /* Write a test character to check if it is handled */ __dcc_putchar('\n'); while (time_is_after_jiffies(time)) { - if (!(__dcc_getstatus() & DCC_STATUS_TX)) { - dcc_core0_available = true; + if (!(__dcc_getstatus() & DCC_STATUS_TX)) return true; - } } return false; @@ -265,7 +250,8 @@ static int __init hvc_dcc_console_init(void) { int ret; - if (!hvc_dcc_check()) + dcc_core0_available = hvc_dcc_check(); + if (!dcc_core0_available) return -ENODEV; /* Returns -1 if error */ @@ -279,7 +265,7 @@ static int __init hvc_dcc_init(void) { struct hvc_struct *p; - if (!hvc_dcc_check()) + if (!dcc_core0_available) return -ENODEV; if (IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP)) {