From 3386c50d66759e4e6ddedabc178db3db33836aa6 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Fri, 3 Jul 2026 10:47:14 +0200 Subject: [PATCH 01/97] goldfish: remove unused gf_write_dma_addr() The last user was removed in 2020 by commit c869eaa617e4 ("drivers: staging: retire drivers/staging/goldfish"). Drop it. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20260703084717.176442-1-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- include/linux/goldfish.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/include/linux/goldfish.h b/include/linux/goldfish.h index bcc17f95b906..40a059e03d78 100644 --- a/include/linux/goldfish.h +++ b/include/linux/goldfish.h @@ -26,15 +26,4 @@ static inline void gf_write_ptr(const void *ptr, void __iomem *portl, #endif } -static inline void gf_write_dma_addr(const dma_addr_t addr, - void __iomem *portl, - void __iomem *porth) -{ - gf_iowrite32(lower_32_bits(addr), portl); -#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT - gf_iowrite32(upper_32_bits(addr), porth); -#endif -} - - #endif /* __LINUX_GOLDFISH_H */ From e2dcf364de2c5453d1f594712651a0275b0c1fe5 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Fri, 3 Jul 2026 10:47:15 +0200 Subject: [PATCH 02/97] tty: goldfish: drop unused goldfish_tty::opencount The field was never used. Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20260703084717.176442-2-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/goldfish.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index 12d08de59095..ba060ce4e9b5 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -38,7 +38,6 @@ struct goldfish_tty { spinlock_t lock; void __iomem *base; u32 irq; - int opencount; struct console console; u32 version; struct device *dev; From e31bd02f19ddb01c1e1fb6d79b72ace8f014cb27 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Fri, 3 Jul 2026 10:47:16 +0200 Subject: [PATCH 03/97] tty: goldfish: move gf_write_ptr() to tty/goldfish.c tty/goldfish.c is the only user of gf_write_ptr(). Move it there, drop the unneeded casts, and name it appropriately. FTR, the last non-tty user was removed in 2018 by 4ae0fe70a097 ("Delete the goldfish_nand driver."). Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20260703084717.176442-3-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/goldfish.c | 13 +++++++++++-- include/linux/goldfish.h | 13 ------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index ba060ce4e9b5..fb135bf5996c 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -18,6 +18,7 @@ #include #include #include +#include /* Goldfish tty register's offsets */ #define GOLDFISH_TTY_REG_BYTES_READY 0x04 @@ -49,6 +50,14 @@ static u32 goldfish_tty_line_count = 8; static u32 goldfish_tty_current_line_count; static struct goldfish_tty *goldfish_ttys; +static inline void gf_write_addr(unsigned long addr, void __iomem *portl, void __iomem *porth) +{ + gf_iowrite32(lower_32_bits(addr), portl); +#ifdef CONFIG_64BIT + gf_iowrite32(upper_32_bits(addr), porth); +#endif +} + static void do_rw_io(struct goldfish_tty *qtty, unsigned long address, size_t count, bool is_write) { @@ -56,8 +65,8 @@ static void do_rw_io(struct goldfish_tty *qtty, unsigned long address, void __iomem *base = qtty->base; spin_lock_irqsave(&qtty->lock, irq_flags); - gf_write_ptr((void *)address, base + GOLDFISH_TTY_REG_DATA_PTR, - base + GOLDFISH_TTY_REG_DATA_PTR_HIGH); + gf_write_addr(address, base + GOLDFISH_TTY_REG_DATA_PTR, + base + GOLDFISH_TTY_REG_DATA_PTR_HIGH); gf_iowrite32(count, base + GOLDFISH_TTY_REG_DATA_LEN); if (is_write) diff --git a/include/linux/goldfish.h b/include/linux/goldfish.h index 40a059e03d78..98a4719c6776 100644 --- a/include/linux/goldfish.h +++ b/include/linux/goldfish.h @@ -2,8 +2,6 @@ #ifndef __LINUX_GOLDFISH_H #define __LINUX_GOLDFISH_H -#include -#include #include /* Helpers for Goldfish virtual platform */ @@ -15,15 +13,4 @@ #define gf_iowrite32 iowrite32 #endif -static inline void gf_write_ptr(const void *ptr, void __iomem *portl, - void __iomem *porth) -{ - const unsigned long addr = (unsigned long)ptr; - - gf_iowrite32(lower_32_bits(addr), portl); -#ifdef CONFIG_64BIT - gf_iowrite32(upper_32_bits(addr), porth); -#endif -} - #endif /* __LINUX_GOLDFISH_H */ From e508a176d86f5ca0916ac1caf80806f9ad3d91ef Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Fri, 3 Jul 2026 10:47:17 +0200 Subject: [PATCH 04/97] tty: goldfish: use guard() for locks Using guard()s is cleaner and safer. goldfish_tty_probe() is omitted due to the crossing err_unmap goto-label. Using scoped_guard() does not look that nice there. Perhaps if someone refactored the locked part into a separate function... Signed-off-by: Jiri Slaby (SUSE) Link: https://patch.msgid.link/20260703084717.176442-4-jirislaby@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/goldfish.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/tty/goldfish.c b/drivers/tty/goldfish.c index fb135bf5996c..aace18e5f847 100644 --- a/drivers/tty/goldfish.c +++ b/drivers/tty/goldfish.c @@ -61,10 +61,10 @@ static inline void gf_write_addr(unsigned long addr, void __iomem *portl, void _ static void do_rw_io(struct goldfish_tty *qtty, unsigned long address, size_t count, bool is_write) { - unsigned long irq_flags; void __iomem *base = qtty->base; - spin_lock_irqsave(&qtty->lock, irq_flags); + guard(spinlock_irqsave)(&qtty->lock); + gf_write_addr(address, base + GOLDFISH_TTY_REG_DATA_PTR, base + GOLDFISH_TTY_REG_DATA_PTR_HIGH); gf_iowrite32(count, base + GOLDFISH_TTY_REG_DATA_LEN); @@ -75,8 +75,6 @@ static void do_rw_io(struct goldfish_tty *qtty, unsigned long address, else gf_iowrite32(GOLDFISH_TTY_CMD_READ_BUFFER, base + GOLDFISH_TTY_REG_CMD); - - spin_unlock_irqrestore(&qtty->lock, irq_flags); } static void goldfish_tty_rw(struct goldfish_tty *qtty, unsigned long addr, @@ -418,7 +416,7 @@ static void goldfish_tty_remove(struct platform_device *pdev) { struct goldfish_tty *qtty = platform_get_drvdata(pdev); - mutex_lock(&goldfish_tty_lock); + guard(mutex)(&goldfish_tty_lock); unregister_console(&qtty->console); tty_unregister_device(goldfish_tty_driver, qtty->console.index); @@ -429,7 +427,6 @@ static void goldfish_tty_remove(struct platform_device *pdev) goldfish_tty_current_line_count--; if (goldfish_tty_current_line_count == 0) goldfish_tty_delete_driver(); - mutex_unlock(&goldfish_tty_lock); } #ifdef CONFIG_GOLDFISH_TTY_EARLY_CONSOLE From 0f09902b05062749e4e5a7e4112f35f70b3f2feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Fri, 22 May 2026 16:01:03 +0200 Subject: [PATCH 05/97] serial: 8250_exar: Consistently define pci_device_ids using named initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member of the struct pci_device_id array were initialized by list expressions. This isn't easily readable if you're not into PCI, still more given that it's hidden in macros. Using named initializers is more explicit and thus easier to parse for a human. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct pci_device_id that replaces .driver_data by an anonymous union. Also drop a few explicit zeros that are not needed and improve indention. This change doesn't introduce changes to the compiled pci_device_id array. Tested on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260522140103.769262-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_exar.c | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/tty/serial/8250/8250_exar.c b/drivers/tty/serial/8250/8250_exar.c index c682c0d0dffa..f9a14eaa13cb 100644 --- a/drivers/tty/serial/8250/8250_exar.c +++ b/drivers/tty/serial/8250/8250_exar.c @@ -1642,14 +1642,14 @@ static const struct exar8250_board pbn_exar_XR17V8358 = { .exit = pci_xr17v35x_exit, }; -#define CTI_EXAR_DEVICE(devid, bd) { \ - PCI_DEVICE_SUB( \ - PCI_VENDOR_ID_EXAR, \ - PCI_DEVICE_ID_EXAR_##devid, \ - PCI_SUBVENDOR_ID_CONNECT_TECH, \ - PCI_ANY_ID), 0, 0, \ - (kernel_ulong_t)&bd \ - } +#define CTI_EXAR_DEVICE(devid, bd) { \ + PCI_DEVICE_SUB( \ + PCI_VENDOR_ID_EXAR, \ + PCI_DEVICE_ID_EXAR_##devid, \ + PCI_SUBVENDOR_ID_CONNECT_TECH, \ + PCI_ANY_ID), \ + .driver_data = (kernel_ulong_t)&bd \ +} #define EXAR_DEVICE(vend, devid, bd) { PCI_DEVICE_DATA(vend, devid, &bd) } @@ -1658,18 +1658,18 @@ static const struct exar8250_board pbn_exar_XR17V8358 = { PCI_VENDOR_ID_EXAR, \ PCI_DEVICE_ID_EXAR_##devid, \ PCI_SUBVENDOR_ID_IBM, \ - PCI_SUBDEVICE_ID_IBM_##sdevid), 0, 0, \ - (kernel_ulong_t)&bd \ - } + PCI_SUBDEVICE_ID_IBM_##sdevid), \ + .driver_data = (kernel_ulong_t)&bd \ +} #define USR_DEVICE(devid, sdevid, bd) { \ PCI_DEVICE_SUB( \ PCI_VENDOR_ID_USR, \ PCI_DEVICE_ID_EXAR_##devid, \ PCI_VENDOR_ID_EXAR, \ - PCI_SUBDEVICE_ID_USR_##sdevid), 0, 0, \ - (kernel_ulong_t)&bd \ - } + PCI_SUBDEVICE_ID_USR_##sdevid), \ + .driver_data = (kernel_ulong_t)&bd \ +} static const struct pci_device_id exar_pci_tbl[] = { EXAR_DEVICE(ACCESSIO, COM_2S, pbn_exar_XR17C15x), @@ -1726,7 +1726,7 @@ static const struct pci_device_id exar_pci_tbl[] = { EXAR_DEVICE(COMMTECH, 4224PCI335, pbn_fastcom335_4), EXAR_DEVICE(COMMTECH, 2324PCI335, pbn_fastcom335_4), EXAR_DEVICE(COMMTECH, 2328PCI335, pbn_fastcom335_8), - { 0, } + { } }; MODULE_DEVICE_TABLE(pci, exar_pci_tbl); From addee0be2481e46a94b21742d2520b8c788e037a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Sun, 24 May 2026 15:19:05 +0200 Subject: [PATCH 06/97] tty: serial: rp2: Use named initializer for pci_device_id::driver_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .driver_data member of the struct pci_device_id array were initialized by list expressions relying on hidden assignment of .class and .class_mask in PCI_VDEVICE(). Make the initialization more robust by using a named initializer. This robustness is relevant for a planned change to struct pci_device_id that replaces .driver_data by an anonymous union. This change doesn't introduce changes to the compiled pci_device_id array. Tested on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260524131905.871222-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/rp2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/rp2.c b/drivers/tty/serial/rp2.c index 6d99a02dd439..51e81ec0ffdb 100644 --- a/drivers/tty/serial/rp2.c +++ b/drivers/tty/serial/rp2.c @@ -197,7 +197,7 @@ struct rp2_card { }; #define RP_ID(prod) PCI_VDEVICE(RP, (prod)) -#define RP_CAP(ports, smpte) (((ports) << 8) | ((smpte) << 0)) +#define RP_CAP(ports, smpte) .driver_data = (((ports) << 8) | ((smpte) << 0)) static inline void rp2_decode_cap(const struct pci_device_id *id, int *ports, int *smpte) From 120fc59a20135d0fba20c522997da38e1a8490a6 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 28 May 2026 13:24:17 +0300 Subject: [PATCH 07/97] serial: pch: replace __get_free_page() with kmalloc() pch_uart_init_port() allocates a staging buffer for non-DMA receive path using __get_free_page(). 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. 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 Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260528-b4-tty-v1-1-9da9f7aec5f2@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/pch_uart.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/pch_uart.c b/drivers/tty/serial/pch_uart.c index 80e31c4d9536..5f7f073f285e 100644 --- a/drivers/tty/serial/pch_uart.c +++ b/drivers/tty/serial/pch_uart.c @@ -1662,7 +1662,7 @@ static struct eg20t_port *pch_uart_init_port(struct pci_dev *pdev, if (priv == NULL) goto init_port_alloc_err; - rxbuf = (unsigned char *)__get_free_page(GFP_KERNEL); + rxbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!rxbuf) goto init_port_free_txbuf; @@ -1735,7 +1735,7 @@ static struct eg20t_port *pch_uart_init_port(struct pci_dev *pdev, #ifdef CONFIG_SERIAL_PCH_UART_CONSOLE pch_uart_ports[board->line_no] = NULL; #endif - free_page((unsigned long)rxbuf); + kfree(rxbuf); init_port_free_txbuf: kfree(priv); init_port_alloc_err: @@ -1750,7 +1750,7 @@ static void pch_uart_exit_port(struct eg20t_port *priv) snprintf(name, sizeof(name), "uart%d_regs", priv->port.line); debugfs_lookup_and_remove(name, NULL); uart_remove_one_port(&pch_uart_driver, &priv->port); - free_page((unsigned long)priv->rxbuf.buf); + kfree(priv->rxbuf.buf); } static void pch_uart_pci_remove(struct pci_dev *pdev) From d26ed502d0c7c05c273b6e45a0b7569f55720af6 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 28 May 2026 13:24:18 +0300 Subject: [PATCH 08/97] tty: amiserial: replace get_zeroed_page() with kzalloc() rs_startup() allocates a transmit ring buffer that is used to buffer reads and writes from/to serial data register. 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. 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 Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260528-b4-tty-v1-2-9da9f7aec5f2@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/amiserial.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/tty/amiserial.c b/drivers/tty/amiserial.c index 81eaca751541..28af0fd98181 100644 --- a/drivers/tty/amiserial.c +++ b/drivers/tty/amiserial.c @@ -443,23 +443,23 @@ static int rs_startup(struct tty_struct *tty, struct serial_state *info) struct tty_port *port = &info->tport; unsigned long flags; int retval=0; - unsigned long page; + void *buffer; - page = get_zeroed_page(GFP_KERNEL); - if (!page) + buffer = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buffer) return -ENOMEM; local_irq_save(flags); if (tty_port_initialized(port)) { - free_page(page); + kfree(buffer); goto errout; } if (info->xmit.buf) - free_page(page); + kfree(buffer); else - info->xmit.buf = (unsigned char *) page; + info->xmit.buf = buffer; #ifdef SERIAL_DEBUG_OPEN printk("starting up ttys%d ...", info->line); @@ -537,7 +537,7 @@ static void rs_shutdown(struct tty_struct *tty, struct serial_state *info) */ free_irq(IRQ_AMIGA_VERTB, info); - free_page((unsigned long)info->xmit.buf); + kfree(info->xmit.buf); info->xmit.buf = NULL; info->IER = 0; From 274afb49eeb3ac974d74104a55050990caae6faa Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 28 May 2026 13:24:19 +0300 Subject: [PATCH 09/97] tty: serial: men_z135_uart: replace __get_free_page() with kmalloc() men_z135_probe() allocates a receive staging buffer filled by the CPU via memcpy_fromio() from the device MMIO region. 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. 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 Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260528-b4-tty-v1-3-9da9f7aec5f2@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/men_z135_uart.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/men_z135_uart.c b/drivers/tty/serial/men_z135_uart.c index 6fad57fee912..9138fa29d301 100644 --- a/drivers/tty/serial/men_z135_uart.c +++ b/drivers/tty/serial/men_z135_uart.c @@ -16,6 +16,7 @@ #include #include #include +#include #define MEN_Z135_MAX_PORTS 12 #define MEN_Z135_BASECLK 29491200 @@ -811,7 +812,7 @@ static int men_z135_probe(struct mcb_device *mdev, if (!uart) return -ENOMEM; - uart->rxbuf = (unsigned char *)__get_free_page(GFP_KERNEL); + uart->rxbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!uart->rxbuf) return -ENOMEM; @@ -841,7 +842,7 @@ static int men_z135_probe(struct mcb_device *mdev, return 0; err: - free_page((unsigned long) uart->rxbuf); + kfree(uart->rxbuf); dev_err(dev, "Failed to add UART: %d\n", err); return err; @@ -858,7 +859,7 @@ static void men_z135_remove(struct mcb_device *mdev) line--; uart_remove_one_port(&men_z135_driver, &uart->port); - free_page((unsigned long) uart->rxbuf); + kfree(uart->rxbuf); } static const struct mcb_device_id men_z135_ids[] = { From de96e8b27b82dc2c773ca3f53bae7bad6e233055 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 28 May 2026 13:24:20 +0300 Subject: [PATCH 10/97] vc_screen: replace __get_free_pages() with kmalloc() vcs_read() and vcs_write() allocate staging buffers with __get_free_pages(). These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and it's a modern way of saying "I need a page-sized buffer" Replace use of __get_free_page() with kmalloc() and drop unused now DEFINE_FREE(free_page_ptr ...) Link: https://lore.kernel.org/all/700c5a5f-3128-4671-99aa-827ca73f5cdf@kernel.org Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260528-b4-tty-v1-4-9da9f7aec5f2@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vc_screen.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/tty/vt/vc_screen.c b/drivers/tty/vt/vc_screen.c index 7d40eacc21b3..bf1502fd5bd4 100644 --- a/drivers/tty/vt/vc_screen.c +++ b/drivers/tty/vt/vc_screen.c @@ -53,8 +53,6 @@ #define HEADER_SIZE 4u #define CON_BUF_SIZE (IS_ENABLED(CONFIG_BASE_SMALL) ? 256 : PAGE_SIZE) -DEFINE_FREE(free_page_ptr, void *, if (_T) free_page((unsigned long)_T)); - /* * Our minor space: * @@ -371,7 +369,7 @@ vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) loff_t pos; bool viewed, attr, uni_mode; - char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL); + char *con_buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!con_buf) return -ENOMEM; @@ -596,7 +594,7 @@ vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) if (use_unicode(inode)) return -EOPNOTSUPP; - char *con_buf __free(free_page_ptr) = (char *)__get_free_page(GFP_KERNEL); + char *con_buf __free(kfree) = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!con_buf) return -ENOMEM; From 25b51d1fd3268a219e43608b165098fff7cd9dcd Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Mon, 15 Jun 2026 10:27:35 +0000 Subject: [PATCH 11/97] serial: max310x: register GPIO controller before adding UART ports The MAX310x exposes four GPIOs per UART port via an in-driver gpio_chip. devm_gpiochip_add_data() used to run after the per-port uart_add_one_port() loop, so a device-tree consumer referencing one of the chip's own GPIOs (for example rs485-term-gpios = <&max310x 0 ...>) could not resolve it during port registration: the GPIO provider it waits for is the very driver still trying to register, and the lookup returns -EPROBE_DEFER on its own provider, deferring probe forever. Split the per-port setup into two passes around the gpio_chip registration: 1. Initialise per-port state - port struct fields, regmap binding, IRQ disable, work queues. The gpio_chip callbacks dereference s->p[i].regmap via to_max310x_port() and become callable as soon as the chip is visible to gpiolib, so every entry must be populated first. 2. devm_gpiochip_add_data() - register the gpio_chip. 3. Allocate a line, uart_add_one_port(), set_bit(), max310x_power(). Keeping line allocation, registration and set_bit() together preserves the existing "bit set <=> port registered" rollback invariant that out_uart relies on. Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20260615-b4-max310x-rs485-dt-v3-1-7e79f064bdd7@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 54 ++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index e28e3065c99d..3e26bdf8806b 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1390,17 +1390,12 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty dev_dbg(dev, "Reference clock set to %i Hz\n", uartclk); + /* + * Set up each port's state before registering the gpiochip, + * since the gpiochip callbacks will read s->p[i].regmap as + * soon as gpiolib exposes the controller. + */ for (i = 0; i < devtype->nr; i++) { - unsigned int line; - - line = find_first_zero_bit(max310x_lines, MAX310X_UART_NRMAX); - if (line == MAX310X_UART_NRMAX) { - ret = -ERANGE; - goto out_uart; - } - - /* Initialize port data */ - s->p[i].port.line = line; s->p[i].port.dev = dev; s->p[i].port.irq = irq; s->p[i].port.type = PORT_MAX310X; @@ -1430,20 +1425,16 @@ 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); - - /* Register port */ - ret = uart_add_one_port(&max310x_uart, &s->p[i].port); - if (ret) - goto out_uart; - - set_bit(line, max310x_lines); - - /* Go to suspend mode */ - max310x_power(&s->p[i].port, 0); } #ifdef CONFIG_GPIOLIB - /* Setup GPIO controller */ + /* + * Register the GPIO controller before adding the UART ports so + * that consumers referencing the chip's own GPIOs from device + * tree (for example rs485-term-gpios = <&max310x ...>) can + * resolve them at uart_add_one_port() time instead of receiving + * -EPROBE_DEFER from their own provider. + */ s->gpio.owner = THIS_MODULE; s->gpio.parent = dev; s->gpio.label = devtype->name; @@ -1460,6 +1451,27 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty goto out_uart; #endif + for (i = 0; i < devtype->nr; i++) { + unsigned int line; + + line = find_first_zero_bit(max310x_lines, MAX310X_UART_NRMAX); + if (line == MAX310X_UART_NRMAX) { + ret = -ERANGE; + goto out_uart; + } + s->p[i].port.line = line; + + /* Register port */ + ret = uart_add_one_port(&max310x_uart, &s->p[i].port); + if (ret) + goto out_uart; + + set_bit(line, max310x_lines); + + /* Go to suspend mode */ + max310x_power(&s->p[i].port, 0); + } + /* Setup interrupt */ ret = devm_request_threaded_irq(dev, irq, NULL, max310x_ist, IRQF_ONESHOT | IRQF_SHARED, dev_name(dev), s); From f3fd73bf208b622be36b923346e1fa7a69d88a1b Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Mon, 15 Jun 2026 10:27:36 +0000 Subject: [PATCH 12/97] dt-bindings: serial: maxim,max310x: describe per-channel rs485 subnodes The MAX310x is a family of one- (max3107, max3108), two- (max3109) and four-channel (max14830) UARTs. The binding pulls in /schemas/serial/rs485.yaml at the chip level, describing a single set of RS-485 properties - enough for the single-channel parts, but a multi-channel chip can wire RS-485 differently on each channel. Split the binding per compatible: - single-channel parts (max3107, max3108): the chip node is itself the serial port and carries the RS-485 properties, as before; - multi-channel parts (max3109, max14830): the chip node is only a container and is no longer a serial node; each channel is a "serial@N" subnode that carries the standard serial.yaml/rs485.yaml properties (and may host a serial slave device). max3109 has channels 0-1, max14830 has 0-3. This avoids a chip node that is simultaneously a serial node and the parent of serial nodes. The driver still reads chip-level RS-485 for single-channel and legacy device trees, so existing users are unaffected. Signed-off-by: Tapio Reijonen Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260615-b4-max310x-rs485-dt-v3-2-7e79f064bdd7@vaisala.com Signed-off-by: Greg Kroah-Hartman --- .../bindings/serial/maxim,max310x.yaml | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/serial/maxim,max310x.yaml b/Documentation/devicetree/bindings/serial/maxim,max310x.yaml index 889eeaca64a0..e598dda4d13f 100644 --- a/Documentation/devicetree/bindings/serial/maxim,max310x.yaml +++ b/Documentation/devicetree/bindings/serial/maxim,max310x.yaml @@ -9,6 +9,13 @@ title: Maxim MAX310X Advanced Universal Asynchronous Receiver-Transmitter (UART) maintainers: - Hugo Villeneuve +description: + The MAX310X is a family of SPI/I2C UARTs with one (max3107, max3108), + two (max3109) or four (max14830) channels. Single-channel parts are + described as a serial node with RS-485 properties on the chip node; + multi-channel parts use one "serial@N" child node per channel, each + carrying its own serial/RS-485 properties. + properties: compatible: enum: @@ -49,8 +56,55 @@ required: allOf: - $ref: /schemas/spi/spi-peripheral-props.yaml# - - $ref: /schemas/serial/serial.yaml# - - $ref: /schemas/serial/rs485.yaml# + + - if: + properties: + compatible: + contains: + enum: + - maxim,max3107 + - maxim,max3108 + then: + allOf: + - $ref: /schemas/serial/serial.yaml# + - $ref: /schemas/serial/rs485.yaml# + + - if: + properties: + compatible: + contains: + enum: + - maxim,max3109 + - maxim,max14830 + then: + properties: + "#address-cells": + const: 1 + "#size-cells": + const: 0 + patternProperties: + "^serial@[0-3]$": + type: object + description: A single UART channel of the chip. + allOf: + - $ref: /schemas/serial/serial.yaml# + - $ref: /schemas/serial/rs485.yaml# + properties: + reg: + description: UART channel number on the chip. + maximum: 3 + required: + - reg + unevaluatedProperties: false + + - if: + properties: + compatible: + contains: + const: maxim,max3109 + then: + patternProperties: + "^serial@[23]$": false unevaluatedProperties: false @@ -70,5 +124,39 @@ examples: interrupts = <7 IRQ_TYPE_LEVEL_LOW>; gpio-controller; #gpio-cells = <2>; + rs485-rts-active-low; + linux,rs485-enabled-at-boot-time; + }; + }; + + - | + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + compatible = "maxim,max14830"; + reg = <0>; + spi-max-frequency = <26000000>; + clocks = <&xtal4m>; + clock-names = "xtal"; + interrupt-parent = <&gpio3>; + interrupts = <7 IRQ_TYPE_LEVEL_LOW>; + gpio-controller; + #gpio-cells = <2>; + #address-cells = <1>; + #size-cells = <0>; + + serial@0 { + reg = <0>; + rs485-rts-active-low; + linux,rs485-enabled-at-boot-time; + }; + + serial@2 { + reg = <2>; + rs485-rts-active-low; + }; }; }; From bed094602ffb5c90fe761977c44653968809ebf3 Mon Sep 17 00:00:00 2001 From: Tapio Reijonen Date: Mon, 15 Jun 2026 10:27:37 +0000 Subject: [PATCH 13/97] serial: max310x: honour rs485 properties from per-channel DT subnode The MAX310x DT binding pulls in /schemas/serial/rs485.yaml via its allOf list, advertising the rs485-* properties defined there - none of which were honoured at runtime, because the driver never called uart_get_rs485_mode(). All channels share the parent SPI/I2C device, so uart_get_rs485_mode() called directly on each port would read the same chip-level fwnode for every call. Walk dev->of_node's children for the "serial@N" subnode with matching reg, and temporarily retarget the parent device's fwnode while uart_get_rs485_mode() runs, so each channel picks up its own subnode's properties. Probe is serialised, so the swap is safe. For single-channel variants (max3107, max3108), fall back to the chip's own fwnode when no subnode is present, so existing DTs that declare rs485 properties at the top level keep working. Signed-off-by: Tapio Reijonen Link: https://patch.msgid.link/20260615-b4-max310x-rs485-dt-v3-3-7e79f064bdd7@vaisala.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 3e26bdf8806b..022502986c5f 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1452,6 +1452,9 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty #endif for (i = 0; i < devtype->nr; i++) { + struct fwnode_handle *saved_fwnode = dev_fwnode(dev); + struct device_node *port_np = NULL; + struct device_node *child; unsigned int line; line = find_first_zero_bit(max310x_lines, MAX310X_UART_NRMAX); @@ -1461,6 +1464,40 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty } s->p[i].port.line = line; + /* Locate the matching "serial@i" DT subnode, if any. */ + for_each_available_child_of_node(dev->of_node, child) { + u32 reg; + + if (!of_node_name_eq(child, "serial")) + continue; + if (of_property_read_u32(child, "reg", ®)) + continue; + if (reg == i) { + port_np = child; + break; + } + } + + /* + * Temporarily retarget dev's fwnode to the per-port subnode + * so uart_get_rs485_mode() picks up the per-port properties. + * For single-port variants, fall back to the chip's own + * fwnode so legacy DTs that declare rs485 properties at the + * top level keep working. + */ + if (port_np) { + device_set_node(dev, of_fwnode_handle(port_np)); + ret = uart_get_rs485_mode(&s->p[i].port); + device_set_node(dev, saved_fwnode); + of_node_put(port_np); + if (ret) + goto out_uart; + } else if (devtype->nr == 1) { + ret = uart_get_rs485_mode(&s->p[i].port); + if (ret) + goto out_uart; + } + /* Register port */ ret = uart_add_one_port(&max310x_uart, &s->p[i].port); if (ret) From 7a52545d37eb805eb1f3c7e03ff336b12c12f5af Mon Sep 17 00:00:00 2001 From: John de la Garza Date: Tue, 30 Jun 2026 20:27:17 -0700 Subject: [PATCH 14/97] tty: tty_jobctrl: use guard()s in tiocspgrp() Convert the manual spin_lock_irq()/rcu_read_lock() nesting in tiocspgrp() to scoped guard()s. This drops the out_unlock and out_unlock_ctrl labels and lets the error paths return directly. No functional change intended. Signed-off-by: John de la Garza Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260701030139.3189030-1-john@jjdev.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_jobctrl.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/drivers/tty/tty_jobctrl.c b/drivers/tty/tty_jobctrl.c index ef8741c3e662..583e2412a47b 100644 --- a/drivers/tty/tty_jobctrl.c +++ b/drivers/tty/tty_jobctrl.c @@ -506,29 +506,23 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t if (pgrp_nr < 0) return -EINVAL; - spin_lock_irq(&real_tty->ctrl.lock); + guard(spinlock_irq)(&real_tty->ctrl.lock); if (!current->signal->tty || (current->signal->tty != real_tty) || - (real_tty->ctrl.session != task_session(current))) { - retval = -ENOTTY; - goto out_unlock_ctrl; - } - rcu_read_lock(); + (real_tty->ctrl.session != task_session(current))) + return -ENOTTY; + + guard(rcu)(); pgrp = find_vpid(pgrp_nr); - retval = -ESRCH; if (!pgrp) - goto out_unlock; - retval = -EPERM; + return -ESRCH; if (session_of_pgrp(pgrp) != task_session(current)) - goto out_unlock; - retval = 0; + return -EPERM; + put_pid(real_tty->ctrl.pgrp); real_tty->ctrl.pgrp = get_pid(pgrp); -out_unlock: - rcu_read_unlock(); -out_unlock_ctrl: - spin_unlock_irq(&real_tty->ctrl.lock); - return retval; + + return 0; } /** From 9505146e885b1a842118aa6410f737290c4a5a32 Mon Sep 17 00:00:00 2001 From: John de la Garza Date: Wed, 1 Jul 2026 16:12:15 -0700 Subject: [PATCH 15/97] tty: tty_jobctrl: use guard() in tiocgsid() guard()s express more clearly what the lock protects and let the function return immediately instead of jumping to an unlock label. Signed-off-by: John de la Garza Reviewed-by: Jiri Slaby Link: https://patch.msgid.link/20260701231215.4092457-1-john@jjdev.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_jobctrl.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/tty/tty_jobctrl.c b/drivers/tty/tty_jobctrl.c index 583e2412a47b..37929fb56174 100644 --- a/drivers/tty/tty_jobctrl.c +++ b/drivers/tty/tty_jobctrl.c @@ -536,7 +536,6 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t */ static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) { - unsigned long flags; pid_t sid; /* @@ -546,17 +545,13 @@ static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t _ if (tty == real_tty && current->signal->tty != real_tty) return -ENOTTY; - spin_lock_irqsave(&real_tty->ctrl.lock, flags); - if (!real_tty->ctrl.session) - goto err; - sid = pid_vnr(real_tty->ctrl.session); - spin_unlock_irqrestore(&real_tty->ctrl.lock, flags); + scoped_guard(spinlock_irqsave, &real_tty->ctrl.lock) { + if (!real_tty->ctrl.session) + return -ENOTTY; + sid = pid_vnr(real_tty->ctrl.session); + } return put_user(sid, p); - -err: - spin_unlock_irqrestore(&real_tty->ctrl.lock, flags); - return -ENOTTY; } /* From 4d105880666ab7f7914a75716d3e95b0d8b879dc Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 29 May 2026 23:10:25 -0700 Subject: [PATCH 16/97] tty: serial: mpc52xx_uart: add bounds check for psc_num array index psc_num is derived from port->mapbase bits 11:8, giving a range of 0-15, but the psc_mclk_clk and psc_ipg_clk arrays are sized to MPC52xx_PSC_MAXNUM (12 when CONFIG_PPC_MPC512x is set). A malformed device tree with bits 11:8 >= 12 would cause out-of-bounds writes in mpc512x_psc_alloc_clock() and out-of-bounds reads/writes in mpc512x_psc_relse_clock() and mpc512x_psc_endis_clock(). The same unchecked index also appears in mpc512x_psc_handle_irq(). Add ARRAY_SIZE() bounds checks to all four functions before using psc_num as an array index. Assisted-by: Opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260530061025.11625-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mpc52xx_uart.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tty/serial/mpc52xx_uart.c b/drivers/tty/serial/mpc52xx_uart.c index 37eb701b0b46..b566206f42a2 100644 --- a/drivers/tty/serial/mpc52xx_uart.c +++ b/drivers/tty/serial/mpc52xx_uart.c @@ -645,6 +645,8 @@ static irqreturn_t mpc512x_psc_handle_irq(struct uart_port *port) /* Check if it is an interrupt for this port */ psc_num = (port->mapbase & 0xf00) >> 8; + if (psc_num >= ARRAY_SIZE(psc_mclk_clk)) + return IRQ_NONE; if (test_bit(psc_num, &fifoc_int) || test_bit(psc_num + 16, &fifoc_int)) return mpc5xxx_uart_process_int(port); @@ -663,6 +665,8 @@ static int mpc512x_psc_alloc_clock(struct uart_port *port) int err; psc_num = (port->mapbase & 0xf00) >> 8; + if (psc_num >= ARRAY_SIZE(psc_mclk_clk)) + return -EINVAL; clk = devm_clk_get(port->dev, "mclk"); if (IS_ERR(clk)) { @@ -711,6 +715,8 @@ static void mpc512x_psc_relse_clock(struct uart_port *port) struct clk *clk; psc_num = (port->mapbase & 0xf00) >> 8; + if (psc_num >= ARRAY_SIZE(psc_mclk_clk)) + return; clk = psc_mclk_clk[psc_num]; if (clk) { clk_disable_unprepare(clk); @@ -733,6 +739,8 @@ static int mpc512x_psc_endis_clock(struct uart_port *port, int enable) return 0; psc_num = (port->mapbase & 0xf00) >> 8; + if (psc_num >= ARRAY_SIZE(psc_mclk_clk)) + return -ENODEV; psc_clk = psc_mclk_clk[psc_num]; if (!psc_clk) { dev_err(port->dev, "Failed to get PSC clock entry!\n"); From d338ab1d90603f875c4f7ed223406535378173a5 Mon Sep 17 00:00:00 2001 From: Fushuai Wang Date: Fri, 22 May 2026 18:10:42 +0800 Subject: [PATCH 17/97] serial: 8250: Clear CON_PRINTBUFFER on port re-registration When two PnP devices map to the same physical port, the serial8250 driver removes and re-registers the console structure for the same port. During re-registration, the console structure still has CON_PRINTBUFFER set from the initial registration, which causes console_init_seq() to set console->seq to syslog_seq. This results in re-printing the entire system log buffer, which may lead to RCU stall on slow serial consoles. Clear CON_PRINTBUFFER when re-registering a port to prevent duplicate log printing. Fixes: 835d844d1a28 ("8250_pnp: do pnp probe before legacy probe") Suggested-by: Greg Kroah-Hartman Signed-off-by: Fushuai Wang Link: https://patch.msgid.link/20260522101042.21976-1-fushuai.wang@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index f49862d90eeb..c0e8a4efbdcc 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -720,8 +720,12 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) /* Preserve specified console flow control. */ cons_flow = uart_cons_flow_enabled(&uart->port); - if (uart->port.dev) + if (uart->port.dev) { + if (uart_console(&uart->port)) + uart->port.cons->flags &= ~CON_PRINTBUFFER; + uart_remove_one_port(&serial8250_reg, &uart->port); + } uart->port.ctrl_id = up->port.ctrl_id; uart->port.port_id = up->port.port_id; From 831603b3e8aa3892de229fda846b2f859c518699 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sat, 6 Jun 2026 19:11:17 -0700 Subject: [PATCH 18/97] serial: cpm_uart: replace irq_of_parse_and_map with platform_get_irq platform_get_irq is a newer API for this that does not require irq_dispose_mapping(). Assisted-by: opencode:big-pickle Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260607021117.6325-1-rosenp@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/cpm_uart.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/cpm_uart.c b/drivers/tty/serial/cpm_uart.c index b778a20ec9b1..39f54bb7b485 100644 --- a/drivers/tty/serial/cpm_uart.c +++ b/drivers/tty/serial/cpm_uart.c @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -1530,16 +1529,14 @@ static int cpm_uart_probe(struct platform_device *ofdev) /* initialize the device pointer for the port */ pinfo->port.dev = &ofdev->dev; - pinfo->port.irq = irq_of_parse_and_map(ofdev->dev.of_node, 0); - if (!pinfo->port.irq) - return -EINVAL; + pinfo->port.irq = platform_get_irq(ofdev, 0); + if (pinfo->port.irq < 0) + return pinfo->port.irq; ret = cpm_uart_init_port(ofdev->dev.of_node, pinfo); if (!ret) return uart_add_one_port(&cpm_reg, &pinfo->port); - irq_dispose_mapping(pinfo->port.irq); - return ret; } From 31e11af34e2c7cc61e25533ab877b1f9cc3eb528 Mon Sep 17 00:00:00 2001 From: Rahul Bukte Date: Wed, 10 Jun 2026 14:41:30 +0900 Subject: [PATCH 19/97] serial: 8250: force synchronous probe for the ISA and PNP drivers On x86_64 defconfig, booting with driver_async_probe=serial hangs in early init. The 8250 PNP driver is put onto the async probe pool. serial8250_register_8250_port() runs in a kworker concurrently with the ISA registration done from the serial8250_init() initcall resulting in a deadlock or NULL dereference. - Deadlock: serial_core_register_port() holds port_mutex across serial_core_add_one_port() uart_configure_port() autoconfig_irq() probe_irq_on() async_synchronize_full(), which waits for the async probe pool to drain. The async PNP worker reaches the "port already in use" check and tries to unregister it. serial8250_register_8250_port() uart_remove_one_port() serial_core_unregister_port() This blocks on port_mutex. The init thread waits for the worker and the worker waits for the init thread. - NULL deref: when the worker instead observes a slot whose port.dev is set but whose port_dev has not yet been populated, it hits the null pointer on the call to serial_core_get_ctrl_dev() in serial_core_unregister_port(). Signed-off-by: Rahul Bukte Link: https://patch.msgid.link/20260610054130.2825182-1-rahul.bukte@sony.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_platform.c | 1 + drivers/tty/serial/8250/8250_pnp.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/tty/serial/8250/8250_platform.c b/drivers/tty/serial/8250/8250_platform.c index ad3a7bc31d6f..af946d12e764 100644 --- a/drivers/tty/serial/8250/8250_platform.c +++ b/drivers/tty/serial/8250/8250_platform.c @@ -284,6 +284,7 @@ static struct platform_driver serial8250_isa_driver = { .driver = { .name = "serial8250", .acpi_match_table = acpi_platform_serial_table, + .probe_type = PROBE_FORCE_SYNCHRONOUS, }, }; diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index 7a837fdf9df1..3f41a9d6cb27 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -521,6 +521,7 @@ static struct pnp_driver serial_pnp_driver = { .remove = serial_pnp_remove, .driver = { .pm = pm_sleep_ptr(&serial_pnp_pm_ops), + .probe_type = PROBE_FORCE_SYNCHRONOUS, }, .id_table = pnp_dev_table, }; From a9b2c446e36f60e540ec29f756bbb5a1de37811a Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 15 Jun 2026 19:46:52 +0530 Subject: [PATCH 20/97] serial: qcom-geni: trace: Drop redundant len field from geni_serial_data The dynamic array stored in the ring buffer already carries its own length in the array metadata. There is no need to also store it as a separate scalar field in the entry struct. Drop __field(unsigned int, len) and the corresponding __entry->len assignment, and use __get_dynamic_array_len(data) in the TP_printk for both the len=%u format argument and the __print_hex() size argument. This saves 4 bytes per event on the ring buffer. Signed-off-by: Praveen Talari Suggested-by: Steven Rostedt Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260615-add-tracepoints-for-qcom-geni-serial-v5-1-2efa4c97e0e2@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- include/trace/events/qcom_geni_serial.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/trace/events/qcom_geni_serial.h b/include/trace/events/qcom_geni_serial.h index 417ec01f9fc8..e1aa551d525e 100644 --- a/include/trace/events/qcom_geni_serial.h +++ b/include/trace/events/qcom_geni_serial.h @@ -97,18 +97,17 @@ DECLARE_EVENT_CLASS(geni_serial_data, TP_ARGS(dev, buf, len), TP_STRUCT__entry(__string(name, dev_name(dev)) - __field(unsigned int, len) __dynamic_array(u8, data, len) ), TP_fast_assign(__assign_str(name); - __entry->len = len; memcpy(__get_dynamic_array(data), buf, len); ), TP_printk("%s: len=%u data=%s", - __get_str(name), __entry->len, - __print_hex(__get_dynamic_array(data), __entry->len)) + __get_str(name), __get_dynamic_array_len(data), + __print_hex(__get_dynamic_array(data), + __get_dynamic_array_len(data))) ); DEFINE_EVENT(geni_serial_data, geni_serial_tx_data, From 970ede67de0e3f6a1e707ec99ae49d80bf3f2966 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Tue, 23 Jun 2026 22:05:39 +0800 Subject: [PATCH 21/97] tty: serial: pch_uart: add check for pci_get_slot() Add check for pci_get_slot() to prevent a potetial null pointer dereference in pch_request_dma(). Signed-off-by: Haoxiang Li Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260623140539.2272473-1-haoxiang_li2024@163.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/pch_uart.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/tty/serial/pch_uart.c b/drivers/tty/serial/pch_uart.c index 5f7f073f285e..db5c62b0322f 100644 --- a/drivers/tty/serial/pch_uart.c +++ b/drivers/tty/serial/pch_uart.c @@ -678,6 +678,11 @@ static void pch_request_dma(struct uart_port *port) /* Get DMA's dev information */ dma_dev = pci_get_slot(priv->pdev->bus, PCI_DEVFN(PCI_SLOT(priv->pdev->devfn), 0)); + if (!dma_dev) { + dev_err(priv->port.dev, "%s: failed to get DMA device\n", + __func__); + return; + } /* Set Tx DMA */ param = &priv->param_tx; From 383f139f191e4d12343f7153880495c46042dd37 Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 8 Jun 2026 10:54:54 +0100 Subject: [PATCH 22/97] drivers/tty/serial/kgdboc: Use strscpy() to copy strings into arrays Replacing strcpy() with strscpy() ensures that overflow of the target buffer cannot happen. Signed-off-by: David Laight Reviewed-by: Daniel Thompson (RISCstar) Link: https://patch.msgid.link/20260608095523.2606-10-david.laight.linux@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 5a955c80a853..09648d643897 100644 --- a/drivers/tty/serial/kgdboc.c +++ b/drivers/tty/serial/kgdboc.c @@ -363,7 +363,7 @@ static int param_set_kgdboc_var(const char *kmessage, mutex_lock(&config_mutex); - strcpy(config, kmessage); + strscpy(config, kmessage); /* Chop out \n char as a result of echo */ if (len && config[len - 1] == '\n') config[len - 1] = '\0'; From 7a68b818d56e5c48b90232d59148ef8e716082ae Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 17 Jun 2026 11:25:15 +0200 Subject: [PATCH 23/97] serdev: acpi: Free resource list at appropriate time We do unneeded "double free" (emptying an empty list) in one case. This is not a critical issue at all, the fix just makes code robust against any possible future changes in the flow. Signed-off-by: Andy Shevchenko Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260617092515.2649521-1-andriy.shevchenko@linux.intel.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serdev/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index e9d044a331b0..7500efcdfc21 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -651,11 +651,11 @@ static int acpi_serdev_do_lookup(struct acpi_device *adev, INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_resources(adev, &resource_list, acpi_serdev_parse_resource, lookup); - acpi_dev_free_resource_list(&resource_list); - if (ret < 0) return -EINVAL; + acpi_dev_free_resource_list(&resource_list); + return 0; } From 4f643bef0984b504adfb4d979ba5068e4d63485a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Mon, 29 Jun 2026 19:04:09 +0200 Subject: [PATCH 24/97] serial: 8250_pnp: Use named initializers for pnp_device_id array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While being less compact, using named initializers allows to more easily see which members of the structs are assigned which value without having to lookup the declaration of the struct. And it's also more robust against changes to the struct definition. The mentioned robustness is relevant for a planned change to struct pnp_device_id that replaces .driver_data by an anonymous union. This patch doesn't modify the compiled array, only their representation in source form benefits. The former was confirmed with x86 and arm64 builds. Also simplify the list terminator and use a consistent and more common indention. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/20260629170409.3412413-2-u.kleine-koenig@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_pnp.c | 308 ++++++++++++++--------------- 1 file changed, 154 insertions(+), 154 deletions(-) diff --git a/drivers/tty/serial/8250/8250_pnp.c b/drivers/tty/serial/8250/8250_pnp.c index 3f41a9d6cb27..6bfdeff5fe22 100644 --- a/drivers/tty/serial/8250/8250_pnp.c +++ b/drivers/tty/serial/8250/8250_pnp.c @@ -28,351 +28,351 @@ static const struct pnp_device_id pnp_dev_table[] = { /* Archtek America Corp. */ /* Archtek SmartLink Modem 3334BT Plug & Play */ - { "AAC000F", 0 }, + { .id = "AAC000F", .driver_data = 0 }, /* Anchor Datacomm BV */ /* SXPro 144 External Data Fax Modem Plug & Play */ - { "ADC0001", 0 }, + { .id = "ADC0001", .driver_data = 0 }, /* SXPro 288 External Data Fax Modem Plug & Play */ - { "ADC0002", 0 }, + { .id = "ADC0002", .driver_data = 0 }, /* PROLiNK 1456VH ISA PnP K56flex Fax Modem */ - { "AEI0250", 0 }, + { .id = "AEI0250", .driver_data = 0 }, /* Actiontec ISA PNP 56K X2 Fax Modem */ - { "AEI1240", 0 }, + { .id = "AEI1240", .driver_data = 0 }, /* Rockwell 56K ACF II Fax+Data+Voice Modem */ - { "AKY1021", 0 /*SPCI_FL_NO_SHIRQ*/ }, + { .id ="AKY1021", .driver_data = 0 /*SPCI_FL_NO_SHIRQ*/ }, /* * ALi Fast Infrared Controller * Native driver (ali-ircc) is broken so at least * it can be used with irtty-sir. */ - { "ALI5123", 0 }, + { .id = "ALI5123", .driver_data = 0 }, /* AZT3005 PnP SOUND DEVICE */ - { "AZT4001", 0 }, + { .id = "AZT4001", .driver_data = 0 }, /* Best Data Products Inc. Smart One 336F PnP Modem */ - { "BDP3336", 0 }, + { .id = "BDP3336", .driver_data = 0 }, /* Boca Research */ /* Boca Complete Ofc Communicator 14.4 Data-FAX */ - { "BRI0A49", 0 }, + { .id = "BRI0A49", .driver_data = 0 }, /* Boca Research 33,600 ACF Modem */ - { "BRI1400", 0 }, + { .id = "BRI1400", .driver_data = 0 }, /* Boca 33.6 Kbps Internal FD34FSVD */ - { "BRI3400", 0 }, + { .id = "BRI3400", .driver_data = 0 }, /* Computer Peripherals Inc */ /* EuroViVa CommCenter-33.6 SP PnP */ - { "CPI4050", 0 }, + { .id = "CPI4050", .driver_data = 0 }, /* Creative Labs */ /* Creative Labs Phone Blaster 28.8 DSVD PnP Voice */ - { "CTL3001", 0 }, + { .id = "CTL3001", .driver_data = 0 }, /* Creative Labs Modem Blaster 28.8 DSVD PnP Voice */ - { "CTL3011", 0 }, + { .id = "CTL3011", .driver_data = 0 }, /* Davicom ISA 33.6K Modem */ - { "DAV0336", 0 }, + { .id = "DAV0336", .driver_data = 0 }, /* Creative */ /* Creative Modem Blaster Flash56 DI5601-1 */ - { "DMB1032", 0 }, + { .id = "DMB1032", .driver_data = 0 }, /* Creative Modem Blaster V.90 DI5660 */ - { "DMB2001", 0 }, + { .id = "DMB2001", .driver_data = 0 }, /* E-Tech */ /* E-Tech CyberBULLET PC56RVP */ - { "ETT0002", 0 }, + { .id = "ETT0002", .driver_data = 0 }, /* FUJITSU */ /* Fujitsu 33600 PnP-I2 R Plug & Play */ - { "FUJ0202", 0 }, + { .id = "FUJ0202", .driver_data = 0 }, /* Fujitsu FMV-FX431 Plug & Play */ - { "FUJ0205", 0 }, + { .id = "FUJ0205", .driver_data = 0 }, /* Fujitsu 33600 PnP-I4 R Plug & Play */ - { "FUJ0206", 0 }, + { .id = "FUJ0206", .driver_data = 0 }, /* Fujitsu Fax Voice 33600 PNP-I5 R Plug & Play */ - { "FUJ0209", 0 }, + { .id = "FUJ0209", .driver_data = 0 }, /* Archtek America Corp. */ /* Archtek SmartLink Modem 3334BT Plug & Play */ - { "GVC000F", 0 }, + { .id = "GVC000F", .driver_data = 0 }, /* Archtek SmartLink Modem 3334BRV 33.6K Data Fax Voice */ - { "GVC0303", 0 }, + { .id = "GVC0303", .driver_data = 0 }, /* Hayes */ /* Hayes Optima 288 V.34-V.FC + FAX + Voice Plug & Play */ - { "HAY0001", 0 }, + { .id = "HAY0001", .driver_data = 0 }, /* Hayes Optima 336 V.34 + FAX + Voice PnP */ - { "HAY000C", 0 }, + { .id = "HAY000C", .driver_data = 0 }, /* Hayes Optima 336B V.34 + FAX + Voice PnP */ - { "HAY000D", 0 }, + { .id = "HAY000D", .driver_data = 0 }, /* Hayes Accura 56K Ext Fax Modem PnP */ - { "HAY5670", 0 }, + { .id = "HAY5670", .driver_data = 0 }, /* Hayes Accura 56K Ext Fax Modem PnP */ - { "HAY5674", 0 }, + { .id = "HAY5674", .driver_data = 0 }, /* Hayes Accura 56K Fax Modem PnP */ - { "HAY5675", 0 }, + { .id = "HAY5675", .driver_data = 0 }, /* Hayes 288, V.34 + FAX */ - { "HAYF000", 0 }, + { .id = "HAYF000", .driver_data = 0 }, /* Hayes Optima 288 V.34 + FAX + Voice, Plug & Play */ - { "HAYF001", 0 }, + { .id = "HAYF001", .driver_data = 0 }, /* IBM */ /* IBM Thinkpad 701 Internal Modem Voice */ - { "IBM0033", 0 }, + { .id = "IBM0033", .driver_data = 0 }, /* Intermec */ /* Intermec CV60 touchscreen port */ - { "PNP4972", 0 }, + { .id = "PNP4972", .driver_data = 0 }, /* Intertex */ /* Intertex 28k8 33k6 Voice EXT PnP */ - { "IXDC801", 0 }, + { .id = "IXDC801", .driver_data = 0 }, /* Intertex 33k6 56k Voice EXT PnP */ - { "IXDC901", 0 }, + { .id = "IXDC901", .driver_data = 0 }, /* Intertex 28k8 33k6 Voice SP EXT PnP */ - { "IXDD801", 0 }, + { .id = "IXDD801", .driver_data = 0 }, /* Intertex 33k6 56k Voice SP EXT PnP */ - { "IXDD901", 0 }, + { .id = "IXDD901", .driver_data = 0 }, /* Intertex 28k8 33k6 Voice SP INT PnP */ - { "IXDF401", 0 }, + { .id = "IXDF401", .driver_data = 0 }, /* Intertex 28k8 33k6 Voice SP EXT PnP */ - { "IXDF801", 0 }, + { .id = "IXDF801", .driver_data = 0 }, /* Intertex 33k6 56k Voice SP EXT PnP */ - { "IXDF901", 0 }, + { .id = "IXDF901", .driver_data = 0 }, /* Kortex International */ /* KORTEX 28800 Externe PnP */ - { "KOR4522", 0 }, + { .id = "KOR4522", .driver_data = 0 }, /* KXPro 33.6 Vocal ASVD PnP */ - { "KORF661", 0 }, + { .id = "KORF661", .driver_data = 0 }, /* Lasat */ /* LASAT Internet 33600 PnP */ - { "LAS4040", 0 }, + { .id = "LAS4040", .driver_data = 0 }, /* Lasat Safire 560 PnP */ - { "LAS4540", 0 }, + { .id = "LAS4540", .driver_data = 0 }, /* Lasat Safire 336 PnP */ - { "LAS5440", 0 }, + { .id = "LAS5440", .driver_data = 0 }, /* Microcom, Inc. */ /* Microcom TravelPorte FAST V.34 Plug & Play */ - { "MNP0281", 0 }, + { .id = "MNP0281", .driver_data = 0 }, /* Microcom DeskPorte V.34 FAST or FAST+ Plug & Play */ - { "MNP0336", 0 }, + { .id = "MNP0336", .driver_data = 0 }, /* Microcom DeskPorte FAST EP 28.8 Plug & Play */ - { "MNP0339", 0 }, + { .id = "MNP0339", .driver_data = 0 }, /* Microcom DeskPorte 28.8P Plug & Play */ - { "MNP0342", 0 }, + { .id = "MNP0342", .driver_data = 0 }, /* Microcom DeskPorte FAST ES 28.8 Plug & Play */ - { "MNP0500", 0 }, + { .id = "MNP0500", .driver_data = 0 }, /* Microcom DeskPorte FAST ES 28.8 Plug & Play */ - { "MNP0501", 0 }, + { .id = "MNP0501", .driver_data = 0 }, /* Microcom DeskPorte 28.8S Internal Plug & Play */ - { "MNP0502", 0 }, + { .id = "MNP0502", .driver_data = 0 }, /* Motorola */ /* Motorola BitSURFR Plug & Play */ - { "MOT1105", 0 }, + { .id = "MOT1105", .driver_data = 0 }, /* Motorola TA210 Plug & Play */ - { "MOT1111", 0 }, + { .id = "MOT1111", .driver_data = 0 }, /* Motorola HMTA 200 (ISDN) Plug & Play */ - { "MOT1114", 0 }, + { .id = "MOT1114", .driver_data = 0 }, /* Motorola BitSURFR Plug & Play */ - { "MOT1115", 0 }, + { .id = "MOT1115", .driver_data = 0 }, /* Motorola Lifestyle 28.8 Internal */ - { "MOT1190", 0 }, + { .id = "MOT1190", .driver_data = 0 }, /* Motorola V.3400 Plug & Play */ - { "MOT1501", 0 }, + { .id = "MOT1501", .driver_data = 0 }, /* Motorola Lifestyle 28.8 V.34 Plug & Play */ - { "MOT1502", 0 }, + { .id = "MOT1502", .driver_data = 0 }, /* Motorola Power 28.8 V.34 Plug & Play */ - { "MOT1505", 0 }, + { .id = "MOT1505", .driver_data = 0 }, /* Motorola ModemSURFR External 28.8 Plug & Play */ - { "MOT1509", 0 }, + { .id = "MOT1509", .driver_data = 0 }, /* Motorola Premier 33.6 Desktop Plug & Play */ - { "MOT150A", 0 }, + { .id = "MOT150A", .driver_data = 0 }, /* Motorola VoiceSURFR 56K External PnP */ - { "MOT150F", 0 }, + { .id = "MOT150F", .driver_data = 0 }, /* Motorola ModemSURFR 56K External PnP */ - { "MOT1510", 0 }, + { .id = "MOT1510", .driver_data = 0 }, /* Motorola ModemSURFR 56K Internal PnP */ - { "MOT1550", 0 }, + { .id = "MOT1550", .driver_data = 0 }, /* Motorola ModemSURFR Internal 28.8 Plug & Play */ - { "MOT1560", 0 }, + { .id = "MOT1560", .driver_data = 0 }, /* Motorola Premier 33.6 Internal Plug & Play */ - { "MOT1580", 0 }, + { .id = "MOT1580", .driver_data = 0 }, /* Motorola OnlineSURFR 28.8 Internal Plug & Play */ - { "MOT15B0", 0 }, + { .id = "MOT15B0", .driver_data = 0 }, /* Motorola VoiceSURFR 56K Internal PnP */ - { "MOT15F0", 0 }, + { .id = "MOT15F0", .driver_data = 0 }, /* Com 1 */ /* Deskline K56 Phone System PnP */ - { "MVX00A1", 0 }, + { .id = "MVX00A1", .driver_data = 0 }, /* PC Rider K56 Phone System PnP */ - { "MVX00F2", 0 }, + { .id = "MVX00F2", .driver_data = 0 }, /* NEC 98NOTE SPEAKER PHONE FAX MODEM(33600bps) */ - { "nEC8241", 0 }, + { .id = "nEC8241", .driver_data = 0 }, /* Pace 56 Voice Internal Plug & Play Modem */ - { "PMC2430", 0 }, + { .id = "PMC2430", .driver_data = 0 }, /* Generic */ /* Generic standard PC COM port */ - { "PNP0500", 0 }, + { .id = "PNP0500", .driver_data = 0 }, /* Generic 16550A-compatible COM port */ - { "PNP0501", 0 }, + { .id = "PNP0501", .driver_data = 0 }, /* Compaq 14400 Modem */ - { "PNPC000", 0 }, + { .id = "PNPC000", .driver_data = 0 }, /* Compaq 2400/9600 Modem */ - { "PNPC001", 0 }, + { .id = "PNPC001", .driver_data = 0 }, /* Dial-Up Networking Serial Cable between 2 PCs */ - { "PNPC031", 0 }, + { .id = "PNPC031", .driver_data = 0 }, /* Dial-Up Networking Parallel Cable between 2 PCs */ - { "PNPC032", 0 }, + { .id = "PNPC032", .driver_data = 0 }, /* Standard 9600 bps Modem */ - { "PNPC100", 0 }, + { .id = "PNPC100", .driver_data = 0 }, /* Standard 14400 bps Modem */ - { "PNPC101", 0 }, + { .id = "PNPC101", .driver_data = 0 }, /* Standard 28800 bps Modem*/ - { "PNPC102", 0 }, + { .id = "PNPC102", .driver_data = 0 }, /* Standard Modem*/ - { "PNPC103", 0 }, + { .id = "PNPC103", .driver_data = 0 }, /* Standard 9600 bps Modem*/ - { "PNPC104", 0 }, + { .id = "PNPC104", .driver_data = 0 }, /* Standard 14400 bps Modem*/ - { "PNPC105", 0 }, + { .id = "PNPC105", .driver_data = 0 }, /* Standard 28800 bps Modem*/ - { "PNPC106", 0 }, + { .id = "PNPC106", .driver_data = 0 }, /* Standard Modem */ - { "PNPC107", 0 }, + { .id = "PNPC107", .driver_data = 0 }, /* Standard 9600 bps Modem */ - { "PNPC108", 0 }, + { .id = "PNPC108", .driver_data = 0 }, /* Standard 14400 bps Modem */ - { "PNPC109", 0 }, + { .id = "PNPC109", .driver_data = 0 }, /* Standard 28800 bps Modem */ - { "PNPC10A", 0 }, + { .id = "PNPC10A", .driver_data = 0 }, /* Standard Modem */ - { "PNPC10B", 0 }, + { .id = "PNPC10B", .driver_data = 0 }, /* Standard 9600 bps Modem */ - { "PNPC10C", 0 }, + { .id = "PNPC10C", .driver_data = 0 }, /* Standard 14400 bps Modem */ - { "PNPC10D", 0 }, + { .id = "PNPC10D", .driver_data = 0 }, /* Standard 28800 bps Modem */ - { "PNPC10E", 0 }, + { .id = "PNPC10E", .driver_data = 0 }, /* Standard Modem */ - { "PNPC10F", 0 }, + { .id = "PNPC10F", .driver_data = 0 }, /* Standard PCMCIA Card Modem */ - { "PNP2000", 0 }, + { .id = "PNP2000", .driver_data = 0 }, /* Rockwell */ /* Modular Technology */ /* Rockwell 33.6 DPF Internal PnP */ /* Modular Technology 33.6 Internal PnP */ - { "ROK0030", 0 }, + { .id = "ROK0030", .driver_data = 0 }, /* Kortex International */ /* KORTEX 14400 Externe PnP */ - { "ROK0100", 0 }, + { .id = "ROK0100", .driver_data = 0 }, /* Rockwell 28.8 */ - { "ROK4120", 0 }, + { .id = "ROK4120", .driver_data = 0 }, /* Viking Components, Inc */ /* Viking 28.8 INTERNAL Fax+Data+Voice PnP */ - { "ROK4920", 0 }, + { .id = "ROK4920", .driver_data = 0 }, /* Rockwell */ /* British Telecom */ /* Modular Technology */ /* Rockwell 33.6 DPF External PnP */ /* BT Prologue 33.6 External PnP */ /* Modular Technology 33.6 External PnP */ - { "RSS00A0", 0 }, + { .id = "RSS00A0", .driver_data = 0 }, /* Viking 56K FAX INT */ - { "RSS0262", 0 }, + { .id = "RSS0262", .driver_data = 0 }, /* K56 par,VV,Voice,Speakphone,AudioSpan,PnP */ - { "RSS0250", 0 }, + { .id = "RSS0250", .driver_data = 0 }, /* SupraExpress 28.8 Data/Fax PnP modem */ - { "SUP1310", 0 }, + { .id = "SUP1310", .driver_data = 0 }, /* SupraExpress 336i PnP Voice Modem */ - { "SUP1381", 0 }, + { .id = "SUP1381", .driver_data = 0 }, /* SupraExpress 33.6 Data/Fax PnP modem */ - { "SUP1421", 0 }, + { .id = "SUP1421", .driver_data = 0 }, /* SupraExpress 33.6 Data/Fax PnP modem */ - { "SUP1590", 0 }, + { .id = "SUP1590", .driver_data = 0 }, /* SupraExpress 336i Sp ASVD */ - { "SUP1620", 0 }, + { .id = "SUP1620", .driver_data = 0 }, /* SupraExpress 33.6 Data/Fax PnP modem */ - { "SUP1760", 0 }, + { .id = "SUP1760", .driver_data = 0 }, /* SupraExpress 56i Sp Intl */ - { "SUP2171", 0 }, + { .id = "SUP2171", .driver_data = 0 }, /* Phoebe Micro */ /* Phoebe Micro 33.6 Data Fax 1433VQH Plug & Play */ - { "TEX0011", 0 }, + { .id = "TEX0011", .driver_data = 0 }, /* Archtek America Corp. */ /* Archtek SmartLink Modem 3334BT Plug & Play */ - { "UAC000F", 0 }, + { .id = "UAC000F", .driver_data = 0 }, /* 3Com Corp. */ /* Gateway Telepath IIvi 33.6 */ - { "USR0000", 0 }, + { .id = "USR0000", .driver_data = 0 }, /* U.S. Robotics Sporster 33.6K Fax INT PnP */ - { "USR0002", 0 }, + { .id = "USR0002", .driver_data = 0 }, /* Sportster Vi 14.4 PnP FAX Voicemail */ - { "USR0004", 0 }, + { .id = "USR0004", .driver_data = 0 }, /* U.S. Robotics 33.6K Voice INT PnP */ - { "USR0006", 0 }, + { .id = "USR0006", .driver_data = 0 }, /* U.S. Robotics 33.6K Voice EXT PnP */ - { "USR0007", 0 }, + { .id = "USR0007", .driver_data = 0 }, /* U.S. Robotics Courier V.Everything INT PnP */ - { "USR0009", 0 }, + { .id = "USR0009", .driver_data = 0 }, /* U.S. Robotics 33.6K Voice INT PnP */ - { "USR2002", 0 }, + { .id = "USR2002", .driver_data = 0 }, /* U.S. Robotics 56K Voice INT PnP */ - { "USR2070", 0 }, + { .id = "USR2070", .driver_data = 0 }, /* U.S. Robotics 56K Voice EXT PnP */ - { "USR2080", 0 }, + { .id = "USR2080", .driver_data = 0 }, /* U.S. Robotics 56K FAX INT */ - { "USR3031", 0 }, + { .id = "USR3031", .driver_data = 0 }, /* U.S. Robotics 56K FAX INT */ - { "USR3050", 0 }, + { .id = "USR3050", .driver_data = 0 }, /* U.S. Robotics 56K Voice INT PnP */ - { "USR3070", 0 }, + { .id = "USR3070", .driver_data = 0 }, /* U.S. Robotics 56K Voice EXT PnP */ - { "USR3080", 0 }, + { .id = "USR3080", .driver_data = 0 }, /* U.S. Robotics 56K Voice INT PnP */ - { "USR3090", 0 }, + { .id = "USR3090", .driver_data = 0 }, /* U.S. Robotics 56K Message */ - { "USR9100", 0 }, + { .id = "USR9100", .driver_data = 0 }, /* U.S. Robotics 56K FAX EXT PnP*/ - { "USR9160", 0 }, + { .id = "USR9160", .driver_data = 0 }, /* U.S. Robotics 56K FAX INT PnP*/ - { "USR9170", 0 }, + { .id = "USR9170", .driver_data = 0 }, /* U.S. Robotics 56K Voice EXT PnP*/ - { "USR9180", 0 }, + { .id = "USR9180", .driver_data = 0 }, /* U.S. Robotics 56K Voice INT PnP*/ - { "USR9190", 0 }, + { .id = "USR9190", .driver_data = 0 }, /* Wacom tablets */ - { "WACFXXX", 0 }, + { .id = "WACFXXX", .driver_data = 0 }, /* Compaq touchscreen */ - { "FPI2002", 0 }, + { .id = "FPI2002", .driver_data = 0 }, /* Fujitsu Stylistic touchscreens */ - { "FUJ02B2", 0 }, - { "FUJ02B3", 0 }, + { .id = "FUJ02B2", .driver_data = 0 }, + { .id = "FUJ02B3", .driver_data = 0 }, /* Fujitsu Stylistic LT touchscreens */ - { "FUJ02B4", 0 }, + { .id = "FUJ02B4", .driver_data = 0 }, /* Passive Fujitsu Stylistic touchscreens */ - { "FUJ02B6", 0 }, - { "FUJ02B7", 0 }, - { "FUJ02B8", 0 }, - { "FUJ02B9", 0 }, - { "FUJ02BC", 0 }, + { .id = "FUJ02B6", .driver_data = 0 }, + { .id = "FUJ02B7", .driver_data = 0 }, + { .id = "FUJ02B8", .driver_data = 0 }, + { .id = "FUJ02B9", .driver_data = 0 }, + { .id = "FUJ02BC", .driver_data = 0 }, /* Fujitsu Wacom Tablet PC device */ - { "FUJ02E5", 0 }, + { .id = "FUJ02E5", .driver_data = 0 }, /* Fujitsu P-series tablet PC device */ - { "FUJ02E6", 0 }, + { .id = "FUJ02E6", .driver_data = 0 }, /* Fujitsu Wacom 2FGT Tablet PC device */ - { "FUJ02E7", 0 }, + { .id = "FUJ02E7", .driver_data = 0 }, /* Fujitsu Wacom 1FGT Tablet PC device */ - { "FUJ02E9", 0 }, + { .id = "FUJ02E9", .driver_data = 0 }, /* * LG C1 EXPRESS DUAL (C1-PB11A3) touch screen (actually a FUJ02E6 * in disguise). */ - { "LTS0001", 0 }, + { .id = "LTS0001", .driver_data = 0 }, /* Rockwell's (PORALiNK) 33600 INT PNP */ - { "WCI0003", 0 }, + { .id = "WCI0003", .driver_data = 0 }, /* Unknown PnP modems */ - { "PNPCXXX", UNKNOWN_DEV }, + { .id = "PNPCXXX", .driver_data = UNKNOWN_DEV }, /* More unknown PnP modems */ - { "PNPDXXX", UNKNOWN_DEV }, + { .id = "PNPDXXX", .driver_data = UNKNOWN_DEV }, /* * Winbond CIR port, should not be probed. We should keep track of * it to prevent the legacy serial driver from probing it. */ - { "WEC1022", CIR_PORT }, + { .id = "WEC1022", .driver_data = CIR_PORT }, /* * SMSC IrCC SIR/FIR port, should not be probed by serial driver as * well so its own driver can bind to it. */ - { "SMCF010", CIR_PORT }, - { "", 0 } + { .id = "SMCF010", .driver_data = CIR_PORT }, + { } }; MODULE_DEVICE_TABLE(pnp, pnp_dev_table); From 8dfea56f350b3dc826f35711802ad6ae8fae0748 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Tue, 30 Jun 2026 17:40:43 -0400 Subject: [PATCH 25/97] serial: ma35d1: Fix OF node reference leaks in console init ma35d1serial_console_init_port() stores matching UART device nodes in ma35d1serial_uart_nodes[] with an extra of_node_get() so that console setup can later read the "reg" property. However, the stored references are never released after console setup has finished using them. Drop the stored node reference after ma35d1serial_console_setup() reads the "reg" property, and clear the array slot to avoid leaving a stale pointer behind. Also release the iterator reference before breaking out of for_each_matching_node(), since the normal iterator advance will not run in that path. Fixes: 930cbf92db01 ("tty: serial: Add Nuvoton ma35d1 serial driver support") Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260630214043.1887351-1-dbgh9129@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/ma35d1_serial.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/ma35d1_serial.c b/drivers/tty/serial/ma35d1_serial.c index 285b0fe41a86..920fe7ff5083 100644 --- a/drivers/tty/serial/ma35d1_serial.c +++ b/drivers/tty/serial/ma35d1_serial.c @@ -608,8 +608,14 @@ static int __init ma35d1serial_console_setup(struct console *co, char *options) if (!np || !p) return -ENODEV; - if (of_property_read_u32_array(np, "reg", val32, ARRAY_SIZE(val32)) != 0) + if (of_property_read_u32_array(np, "reg", val32, ARRAY_SIZE(val32)) != 0) { + of_node_put(np); + ma35d1serial_uart_nodes[co->index] = NULL; return -EINVAL; + } + + of_node_put(np); + ma35d1serial_uart_nodes[co->index] = NULL; p->port.iobase = val32[1]; p->port.membase = ioremap(p->port.iobase, MA35_UART_REG_SIZE); @@ -648,8 +654,10 @@ static void ma35d1serial_console_init_port(void) of_node_get(np); ma35d1serial_uart_nodes[i] = np; i++; - if (i == MA35_UART_NR) + if (i == MA35_UART_NR) { + of_node_put(np); break; + } } } } From bafe277d6c5a8bc4b6b9ecafc35cc357c42e813d Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 1 Jul 2026 09:53:10 +0530 Subject: [PATCH 26/97] serial: qcom-geni: Pre-map RX DMA buffer at probe to avoid sleep-in-atomic geni_se_rx_dma_prep() calls dma_map_single() which can trigger IOMMU page table allocations under GFP_KERNEL. This is unsafe when called from qcom_geni_serial_start_rx_dma(), which runs in atomic context producing a "sleeping function called from invalid context" splat: __might_resched+0x15c/0x17c __alloc_pages_noprof+0xe4/0x4c8 qcom_io_pgtable_alloc_page+0x100/0x250 __arm_lpae_map+0x2d0/0x870 geni_se_rx_dma_prep+0xd8/0x158 qcom_geni_serial_start_rx_dma+0x84/0x16c qcom_geni_serial_startup+0x70/0x104 Fix this by mapping the RX DMA buffer once during probe, where sleeping is allowed, and keeping it mapped for the lifetime of the device. Replace the geni_se_rx_dma_prep() / geni_se_rx_dma_unprep() calls in the runtime paths with dma_sync_single_for_device() before initiating a transfer and dma_sync_single_for_cpu() on completion, using the persistent mapping. The buffer is unmapped in probe's error path and in remove(). Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260701-fix-sleep-in-atomic-context-during-rx-dma-setup-v1-1-95c208380c65@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 55 ++++++++++++++++----------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 7ead87b4eb65..3e460b0358eb 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -863,37 +864,31 @@ static void qcom_geni_serial_stop_rx_dma(struct uart_port *uport) uport->membase + SE_DMA_RX_IRQ_CLR); } - if (port->rx_dma_addr) { - geni_se_rx_dma_unprep(&port->se, port->rx_dma_addr, - DMA_RX_BUF_SIZE); - port->rx_dma_addr = 0; - } } static void qcom_geni_serial_start_rx_dma(struct uart_port *uport) { struct qcom_geni_serial_port *port = to_dev_port(uport); - int ret; if (qcom_geni_serial_secondary_active(uport)) qcom_geni_serial_stop_rx_dma(uport); geni_se_setup_s_cmd(&port->se, UART_START_READ, UART_PARAM_RFR_OPEN); - ret = geni_se_rx_dma_prep(&port->se, port->rx_buf, - DMA_RX_BUF_SIZE, - &port->rx_dma_addr); - if (ret) { - dev_err(uport->dev, "unable to start RX SE DMA: %d\n", ret); - qcom_geni_serial_stop_rx_dma(uport); + if (!port->rx_dma_addr) { + dev_err(uport->dev, "RX DMA buffer not mapped\n"); + return; } + + dma_sync_single_for_device(uport->dev->parent, port->rx_dma_addr, + DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + geni_se_rx_init_dma(&port->se, port->rx_dma_addr, DMA_RX_BUF_SIZE); } static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop) { struct qcom_geni_serial_port *port = to_dev_port(uport); u32 rx_in; - int ret; if (!qcom_geni_serial_secondary_active(uport)) return; @@ -901,8 +896,8 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop) if (!port->rx_dma_addr) return; - geni_se_rx_dma_unprep(&port->se, port->rx_dma_addr, DMA_RX_BUF_SIZE); - port->rx_dma_addr = 0; + dma_sync_single_for_cpu(uport->dev->parent, port->rx_dma_addr, + DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); rx_in = readl(uport->membase + SE_DMA_RX_LEN_IN); if (!rx_in) @@ -910,13 +905,9 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop) else if (!drop) handle_rx_uart(uport, rx_in); - ret = geni_se_rx_dma_prep(&port->se, port->rx_buf, - DMA_RX_BUF_SIZE, - &port->rx_dma_addr); - if (ret) { - dev_err(uport->dev, "unable to start RX SE DMA: %d\n", ret); - qcom_geni_serial_stop_rx_dma(uport); - } + dma_sync_single_for_device(uport->dev->parent, port->rx_dma_addr, + DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + geni_se_rx_init_dma(&port->se, port->rx_dma_addr, DMA_RX_BUF_SIZE); } static void qcom_geni_serial_start_rx(struct uart_port *uport) @@ -1864,6 +1855,14 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) ret = -ENOMEM; goto error; } + + port->rx_dma_addr = dma_map_single(pdev->dev.parent, port->rx_buf, + DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(pdev->dev.parent, port->rx_dma_addr)) { + ret = -EIO; + dev_err(&pdev->dev, "Failed to map RX DMA buffer: %d\n", ret); + goto error; + } } port->name = devm_kasprintf(uport->dev, GFP_KERNEL, @@ -1928,6 +1927,11 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) return 0; error: + if (port->rx_dma_addr) { + dma_unmap_single(pdev->dev.parent, port->rx_dma_addr, + DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + port->rx_dma_addr = 0; + } dev_pm_domain_detach_list(port->pd_list); return ret; } @@ -1942,6 +1946,13 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) device_init_wakeup(&pdev->dev, false); ida_free(&port_ida, uport->line); uart_remove_one_port(drv, &port->uport); + + if (port->rx_dma_addr) { + dma_unmap_single(pdev->dev.parent, port->rx_dma_addr, + DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); + port->rx_dma_addr = 0; + } + dev_pm_domain_detach_list(port->pd_list); } From d0cd9c8d0fd59bc7d140f3d60cf02e1d80376dab Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Thu, 2 Jul 2026 17:11:50 +0530 Subject: [PATCH 27/97] serial: qcom-geni: add force suspend/resume to system sleep callbacks During system sleep the hardware resources (clocks, interconnect) are not gated because the runtime-suspend callback is never invoked from the system sleep path. This prevents the platform from reaching its lowest idle state. The system sleep callbacks qcom_geni_serial_suspend() and qcom_geni_serial_resume() rely solely on uart_suspend_port() / uart_resume_port() to manage power. uart_suspend_port() drives the UART PM state machine to UART_PM_STATE_OFF, which in turn calls pm_runtime_put_sync() and eventually the runtime-suspend callback. However, if the runtime-PM usage count is still elevated at the time of system sleep (e.g. the port is held active by an open file descriptor), the runtime-suspend callback is never invoked and the hardware resources (clocks, interconnect) remain enabled across suspend, preventing the platform from reaching its lowest idle state. Fix this by calling pm_runtime_force_suspend() at the end of qcom_geni_serial_suspend() so that the runtime-suspend callback is always executed regardless of the usage count, and by calling pm_runtime_force_resume() at the start of qcom_geni_serial_resume() to restore those resources before uart_resume_port() re-opens the port. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260702-add_force_suspend_resume_to_system_sleep_callbacks-v2-1-b79e254a7015@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 3e460b0358eb..949e16ead7b5 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1985,6 +1985,7 @@ static int qcom_geni_serial_suspend(struct device *dev) struct qcom_geni_serial_port *port = dev_get_drvdata(dev); struct uart_port *uport = &port->uport; struct qcom_geni_private_data *private_data = uport->private_data; + int ret; /* * This is done so we can hit the lowest possible state in suspend @@ -1994,7 +1995,19 @@ static int qcom_geni_serial_suspend(struct device *dev) geni_icc_set_tag(&port->se, QCOM_ICC_TAG_ACTIVE_ONLY); geni_icc_set_bw(&port->se); } - return uart_suspend_port(private_data->drv, uport); + + ret = uart_suspend_port(private_data->drv, uport); + if (ret) + return ret; + + /* + * When no_console_suspend is set the console must remain active + * across system sleep, so skip the force suspend path. + */ + if (!console_suspend_enabled && uart_console(uport)) + return 0; + + return pm_runtime_force_suspend(dev); } static int qcom_geni_serial_resume(struct device *dev) @@ -2004,6 +2017,10 @@ static int qcom_geni_serial_resume(struct device *dev) struct uart_port *uport = &port->uport; struct qcom_geni_private_data *private_data = uport->private_data; + ret = pm_runtime_force_resume(dev); + if (ret) + 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 acba38e675dd66f52925cb9edf9ed8db16335875 Mon Sep 17 00:00:00 2001 From: Aniket Randive Date: Mon, 6 Jul 2026 15:07:49 +0530 Subject: [PATCH 28/97] serial: qcom_geni: Disable closing_wait for console to prevent shutdown timeout During system power-off, systemd closes the console UART and blocks in tty_wait_until_sent() waiting for pending TX completion before uart_shutdown() can cancel pending TX operations. With the default closing_wait of 30s, this causes a watchdog reset when systemd is responsible for watchdog feeding. Set closing_wait to ASYNC_CLOSING_WAIT_NONE in startup() for console ports to bypass tty_wait_until_sent() on close, allowing uart_shutdown() to cancel TX commands cleanly. This change doesn't impact regular ports functionality. Signed-off-by: Aniket Randive Link: https://patch.msgid.link/20260706093749.836121-1-aniket.randive@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 949e16ead7b5..ccbf6fb6b478 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1236,6 +1236,7 @@ static int qcom_geni_serial_startup(struct uart_port *uport) { int ret; struct qcom_geni_serial_port *port = to_dev_port(uport); + struct tty_port *tport = &uport->state->port; if (!port->setup) { ret = qcom_geni_serial_port_setup(uport); @@ -1243,6 +1244,13 @@ static int qcom_geni_serial_startup(struct uart_port *uport) return ret; } + /* + * Skip the close-time transmit drain for console ports so that + * shutdown can proceed without waiting for pending TX completion. + */ + if (uart_console(uport)) + tport->closing_wait = ASYNC_CLOSING_WAIT_NONE; + uart_port_lock_irq(uport); qcom_geni_serial_start_rx(uport); uart_port_unlock_irq(uport); From 7ea38c49e7178960926657863299face6dc0e1b0 Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 8 Jul 2026 21:17:26 +0800 Subject: [PATCH 29/97] serial: qcom-geni: do not advance stale DMA completions The qcom GENI serial DMA TX completion path advances the transmit fifo by the number of bytes recorded in port->tx_remaining. If uart_flush_buffer() runs after the hardware has completed a DMA transfer but before the DMA completion interrupt has been handled, the serial core resets the transmit fifo while port->tx_remaining still describes the old DMA transfer. A previous fix avoided advancing an empty fifo by checking that the fifo length is at least tx_remaining. That still does not distinguish the old DMA payload from new bytes written after the flush. If userspace writes new data before the stale DMA completion interrupt is handled, the fifo can again contain at least tx_remaining bytes and the stale completion can advance and discard those new bytes. Mark an in-flight DMA transfer stale when the transmit fifo is flushed. The later completion still unprepares the original DMA mapping using the saved length, but it no longer advances the transmit fifo. Fixes: 2aaa43c70778 ("tty: serial: qcom-geni-serial: add support for serial engine DMA") Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260708131726.768692-1-lgs201920130244@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index ccbf6fb6b478..e037af56a159 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -144,6 +144,7 @@ struct qcom_geni_serial_port { unsigned int tx_remaining; unsigned int tx_queued; + bool tx_dma_stale; int wakeup_irq; bool rx_tx_swap; bool cts_rts_swap; @@ -698,6 +699,7 @@ static void qcom_geni_serial_start_tx_dma(struct uart_port *uport) } port->tx_remaining = xmit_size; + port->tx_dma_stale = false; } static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport) @@ -1020,6 +1022,7 @@ static void qcom_geni_serial_handle_tx_dma(struct uart_port *uport) struct qcom_geni_serial_port *port = to_dev_port(uport); struct tty_port *tport = &uport->state->port; unsigned int fifo_len = kfifo_len(&tport->xmit_fifo); + bool tx_dma_stale = port->tx_dma_stale; /* * Only advance the kfifo if it still contains the bytes that were @@ -1030,12 +1033,13 @@ static void qcom_geni_serial_handle_tx_dma(struct uart_port *uport) * kfifo->in, making kfifo_len() wrap to UART_XMIT_SIZE - tx_remaining * and triggering a spurious large DMA transfer of stale data. */ - if (fifo_len >= port->tx_remaining) + if (!tx_dma_stale && fifo_len >= port->tx_remaining) uart_xmit_advance(uport, port->tx_remaining); geni_se_tx_dma_unprep(&port->se, port->tx_dma_addr, port->tx_remaining); port->tx_dma_addr = 0; port->tx_remaining = 0; + port->tx_dma_stale = false; if (!kfifo_is_empty(&tport->xmit_fifo)) qcom_geni_serial_start_tx_dma(uport); @@ -1173,6 +1177,10 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) static void qcom_geni_serial_flush_buffer(struct uart_port *uport) { + struct qcom_geni_serial_port *port = to_dev_port(uport); + + if (port->tx_dma_addr) + port->tx_dma_stale = true; qcom_geni_serial_cancel_tx_cmd(uport); } From a76c010ec369ff3e3b3b0bf9224840caa8843a64 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 9 Jul 2026 21:21:08 +0200 Subject: [PATCH 30/97] serial: 8250: handle ixp4xx register endianness correctly Unlike modern SoCs that just work in both big-endian and little-endian mode using the readl()/writel() or readb()/writeb() accessors, the internal registers on ixp4xx behave like native-endian 32-bit registers in both modes, which requires adjusting the register address when using 8-bit access. The existing dts files are written for big-endian kernels and 8-bit access, which does not work with little-endian kernels. Add a quirk that makes the 8250 OF driver: 1. Mask off any hardcoded offset. 2. Add the += 3 offset if and only if we are running on big endian. This should work in all combinations of big-endian and little-endian kernels with either variant of the DTS file. Signed-off-by: Arnd Bergmann [linusw@kernel.org: Modified to just play with the offset] Signed-off-by: Linus Walleij Link: https://patch.msgid.link/20260709-ixp4xx-serial-hackfix-v2-1-465fc8e4c54c@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_early.c | 19 ++++++++++++++++++- drivers/tty/serial/8250/8250_of.c | 12 ++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_early.c b/drivers/tty/serial/8250/8250_early.c index dc0371857ecb..44ec209f37c4 100644 --- a/drivers/tty/serial/8250/8250_early.c +++ b/drivers/tty/serial/8250/8250_early.c @@ -23,6 +23,7 @@ * console=uart8250,mmio32,0xff5e0000,115200n8 */ +#include #include #include #include @@ -177,6 +178,23 @@ OF_EARLYCON_DECLARE(ns16550a, "ns16550a", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "nvidia,tegra20-uart", early_serial8250_setup); OF_EARLYCON_DECLARE(uart, "snps,dw-apb-uart", early_serial8250_setup); +static int __init early_serial8250_xscale_setup(struct earlycon_device *device, + const char *options) +{ + /* + * Adjust for BE32 register accesses: drop any hardcoded + * address for the big endian byte target, add it explicitly + * if running on BE32. + */ + device->port.membase = PTR_ALIGN_DOWN(device->port.membase, 4); + if (IS_ENABLED(CONFIG_CPU_ENDIAN_BE32)) + device->port.membase += 3; + device->port.regshift = 2; + + return early_serial8250_setup(device, options); +} +OF_EARLYCON_DECLARE(uart, "intel,xscale-uart", early_serial8250_xscale_setup); + static int __init early_serial8250_rs2_setup(struct earlycon_device *device, const char *options) { @@ -184,7 +202,6 @@ static int __init early_serial8250_rs2_setup(struct earlycon_device *device, return early_serial8250_setup(device, options); } -OF_EARLYCON_DECLARE(uart, "intel,xscale-uart", early_serial8250_rs2_setup); OF_EARLYCON_DECLARE(uart, "mrvl,mmp-uart", early_serial8250_rs2_setup); OF_EARLYCON_DECLARE(uart, "mrvl,pxa-uart", early_serial8250_rs2_setup); diff --git a/drivers/tty/serial/8250/8250_of.c b/drivers/tty/serial/8250/8250_of.c index 81644d40b09a..f0537fb6ef4f 100644 --- a/drivers/tty/serial/8250/8250_of.c +++ b/drivers/tty/serial/8250/8250_of.c @@ -5,6 +5,7 @@ * Copyright (C) 2006 Arnd Bergmann , IBM Corp. */ +#include #include #include #include @@ -122,6 +123,17 @@ static int of_platform_serial_setup(struct platform_device *ofdev, if (ret) goto err_pmruntime; + if (IS_ENABLED(CONFIG_CPU_XSCALE) && type == PORT_XSCALE) { + /* + * Adjust for BE32 register accesses: drop any hardcoded + * address for the big endian byte target, add it explicitly + * if running on BE32. + */ + port->mapbase = PTR_ALIGN_DOWN(port->mapbase, 4); + if (IS_ENABLED(CONFIG_CPU_ENDIAN_BE32)) + port->mapbase += 3; + } + /* Get clk rate through clk driver if present */ if (!port->uartclk) { struct clk *bus_clk; From 3d71f8d7eeb374d0eb84c64c6ffd68bdcc0d42d4 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 10 Jul 2026 14:51:32 +0530 Subject: [PATCH 31/97] serial: qcom-geni: remove .pm callback, use runtime PM in startup/shutdown The driver currently relies on qcom_geni_serial_pm() through the uart_ops.pm callback to manage runtime PM references. However, the callback has a void return type, so failures from pm_runtime_resume_and_get() cannot be propagated to the caller. As a result, startup() may continue and access hardware even when the runtime PM resume operation failed, leading to register accesses while the device is not powered. Move runtime PM acquisition to qcom_geni_serial_startup() and release it to qcom_geni_serial_shutdown(). Since startup() can return an error, PM resume failures are now detected and propagated before any hardware initialization is performed. The startup/shutdown pair also provides a natural place to balance runtime PM references for normal port usage. During probe, uart_add_one_port() may configure the port before any user opens the TTY, meaning startup() has not yet been called. To keep the hardware powered during port registration, wrap uart_add_one_port() with PM_RUNTIME_ACQUIRE_IF_ENABLED() and PM_RUNTIME_ACQUIRE_ERR(). This ensures the device is resumed for the duration of registration and that the runtime PM reference is automatically released afterwards. By moving runtime PM handling out of uart_ops.pm, resume failures are no longer silently ignored and all hardware accesses are guaranteed to occur while the device is powered. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260710-remove_uart_change_state-v1-1-8e8468da22a1@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 37 +++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index e037af56a159..53d221447557 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1173,6 +1173,8 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) qcom_geni_serial_cancel_tx_cmd(uport); uart_port_unlock_irq(uport); + + pm_runtime_put_sync(uport->dev); } static void qcom_geni_serial_flush_buffer(struct uart_port *uport) @@ -1246,10 +1248,18 @@ static int qcom_geni_serial_startup(struct uart_port *uport) struct qcom_geni_serial_port *port = to_dev_port(uport); struct tty_port *tport = &uport->state->port; + ret = pm_runtime_resume_and_get(uport->dev); + if (ret < 0) { + dev_err(uport->dev, "Failed to resume and get %d\n", ret); + return ret; + } + if (!port->setup) { ret = qcom_geni_serial_port_setup(uport); - if (ret) + if (ret) { + pm_runtime_put_sync(uport->dev); return ret; + } } /* @@ -1731,22 +1741,6 @@ static int geni_serial_resource_init(struct uart_port *uport) return 0; } -static void qcom_geni_serial_pm(struct uart_port *uport, - unsigned int new_state, unsigned int old_state) -{ - - /* If we've never been called, treat it as off */ - if (old_state == UART_PM_STATE_UNDEFINED) - old_state = UART_PM_STATE_OFF; - - if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) - pm_runtime_resume_and_get(uport->dev); - else if (new_state == UART_PM_STATE_OFF && - old_state == UART_PM_STATE_ON) - pm_runtime_put_sync(uport->dev); - -} - /** * qcom_geni_rs485_config - Configure RS485 settings for the UART port * @uport: Pointer to the UART port structure @@ -1785,7 +1779,6 @@ static const struct uart_ops qcom_geni_console_pops = { .poll_put_char = qcom_geni_serial_poll_put_char, .poll_init = qcom_geni_serial_poll_init, #endif - .pm = qcom_geni_serial_pm, }; static const struct uart_ops qcom_geni_uart_pops = { @@ -1802,7 +1795,6 @@ static const struct uart_ops qcom_geni_uart_pops = { .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, - .pm = qcom_geni_serial_pm, }; static int qcom_geni_serial_probe(struct platform_device *pdev) @@ -1936,6 +1928,13 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) devm_pm_runtime_enable(port->se.dev); + PM_RUNTIME_ACQUIRE_IF_ENABLED(uport->dev, pm); + ret = PM_RUNTIME_ACQUIRE_ERR(&pm); + if (ret < 0) { + dev_err(uport->dev, "Failed to resume and get %d\n", ret); + goto error; + } + ret = uart_add_one_port(drv, uport); if (ret) goto error; From 3d406299d8829747fe2e8692f4c29fe3dc1d101f Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:47 -0400 Subject: [PATCH 32/97] serial: 8250_hub6: add hub6_match_port() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the entire hub6 related match port check into its own function in 8250_hub6.c and add a stub for the case when hub6 code is not even built into kernel. Suggested-by: Ilpo Järvinen Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-1-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 7 +++++++ drivers/tty/serial/8250/8250_hub6.c | 6 ++++++ drivers/tty/serial/serial_core.c | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 77fe0588fd6b..9d1068d0489d 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -334,6 +334,13 @@ int fintek_8250_probe(struct uart_8250_port *uart); static inline int fintek_8250_probe(struct uart_8250_port *uart) { return 0; } #endif +#ifdef 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) +{ return false; } +#endif + #ifdef CONFIG_ARCH_OMAP1 #include static inline int is_omap1_8250(struct uart_8250_port *pt) diff --git a/drivers/tty/serial/8250/8250_hub6.c b/drivers/tty/serial/8250/8250_hub6.c index 273f59b9bca5..eae32c924e29 100644 --- a/drivers/tty/serial/8250/8250_hub6.c +++ b/drivers/tty/serial/8250/8250_hub6.c @@ -41,6 +41,12 @@ static struct platform_device hub6_device = { }, }; +bool hub6_match_port(const struct uart_port *port1, const struct uart_port *port2) +{ + return port1->iobase == port2->iobase && port1->hub6 == port2->hub6; +} +EXPORT_SYMBOL_GPL(hub6_match_port); + static int __init hub6_init(void) { return platform_device_register(&hub6_device); diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index a530ad372b43..965ba0335a36 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -33,6 +33,7 @@ #include #include "serial_base.h" +#include "8250/8250.h" /* For hub6_match_port() */ /* * This is used to lock changes in serial line configuration. @@ -3213,8 +3214,7 @@ bool uart_match_port(const struct uart_port *port1, case UPIO_PORT: return port1->iobase == port2->iobase; case UPIO_HUB6: - return port1->iobase == port2->iobase && - port1->hub6 == port2->hub6; + return hub6_match_port(port1, port2); case UPIO_MEM: case UPIO_MEM16: case UPIO_MEM32: From 6e85378a38cebf9b437bc2b5c22dc1ba58161bd2 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:48 -0400 Subject: [PATCH 33/97] serial: core: add uart_iotype_mmio/io helper functions To help simplify code that check on the io type mode of the port. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-2-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 28 ++++++++++++++++++++++++++++ include/linux/serial_core.h | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 965ba0335a36..d82f3ff44532 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1963,6 +1963,34 @@ static const char *uart_type(struct uart_port *port) return str; } +bool uart_iotype_mmio(enum uart_iotype iotype) +{ + switch (iotype) { + case UPIO_MEM: + case UPIO_MEM32: + case UPIO_AU: + case UPIO_TSI: + case UPIO_MEM32BE: + case UPIO_MEM16: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(uart_iotype_mmio); + +bool uart_iotype_io(enum uart_iotype iotype) +{ + switch (iotype) { + case UPIO_PORT: + case UPIO_HUB6: + return true; + default: + return false; + } +} +EXPORT_SYMBOL_GPL(uart_iotype_io); + #ifdef CONFIG_PROC_FS static void uart_line_info(struct seq_file *m, struct uart_state *state) diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index bdc214386e4a..3cfde1af12fe 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -1338,4 +1338,8 @@ static inline int uart_handle_break(struct uart_port *port) !((cflag) & CLOCAL)) int uart_get_rs485_mode(struct uart_port *port); + +bool uart_iotype_mmio(enum uart_iotype iotype); +bool uart_iotype_io(enum uart_iotype iotype); + #endif /* LINUX_SERIAL_CORE_H */ From 11f1d49122ec2227b050f4596a7422606237347e Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:49 -0400 Subject: [PATCH 34/97] serial: core: use uart_iotype_*() to simplify uart_match_port() Make use of new functions uart_iotype_mmio() and uart_iotype_legacy_io() to simplify and improve code readability. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-3-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index d82f3ff44532..6a8f27718648 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3237,22 +3237,14 @@ bool uart_match_port(const struct uart_port *port1, { if (port1->iotype != port2->iotype) return false; - - switch (port1->iotype) { - case UPIO_PORT: + else if (port1->iotype == UPIO_PORT) return port1->iobase == port2->iobase; - case UPIO_HUB6: + else if (port1->iotype == UPIO_HUB6) return hub6_match_port(port1, port2); - case UPIO_MEM: - case UPIO_MEM16: - case UPIO_MEM32: - case UPIO_MEM32BE: - case UPIO_AU: - case UPIO_TSI: + else if (uart_iotype_mmio(port1->iotype)) return port1->mapbase == port2->mapbase; - default: + else return false; - } } EXPORT_SYMBOL(uart_match_port); From 86305190f307c05bc2e0660f4ce16b7aeca6711b Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:50 -0400 Subject: [PATCH 35/97] serial: uniformize serial port I/O infos display Uniformize serial port I/O infos display from three different functions that display mostly the same information, but with some variations, by adding a common function. This make use of new functions uart_iotype_mmio() and uart_iotype_legacy_io() to simplify and improve code readability. This will prevent displaying irrelevant information for future IO types (ex: UPIO_BUS), while also addressing the (eventually) invalid check for "iotype >= UPIO_MEM". This also allows us to remove the confusing cast to (unsigned long long) for iobase which is defined as an unsigned long, and use %pa to display the mapbase pointer, as it is done in earlycon_print_info(). Replace snprintf with more robust scnprintf so we could perhaps one day get rid of snprintf() entirely. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-4-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/earlycon.c | 17 +++---- drivers/tty/serial/serial_core.c | 76 +++++++++++++++++--------------- include/linux/serial_core.h | 1 + 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/drivers/tty/serial/earlycon.c b/drivers/tty/serial/earlycon.c index ab9af37f6cda..ce740cdc7ceb 100644 --- a/drivers/tty/serial/earlycon.c +++ b/drivers/tty/serial/earlycon.c @@ -75,19 +75,12 @@ static void __init earlycon_print_info(struct earlycon_device *device) { struct console *earlycon = device->con; struct uart_port *port = &device->port; + char ioinfos[64]; - if (port->iotype == UPIO_MEM || port->iotype == UPIO_MEM16 || - port->iotype == UPIO_MEM32 || port->iotype == UPIO_MEM32BE) - pr_info("%s%d at MMIO%s %pa (options '%s')\n", - earlycon->name, earlycon->index, - (port->iotype == UPIO_MEM) ? "" : - (port->iotype == UPIO_MEM16) ? "16" : - (port->iotype == UPIO_MEM32) ? "32" : "32be", - &port->mapbase, device->options); - else - pr_info("%s%d at I/O port 0x%lx (options '%s')\n", - earlycon->name, earlycon->index, - port->iobase, device->options); + uart_get_ioinfos(port, ioinfos, sizeof(ioinfos)); + + pr_info("%s%d%s (options '%s')\n", earlycon->name, earlycon->index, + ioinfos, device->options); } static int __init parse_options(struct earlycon_device *device, char *options) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 6a8f27718648..9b16480b374a 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1998,9 +1998,9 @@ static void uart_line_info(struct seq_file *m, struct uart_state *state) struct tty_port *port = &state->port; enum uart_pm_state pm_state; struct uart_port *uport; + char ioinfos[64]; char stat_buf[32]; unsigned int status; - int mmio; guard(mutex)(&port->mutex); @@ -2008,13 +2008,10 @@ static void uart_line_info(struct seq_file *m, struct uart_state *state) if (!uport) return; - mmio = uport->iotype >= UPIO_MEM; - seq_printf(m, "%u: uart:%s %s%08llX irq:%u", - uport->line, uart_type(uport), - mmio ? "mmio:0x" : "port:", - mmio ? (unsigned long long)uport->mapbase - : (unsigned long long)uport->iobase, - uport->irq); + seq_printf(m, "%u: uart:%s", uport->line, uart_type(uport)); + uart_get_ioinfos(uport, ioinfos, sizeof(ioinfos)); + seq_printf(m, "%s", ioinfos); + seq_printf(m, " irq:%u", uport->irq); if (uport->type == PORT_UNKNOWN) { seq_putc(m, '\n'); @@ -2488,38 +2485,47 @@ int uart_resume_port(struct uart_driver *drv, struct uart_port *uport) } EXPORT_SYMBOL(uart_resume_port); +static const char *uart_get_mmio_width(struct uart_port *port) +{ + switch (port->iotype) { + case UPIO_MEM16: + return "16"; + case UPIO_MEM32: + case UPIO_MEM32BE: + return "32be"; + case UPIO_AU: + case UPIO_MEM: + default: + return ""; + } +} + +void uart_get_ioinfos(struct uart_port *port, char *buf, size_t size) +{ + buf[0] = '\0'; + + if (uart_iotype_mmio(port->iotype)) { + scnprintf(buf, size, " MMIO%s:%pa", uart_get_mmio_width(port), &port->mapbase); + } else if (uart_iotype_io(port->iotype)) { + if (port->iotype == UPIO_PORT) + scnprintf(buf, size, " I/O:0x%lx", port->iobase); + else if (port->iotype == UPIO_HUB6) + scnprintf(buf, size, " I/O:0x%lx, offset 0x%x", port->iobase, port->hub6); + } +} +EXPORT_SYMBOL(uart_get_ioinfos); + static inline void uart_report_port(struct uart_driver *drv, struct uart_port *port) { - char address[64]; + char ioinfos[64]; - switch (port->iotype) { - case UPIO_PORT: - snprintf(address, sizeof(address), "I/O 0x%lx", port->iobase); - break; - case UPIO_HUB6: - snprintf(address, sizeof(address), - "I/O 0x%lx offset 0x%x", port->iobase, port->hub6); - break; - case UPIO_MEM: - case UPIO_MEM16: - case UPIO_MEM32: - case UPIO_MEM32BE: - case UPIO_AU: - case UPIO_TSI: - snprintf(address, sizeof(address), - "MMIO 0x%llx", (unsigned long long)port->mapbase); - break; - default: - strscpy(address, "*unknown*", sizeof(address)); - break; - } + uart_get_ioinfos(port, ioinfos, sizeof(ioinfos)); - pr_info("%s%s%s at %s (irq = %u, base_baud = %u) is a %s\n", - port->dev ? dev_name(port->dev) : "", - port->dev ? ": " : "", - port->name, - address, port->irq, port->uartclk / 16, uart_type(port)); + pr_info("%s%s%s%s (irq = %u, base_baud = %u) is a %s\n", + port->dev ? dev_name(port->dev) : "", + port->dev ? ": " : "", + port->name, ioinfos, port->irq, port->uartclk / 16, uart_type(port)); /* The magic multiplier feature is a bit obscure, so report it too. */ if (port->flags & UPF_MAGIC_MULTIPLIER) diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 3cfde1af12fe..66b1459d53fc 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -1339,6 +1339,7 @@ static inline int uart_handle_break(struct uart_port *port) int uart_get_rs485_mode(struct uart_port *port); +void uart_get_ioinfos(struct uart_port *port, char *buf, size_t size); bool uart_iotype_mmio(enum uart_iotype iotype); bool uart_iotype_io(enum uart_iotype iotype); From 548aa0c850081dc923ed057a1ae3ec80cbbba618 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:51 -0400 Subject: [PATCH 36/97] serial: 8250: use uart_iotype_*() to simplify code Make use of new functions uart_iotype_mmio() and uart_iotype_legacy_io() to simplify and improve code readability. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-5-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 43 +++++------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 630deb7dd344..dd63aedb8675 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -474,16 +474,10 @@ static void set_io_from_upio(struct uart_port *p) static void serial_port_out_sync(struct uart_port *p, int offset, int value) { - switch (p->iotype) { - case UPIO_MEM: - case UPIO_MEM16: - case UPIO_MEM32: - case UPIO_MEM32BE: - case UPIO_AU: + if (uart_iotype_mmio(p->iotype)) { p->serial_out(p, offset, value); p->serial_in(p, UART_LCR); /* safe, no side-effects */ - break; - default: + } else { p->serial_out(p, offset, value); } } @@ -2885,13 +2879,7 @@ static int serial8250_request_std_resource(struct uart_8250_port *up) unsigned int size = serial8250_port_size(up); struct uart_port *port = &up->port; - switch (port->iotype) { - case UPIO_AU: - case UPIO_TSI: - case UPIO_MEM32: - case UPIO_MEM32BE: - case UPIO_MEM16: - case UPIO_MEM: + if (uart_iotype_mmio(port->iotype)) { if (!port->mapbase) return -EINVAL; @@ -2905,14 +2893,9 @@ static int serial8250_request_std_resource(struct uart_8250_port *up) return -ENOMEM; } } - return 0; - case UPIO_HUB6: - case UPIO_PORT: + } else if (uart_iotype_io(port->iotype)) { if (!request_region(port->iobase, size, "serial")) return -EBUSY; - return 0; - case UPIO_UNKNOWN: - break; } return 0; @@ -2923,15 +2906,9 @@ static void serial8250_release_std_resource(struct uart_8250_port *up) unsigned int size = serial8250_port_size(up); struct uart_port *port = &up->port; - switch (port->iotype) { - case UPIO_AU: - case UPIO_TSI: - case UPIO_MEM32: - case UPIO_MEM32BE: - case UPIO_MEM16: - case UPIO_MEM: + if (uart_iotype_mmio(port->iotype)) { if (!port->mapbase) - break; + return; if (port->flags & UPF_IOREMAP) { iounmap(port->membase); @@ -2939,14 +2916,8 @@ static void serial8250_release_std_resource(struct uart_8250_port *up) } release_mem_region(port->mapbase, size); - break; - - case UPIO_HUB6: - case UPIO_PORT: + } else if (uart_iotype_io(port->iotype)) { release_region(port->iobase, size); - break; - case UPIO_UNKNOWN: - break; } } From 650d60c734ced3c0efa115d6bad031a24680be4e Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:52 -0400 Subject: [PATCH 37/97] serial: 8250_rsa: use uart_iotype_*() to simplify code Make use of new functions uart_iotype_mmio() and uart_iotype_legacy_io() to simplify and improve code readability, as well as avoid some variables init if the iotype is not valid. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-6-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_rsa.c | 40 ++++++++++++++---------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/drivers/tty/serial/8250/8250_rsa.c b/drivers/tty/serial/8250/8250_rsa.c index fff9395948e3..da971437e89c 100644 --- a/drivers/tty/serial/8250/8250_rsa.c +++ b/drivers/tty/serial/8250/8250_rsa.c @@ -19,35 +19,33 @@ static const struct uart_ops *core_port_base_ops; static int rsa8250_request_resource(struct uart_8250_port *up) { struct uart_port *port = &up->port; - unsigned long start = UART_RSA_BASE << port->regshift; - unsigned int size = 8 << port->regshift; + unsigned long start; + unsigned int size; - switch (port->iotype) { - case UPIO_HUB6: - case UPIO_PORT: - start += port->iobase; - if (!request_region(start, size, "serial-rsa")) - return -EBUSY; - return 0; - default: + if (!uart_iotype_io(port->iotype)) return -EINVAL; - } + + start = UART_RSA_BASE << port->regshift; + start += port->iobase; + size = 8 << port->regshift; + + if (!request_region(start, size, "serial-rsa")) + return -EBUSY; + return 0; } static void rsa8250_release_resource(struct uart_8250_port *up) { struct uart_port *port = &up->port; - unsigned long offset = UART_RSA_BASE << port->regshift; - unsigned int size = 8 << port->regshift; + unsigned long offset; + unsigned int size; - switch (port->iotype) { - case UPIO_HUB6: - case UPIO_PORT: - release_region(port->iobase + offset, size); - break; - default: - break; - } + if (!uart_iotype_io(port->iotype)) + return; + + offset = UART_RSA_BASE << port->regshift; + size = 8 << port->regshift; + release_region(port->iobase + offset, size); } static void univ8250_config_port(struct uart_port *port, int flags) From 44b374622a3f93f2f9ce0a782f3fedb7cd758b6b Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:53 -0400 Subject: [PATCH 38/97] serial: core: add new I/O type for SPI and I2C bus devices I2C/SPI serial drivers don't use the following struct uart_port variables: port->membase port->mapbase port->iobase However, they are forced to set membase to a non-zero value so that uart_configure_port() will succeed because of the following check: /* If there isn't a port here, don't do anything further. */ if (!port->iobase && !port->mapbase && !port->membase) return; Add a new I/O type for SPI and I2C bus devices to remove the need to implement the kind of above-mentioned ambiguous workarounds to make them work. Now that UART report functions are using uart_iotype_*() functions, no more irrelevant I/O information are being printed for UPIO_BUS iotypes. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-7-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 11 ++++++----- include/linux/serial_core.h | 1 + include/uapi/linux/serial.h | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 9b16480b374a..06d7f44643d6 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -2542,11 +2542,10 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state, { unsigned int flags; - /* - * If there isn't a port here, don't do anything further. - */ - if (!port->iobase && !port->mapbase && !port->membase) - return; + /* If there isn't a port here, don't do anything further. */ + if (uart_iotype_mmio(port->iotype) || uart_iotype_io(port->iotype)) + if (!port->iobase && !port->mapbase && !port->membase) + return; /* * Now do the auto configuration stuff. Note that config_port @@ -3249,6 +3248,8 @@ bool uart_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; } diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 66b1459d53fc..c4cc4f66af4b 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -437,6 +437,7 @@ enum uart_iotype { UPIO_TSI = SERIAL_IO_TSI, /* Tsi108/109 type IO */ UPIO_MEM32BE = SERIAL_IO_MEM32BE, /* 32b big endian */ UPIO_MEM16 = SERIAL_IO_MEM16, /* 16b little endian */ + UPIO_BUS = SERIAL_IO_BUS, /* Serial bus I/O access (ex: SPI, I2C) */ }; struct uart_port { diff --git a/include/uapi/linux/serial.h b/include/uapi/linux/serial.h index de9b4733607e..e6f61538fc28 100644 --- a/include/uapi/linux/serial.h +++ b/include/uapi/linux/serial.h @@ -72,6 +72,7 @@ struct serial_struct { #define SERIAL_IO_TSI 5 #define SERIAL_IO_MEM32BE 6 #define SERIAL_IO_MEM16 7 +#define SERIAL_IO_BUS 8 #define UART_CLEAR_FIFO 0x01 #define UART_USE_FIFO 0x02 From ac60073d41fb00be2aa0ccb88b2445b95057700b Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:54 -0400 Subject: [PATCH 39/97] serial: sc16is7xx: use new UPIO_BUS as iotype Now that we have a new UPIO_BUS I/O type, use it to register our serial port and remove ambiguous membase/iobase workaround. Note that commit 5da6b1c079e6 ("sc16is7xx: Set iobase to device index") used the iobase field as an index within the device to allow infering the order through sysfs, but this is no longer needed since commit 1ef2c2df1199 ("serial: core: Fix serial core controller port name to show controller id"). Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-8-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sc16is7xx.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c index 1fd64a47341d..4b638e69f36f 100644 --- a/drivers/tty/serial/sc16is7xx.c +++ b/drivers/tty/serial/sc16is7xx.c @@ -1472,14 +1472,7 @@ static int sc16is7xx_setup_channel(struct sc16is7xx_one *one, int i, port->type = PORT_SC16IS7XX; port->fifosize = SC16IS7XX_FIFO_SIZE; port->flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; - port->iobase = i; - /* - * Use all ones as membase to make sure uart_configure_port() in - * serial_core.c does not abort for SPI/I2C devices where the - * membase address is not applicable. - */ - port->membase = (void __iomem *)~0; - port->iotype = UPIO_PORT; + port->iotype = UPIO_BUS; port->rs485_config = sc16is7xx_config_rs485; port->rs485_supported = sc16is7xx_rs485_supported; port->ops = &sc16is7xx_ops; From 03efc41d6b840fb7d4fd58aedbccc76ad3105f47 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:55 -0400 Subject: [PATCH 40/97] serial: max310x: use new UPIO_BUS as iotype Now that we have a new UPIO_BUS I/O type, use it to register our serial port and remove obscure membase/iobase workaround. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-9-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 022502986c5f..7592e69956d9 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1401,14 +1401,7 @@ static int max310x_probe(struct device *dev, const struct max310x_devtype *devty s->p[i].port.type = PORT_MAX310X; s->p[i].port.fifosize = MAX310X_FIFO_SIZE; s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY; - s->p[i].port.iotype = UPIO_PORT; - s->p[i].port.iobase = i; - /* - * Use all ones as membase to make sure uart_configure_port() in - * serial_core.c does not abort for SPI/I2C devices where the - * membase address is not applicable. - */ - s->p[i].port.membase = (void __iomem *)~0; + s->p[i].port.iotype = UPIO_BUS; s->p[i].port.uartclk = uartclk; s->p[i].port.rs485_config = max310x_rs485_config; s->p[i].port.rs485_supported = max310x_rs485_supported; From bef5e068b89b0f0cf974c987ebba9869a14b44c6 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:56 -0400 Subject: [PATCH 41/97] serial: max3100: use new UPIO_BUS as iotype Now that we have a new UPIO_BUS I/O type, use it to register our serial port. This allows the driver to work properly when using DT where membase/iobase are not set. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-10-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max3100.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/max3100.c b/drivers/tty/serial/max3100.c index 475b0a6efce4..17a2ff410305 100644 --- a/drivers/tty/serial/max3100.c +++ b/drivers/tty/serial/max3100.c @@ -725,6 +725,7 @@ static int max3100_probe(struct spi_device *spi) max3100s[i]->port.ops = &max3100_ops; max3100s[i]->port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF; max3100s[i]->port.line = i; + max3100s[i]->port.iotype = UPIO_BUS; max3100s[i]->port.type = PORT_MAX3100; max3100s[i]->port.dev = &spi->dev; From da7b5fd4e17f8e44c5590f2d603c01d499f056e6 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 13 Jul 2026 21:26:07 -0400 Subject: [PATCH 42/97] serial: 8250_hub6: add missing include for hub6_match_port() Add missing include to fix compile warning: drivers/tty/serial/8250/8250_hub6.c:44:6: warning: no previous prototype for 'hub6_match_port' [-Wmissing-prototypes] Fixes: 3d406299d882 ("serial: 8250_hub6: add hub6_match_port()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607110715.VGT2dVVz-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202607111219.QG9uOW8H-lkp@intel.com/ Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260714012610.576746-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_hub6.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/serial/8250/8250_hub6.c b/drivers/tty/serial/8250/8250_hub6.c index eae32c924e29..b6767633c966 100644 --- a/drivers/tty/serial/8250/8250_hub6.c +++ b/drivers/tty/serial/8250/8250_hub6.c @@ -7,6 +7,8 @@ #include #include +#include "8250.h" + #define HUB6(card, port) \ { \ .iobase = 0x302, \ From facd005eee65df088cd73733741fbcf8d989fc5e Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 17 Jul 2026 12:57:02 +0200 Subject: [PATCH 43/97] Revert "serial: qcom-geni: remove .pm callback, use runtime PM in startup/shutdown" This reverts commit 3d71f8d7eeb374d0eb84c64c6ffd68bdcc0d42d4. It causes lots of build problems both in linux-next and reported by the 0-day bot. Reported-by: Mark Brown Reported-by: kernel test robot Cc: Praveen Talari Closes: https://lore.kernel.org/oe-kbuild-all/202607110008.JQ2vBeKC-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202607150830.LsNxeVYw-lkp@intel.com/ Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 37 ++++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 53d221447557..e037af56a159 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1173,8 +1173,6 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) qcom_geni_serial_cancel_tx_cmd(uport); uart_port_unlock_irq(uport); - - pm_runtime_put_sync(uport->dev); } static void qcom_geni_serial_flush_buffer(struct uart_port *uport) @@ -1248,18 +1246,10 @@ static int qcom_geni_serial_startup(struct uart_port *uport) struct qcom_geni_serial_port *port = to_dev_port(uport); struct tty_port *tport = &uport->state->port; - ret = pm_runtime_resume_and_get(uport->dev); - if (ret < 0) { - dev_err(uport->dev, "Failed to resume and get %d\n", ret); - return ret; - } - if (!port->setup) { ret = qcom_geni_serial_port_setup(uport); - if (ret) { - pm_runtime_put_sync(uport->dev); + if (ret) return ret; - } } /* @@ -1741,6 +1731,22 @@ static int geni_serial_resource_init(struct uart_port *uport) return 0; } +static void qcom_geni_serial_pm(struct uart_port *uport, + unsigned int new_state, unsigned int old_state) +{ + + /* If we've never been called, treat it as off */ + if (old_state == UART_PM_STATE_UNDEFINED) + old_state = UART_PM_STATE_OFF; + + if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) + pm_runtime_resume_and_get(uport->dev); + else if (new_state == UART_PM_STATE_OFF && + old_state == UART_PM_STATE_ON) + pm_runtime_put_sync(uport->dev); + +} + /** * qcom_geni_rs485_config - Configure RS485 settings for the UART port * @uport: Pointer to the UART port structure @@ -1779,6 +1785,7 @@ static const struct uart_ops qcom_geni_console_pops = { .poll_put_char = qcom_geni_serial_poll_put_char, .poll_init = qcom_geni_serial_poll_init, #endif + .pm = qcom_geni_serial_pm, }; static const struct uart_ops qcom_geni_uart_pops = { @@ -1795,6 +1802,7 @@ static const struct uart_ops qcom_geni_uart_pops = { .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, + .pm = qcom_geni_serial_pm, }; static int qcom_geni_serial_probe(struct platform_device *pdev) @@ -1928,13 +1936,6 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) devm_pm_runtime_enable(port->se.dev); - PM_RUNTIME_ACQUIRE_IF_ENABLED(uport->dev, pm); - ret = PM_RUNTIME_ACQUIRE_ERR(&pm); - if (ret < 0) { - dev_err(uport->dev, "Failed to resume and get %d\n", ret); - goto error; - } - ret = uart_add_one_port(drv, uport); if (ret) goto error; From 7ab80d1e72431f5b7acbc55d83a23b76814957cf Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Wed, 15 Jul 2026 11:37:05 -0400 Subject: [PATCH 44/97] serial: 8250: fix compile error with hub6_match_port() when compiled as a module With CONFIG_SERIAL_8250_HUB6=m, we have the following compile error: ../drivers/tty/serial/8250/8250_hub6.c:46:6: error: redefinition of 'hub6_match_port' Fix hub6_match_port() prototype definition by using IS_REACHABLE() to support both built-in and module values, and substitute empty prototype otherwise. Fixes: 3d406299d8829 ("serial: 8250_hub6: add hub6_match_port()") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607150717.2YxVdWpX-lkp@intel.com/ Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260715153707.4181828-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index 9d1068d0489d..b62f88eec881 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -334,7 +335,7 @@ int fintek_8250_probe(struct uart_8250_port *uart); static inline int fintek_8250_probe(struct uart_8250_port *uart) { return 0; } #endif -#ifdef CONFIG_SERIAL_8250_HUB6 +#if IS_REACHABLE(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) From d7614cd72dc1680801936c1d468704426c2d142a Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 16 Jul 2026 17:12:10 -0400 Subject: [PATCH 45/97] serial: core: display 32 for MEM32 and MEM32BE UPIO types Displaying "32be" is wrong for UPIO_MEM32. Fix and simplify by displaying "32" for MEM32 and MEM32BE UPIO types. Fixes: 86305190f307 ("serial: uniformize serial port I/O infos display") Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260716211216.2583291-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 06d7f44643d6..edd1e7be2a5c 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -2492,7 +2492,7 @@ static const char *uart_get_mmio_width(struct uart_port *port) return "16"; case UPIO_MEM32: case UPIO_MEM32BE: - return "32be"; + return "32"; case UPIO_AU: case UPIO_MEM: default: From 6e5bd7cc3a2f304a66d294011647d82074421979 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Fri, 10 Jul 2026 22:42:13 +0530 Subject: [PATCH 46/97] serial: qcom-geni: Add tracepoints for Qualcomm GENI serial driver Add tracing to the Qualcomm GENI serial driver to improve runtime observability. Trace hooks are added at key points including termios and clock configuration, manual control get/set, interrupt handling, and data TX/RX paths. Reviewed-by: Konrad Dybcio Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260710-add-tracepoints-for-qcom-geni-serial-v6-2-2bb6b6836dfd@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index e037af56a159..751738f848ad 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -7,6 +7,9 @@ /* Disable MMIO tracing to prevent excessive logging of unwanted MMIO traces */ #define __DISABLE_TRACE_MMIO__ +#define CREATE_TRACE_POINTS +#include + #include #include #include @@ -228,7 +231,7 @@ static void qcom_geni_serial_config_port(struct uart_port *uport, int cfg_flags) static unsigned int qcom_geni_serial_get_mctrl(struct uart_port *uport) { unsigned int mctrl = TIOCM_DSR | TIOCM_CAR; - u32 geni_ios; + u32 geni_ios = 0; if (uart_console(uport)) { mctrl |= TIOCM_CTS; @@ -238,6 +241,8 @@ static unsigned int qcom_geni_serial_get_mctrl(struct uart_port *uport) mctrl |= TIOCM_CTS; } + trace_geni_serial_get_mctrl(uport->dev, mctrl, geni_ios); + return mctrl; } @@ -256,6 +261,8 @@ static void qcom_geni_serial_set_mctrl(struct uart_port *uport, if (port->manual_flow && !(mctrl & TIOCM_RTS) && !uport->suspended) uart_manual_rfr = UART_MANUAL_RFR_EN | UART_RFR_NOT_READY; writel(uart_manual_rfr, uport->membase + SE_UART_MANUAL_RFR); + + trace_geni_serial_set_mctrl(uport->dev, mctrl, uart_manual_rfr); } static const char *qcom_geni_serial_get_type(struct uart_port *uport) @@ -686,6 +693,8 @@ static void qcom_geni_serial_start_tx_dma(struct uart_port *uport) xmit_size = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, UART_XMIT_SIZE); + trace_geni_serial_tx_data(uport->dev, tail, xmit_size); + qcom_geni_set_rs485_mode(uport, SER_RS485_RTS_ON_SEND); qcom_geni_serial_setup_tx(uport, xmit_size); @@ -904,8 +913,10 @@ static void qcom_geni_serial_handle_rx_dma(struct uart_port *uport, bool drop) rx_in = readl(uport->membase + SE_DMA_RX_LEN_IN); if (!rx_in) dev_warn_ratelimited(uport->dev, "serial engine reports 0 RX bytes in!\n"); - else if (!drop) + else if (!drop) { + trace_geni_serial_rx_data(uport->dev, port->rx_buf, rx_in); handle_rx_uart(uport, rx_in); + } dma_sync_single_for_device(uport->dev->parent, port->rx_dma_addr, DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); @@ -1074,6 +1085,10 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) geni_status = readl(uport->membase + SE_GENI_STATUS); dma = readl(uport->membase + SE_GENI_DMA_MODE_EN); m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); + + trace_geni_serial_irq(uport->dev, m_irq_status, s_irq_status, + dma_tx_status, dma_rx_status); + writel(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR); writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); writel(dma_tx_status, uport->membase + SE_DMA_TX_IRQ_CLR); @@ -1298,8 +1313,8 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) return -EINVAL; } - dev_dbg(port->se.dev, "desired_rate = %u, clk_rate = %lu, clk_div = %u, clk_idx = %u\n", - baud * sampling_rate, clk_rate, clk_div, clk_idx); + trace_geni_serial_clk_cfg(uport->dev, baud * sampling_rate, clk_rate, + clk_div, clk_idx); uport->uartclk = clk_rate; port->clk_rate = clk_rate; @@ -1459,6 +1474,10 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport, writel(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN); writel(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN); writel(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN); + + trace_geni_serial_set_termios(uport->dev, baud, bits_per_char, + tx_trans_cfg, tx_parity_cfg, rx_trans_cfg, + rx_parity_cfg, stop_bit_len); } #ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE From dbe2afb952bdb782450d8f678adeb502791e7edd Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 15 Jul 2026 09:55:14 +0530 Subject: [PATCH 47/97] serial: qcom_geni: Add shutdown callback to quiesce hardware on reboot During system reboot, an active UART DMA transfer can leave the GENI Serial Engine in an indeterminate state. On VM-based platforms, if a DMA transfer is in progress when the VM is shut down, the SMMU can raise context faults as the DMA engine continues to access IOVAs that have already been invalidated during VM teardown. Add a shutdown callback to stop TX and RX and bring the hardware to idle before the system resets, preventing both hardware state corruption on reboot and SMMU faults during VM shutdown. The port lock is not taken here since shutdown runs from process context with the device already quiesced from the UART core's perspective; instead, the runtime PM status is checked so that TX/RX are only stopped while clocks and resources are still active, avoiding any register access once the device is runtime suspended. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260715-add_shutdown_and_panic_notifier_serial-v1-1-23e3787c7109@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 751738f848ad..04e1227390dd 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -2112,6 +2112,18 @@ static const struct dev_pm_ops qcom_geni_serial_pm_ops = { SYSTEM_SLEEP_PM_OPS(qcom_geni_serial_suspend, qcom_geni_serial_resume) }; +static void qcom_geni_serial_sys_shutdown(struct platform_device *pdev) +{ + struct qcom_geni_serial_port *port = platform_get_drvdata(pdev); + struct uart_port *uport = &port->uport; + + if (pm_runtime_status_suspended(uport->dev)) + return; + + qcom_geni_serial_stop_tx(uport); + qcom_geni_serial_stop_rx(uport); +} + static const struct of_device_id qcom_geni_serial_match_table[] = { #if IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE) { @@ -2138,6 +2150,7 @@ MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table); static struct platform_driver qcom_geni_serial_platform_driver = { .remove = qcom_geni_serial_remove, .probe = qcom_geni_serial_probe, + .shutdown = qcom_geni_serial_sys_shutdown, .driver = { .name = "qcom_geni_serial", .of_match_table = qcom_geni_serial_match_table, From 5d51a3958ae03e2eda5db9e32b4b224d17faf7d2 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Wed, 15 Jul 2026 09:55:15 +0530 Subject: [PATCH 48/97] serial: qcom_geni: Add panic notifier to stop UART on panic When a VM crashes with an active UART DMA transfer in progress, the SMMU raises context faults as the DMA engine continues to access IOVAs that are invalidated when the VM's memory context is torn down. These faults can affect other VMs sharing the same SMMU instance and obscure the root cause of the crash. Additionally, a stuck TX transfer on the panic console UART can cause the panic handler to stall, dropping the panic output. Register a panic notifier to stop TX and RX. The notifier does not take the port lock, since panic can be entered with the lock already held by the interrupted context, and there is no safe way to detect that here; instead, the device's runtime PM status is checked first so that TX/RX are only stopped while the hardware is still clocked and accessible, and the register accesses are skipped entirely once the device is runtime suspended. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260715-add_shutdown_and_panic_notifier_serial-v1-2-23e3787c7109@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 04e1227390dd..67b14fda4ff9 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,7 @@ struct qcom_geni_serial_port { struct qcom_geni_private_data private_data; const struct qcom_geni_device_data *dev_data; struct dev_pm_domain_list *pd_list; + struct notifier_block panic_nb; }; static const struct uart_ops qcom_geni_console_pops; @@ -1824,6 +1826,22 @@ static const struct uart_ops qcom_geni_uart_pops = { .pm = qcom_geni_serial_pm, }; +static int qcom_geni_serial_panic_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct qcom_geni_serial_port *port = + container_of(nb, struct qcom_geni_serial_port, panic_nb); + struct uart_port *uport = &port->uport; + + if (pm_runtime_status_suspended(uport->dev)) + return NOTIFY_OK; + + qcom_geni_serial_stop_tx(uport); + qcom_geni_serial_stop_rx(uport); + + return NOTIFY_OK; +} + static int qcom_geni_serial_probe(struct platform_device *pdev) { int ret = 0; @@ -1959,6 +1977,9 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) if (ret) goto error; + port->panic_nb.notifier_call = qcom_geni_serial_panic_notifier; + atomic_notifier_chain_register(&panic_notifier_list, &port->panic_nb); + return 0; error: @@ -1977,6 +1998,8 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) struct uart_port *uport = &port->uport; struct uart_driver *drv = port->private_data.drv; + atomic_notifier_chain_unregister(&panic_notifier_list, &port->panic_nb); + dev_pm_clear_wake_irq(&pdev->dev); device_init_wakeup(&pdev->dev, false); ida_free(&port_ida, uport->line); From a5ef89f7a8cda4c58bbe3dc68a475d446e5422bc Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 14 Jul 2026 13:20:36 -0600 Subject: [PATCH 49/97] dt-bindings: serial: snps-dw-apb-uart: Add RV1106 compatible Add the compatible for the UARTs of the Rockchip RV1106, which are compatible with the Synopsys DesignWare APB UART. Signed-off-by: Simon Glass Reviewed-by: Heiko Stuebner Link: https://patch.msgid.link/20260714132035.v2.1.c1d92213393f49330ec14d0c670a802181b4fcbf@changeid 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 49f51b002879..c0d0524458c1 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -72,6 +72,7 @@ properties: - rockchip,rk3576-uart - rockchip,rk3588-uart - rockchip,rv1103b-uart + - rockchip,rv1106-uart - rockchip,rv1108-uart - rockchip,rv1126-uart - sophgo,sg2044-uart From 782f4dbd1794b4f30dc116a7ca42c5962c409be8 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 17 Jul 2026 09:16:16 +0200 Subject: [PATCH 50/97] tty: hvc: restrict HVC_DCC to ARMv6+ and ARM64 hvc_dcc drives the JTAG DCC via the ARMv6/v7 CP14 debug registers (mrc/mcr p14, 0, rX, c0, c1/c5, 0 in asm/dcc.h). That encoding is undefined on older ARM cores, and also on ARMv7-M, but HVC_DCC only depends on ARM, so it can be enabled on e.g. ARM926 (ARCH_MULTI_V5), where hvc_dcc_console_init() runs __dcc_putchar() at boot and takes an undefined-instruction trap before the console is up: Internal error: Oops - undefined instruction: 0 [#1] ARM PC is at hvc_dcc_check+0x50/0x8c hvc_dcc_check from hvc_dcc_console_init+0x18/0x48 hvc_dcc_console_init from console_init+0x58/0x170 Kernel panic - not syncing: Fatal exception Restrict HVC_DCC to the CPUs where that encoding is valid: the CPU_V6 || CPU_V6K || CPU_V7 set that arch/arm/include/debug/icedcc.S guards it with, plus ARM64. Fixes: 16c63f8ea49c ("drivers: char: hvc: add arm JTAG DCC console support") Signed-off-by: Karl Mehltretter Reviewed-by: Arnd Bergmann Link: https://patch.msgid.link/20260717071616.91423-1-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/hvc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig index c2a4e88b328f..5866195de26a 100644 --- a/drivers/tty/hvc/Kconfig +++ b/drivers/tty/hvc/Kconfig @@ -79,7 +79,7 @@ config HVC_UDBG config HVC_DCC bool "ARM JTAG DCC console" - depends on ARM || ARM64 + depends on (ARM && (CPU_V6 || CPU_V6K || CPU_V7)) || ARM64 select HVC_DRIVER select SERIAL_CORE_CONSOLE help From 57c0741b8c15b93ba4aa92c6618cde6f3f4115b2 Mon Sep 17 00:00:00 2001 From: Fushuai Wang Date: Fri, 24 Jul 2026 17:31:51 +0800 Subject: [PATCH 51/97] Revert "serial: 8250: Clear CON_PRINTBUFFER on port re-registration" This reverts commit d338ab1d90603f875c4f7ed223406535378173a5. uart_console() only indicates that the port is selected as the console. It does not mean that the console has already been registered or has printed the buffered messages. On platforms where an initial 8250 port is replaced when the real UART device is registered, clearing CON_PRINTBUFFER causes the console to start at the end of the printk ring buffer. Without earlycon, all messages logged before UART registration are therefore lost. Fixes: d338ab1d9060 ("serial: 8250: Clear CON_PRINTBUFFER on port re-registration") Reported-by: Mark Brown Reported-by: Anirudh Srinivasan Link: https://lore.kernel.org/all/20260522101042.21976-1-fushuai.wang@linux.dev/ Signed-off-by: Fushuai Wang Reviewed-by: John Ogness Link: https://patch.msgid.link/20260724093151.53216-1-fushuai.wang@linux.dev Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index c0e8a4efbdcc..f49862d90eeb 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -720,12 +720,8 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) /* Preserve specified console flow control. */ cons_flow = uart_cons_flow_enabled(&uart->port); - if (uart->port.dev) { - if (uart_console(&uart->port)) - uart->port.cons->flags &= ~CON_PRINTBUFFER; - + if (uart->port.dev) uart_remove_one_port(&serial8250_reg, &uart->port); - } uart->port.ctrl_id = up->port.ctrl_id; uart->port.port_id = up->port.port_id; From 348f045f29637352996b91744d0c61f581173627 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Tue, 21 Jul 2026 10:44:17 -0400 Subject: [PATCH 52/97] serial: max310x: remove always included kconfig.h The inclusion of in commit f18643843bc6 ("serial: max310x: fix compile errors if CONFIG_SPI_MASTER is disabled") is unneeded as it's guaranteed by the build starting from the commit 2a11c8ea20bf ("kconfig: Introduce IS_ENABLED(), IS_BUILTIN() and IS_MODULE()"). Remove it here. Suggested-by: Andy Shevchenko Signed-off-by: Hugo Villeneuve Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260721144420.3727708-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/max310x.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 41a7bff941c1..9e62778e4191 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include From 050f2ba1cbe510813ca979ce00ed386d118d13a9 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Tue, 21 Jul 2026 10:48:32 -0400 Subject: [PATCH 53/97] serial: 8250: remove always included kconfig.h The inclusion of in commit 7ab80d1e72431 ("serial: 8250: fix compile error with hub6_match_port() when compiled as a module") is unneeded as it's guaranteed by the build starting from commit 2a11c8ea20bf ("kconfig: Introduce IS_ENABLED(), IS_BUILTIN() and IS_MODULE()"). Remove it here. Suggested-by: Andy Shevchenko Signed-off-by: Hugo Villeneuve Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260721144847.3728422-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index b62f88eec881..f72c7e7bb94f 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -8,7 +8,6 @@ */ #include -#include #include #include #include From d3539347022ad4eeb9dbd29c50bfca17b9d8a146 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Wed, 29 Jul 2026 14:10:33 +0206 Subject: [PATCH 54/97] serial: 8250: Switch to nbcon console, take 2 Implement the necessary callbacks to switch the 8250 console driver to perform as an nbcon console. Add implementations for the nbcon console callbacks: ->write_atomic() ->write_thread() ->device_lock() ->device_unlock() and add CON_NBCON to the initial @flags. All hardware access in the callbacks is within unsafe sections. The ->write_atomic() and ->write_thread() callbacks allow safe handover/takeover per byte and add a preceding newline if they take over from another context mid-line. For the ->write_atomic() callback, a new irq_work is used to defer modem control since it may be called from a context that does not allow waking up tasks. During suspend/resume the irq_work is not used as this has been shown to cause suspend problems for some hardware. Upon resume, any pending modem control is performed. Note: A new __serial8250_clear_IER() is introduced for direct clearing of UART_IER during console writing (which will not be holding the port lock for atomic printing or KDB/KGDB). This allows restoring a lockdep check to serial8250_clear_IER() in a follow-up commit. Signed-off-by: John Ogness Link: https://patch.msgid.link/20260729120439.281252-2-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250.h | 4 +- drivers/tty/serial/8250/8250_core.c | 65 ++++++-- drivers/tty/serial/8250/8250_dw.c | 2 +- drivers/tty/serial/8250/8250_port.c | 242 +++++++++++++++++++++++----- include/linux/serial_8250.h | 16 +- 5 files changed, 274 insertions(+), 55 deletions(-) diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h index f72c7e7bb94f..9337fec9394e 100644 --- a/drivers/tty/serial/8250/8250.h +++ b/drivers/tty/serial/8250/8250.h @@ -177,7 +177,9 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up, void serial8250_clear_fifos(struct uart_8250_port *p); void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p); -void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count); +void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, + unsigned int count); 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_core.c b/drivers/tty/serial/8250/8250_core.c index f49862d90eeb..2f569c370856 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -390,12 +390,34 @@ void __init serial8250_register_ports(struct uart_driver *drv, struct device *de #ifdef CONFIG_SERIAL_8250_CONSOLE -static void univ8250_console_write(struct console *co, const char *s, - unsigned int count) +static void univ8250_console_write_atomic(struct console *co, + struct nbcon_write_context *wctxt) { struct uart_8250_port *up = &serial8250_ports[co->index]; - serial8250_console_write(up, s, count); + serial8250_console_write(up, wctxt, true); +} + +static void univ8250_console_write_thread(struct console *co, + struct nbcon_write_context *wctxt) +{ + struct uart_8250_port *up = &serial8250_ports[co->index]; + + serial8250_console_write(up, wctxt, false); +} + +static void univ8250_console_device_lock(struct console *co, unsigned long *flags) +{ + struct uart_port *up = &serial8250_ports[co->index].port; + + __uart_port_lock_irqsave(up, flags); +} + +static void univ8250_console_device_unlock(struct console *co, unsigned long flags) +{ + struct uart_port *up = &serial8250_ports[co->index].port; + + __uart_port_unlock_irqrestore(up, flags); } static int univ8250_console_setup(struct console *co, char *options) @@ -496,12 +518,15 @@ static int univ8250_console_match(struct console *co, char *name, int idx, static struct console univ8250_console = { .name = "ttyS", - .write = univ8250_console_write, + .write_atomic = univ8250_console_write_atomic, + .write_thread = univ8250_console_write_thread, + .device_lock = univ8250_console_device_lock, + .device_unlock = univ8250_console_device_unlock, .device = uart_console_device, .setup = univ8250_console_setup, .exit = univ8250_console_exit, .match = univ8250_console_match, - .flags = CON_PRINTBUFFER | CON_ANYTIME, + .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_NBCON, .index = -1, .data = &serial8250_reg, }; @@ -584,13 +609,19 @@ void serial8250_suspend_port(int line) struct uart_8250_port *up = &serial8250_ports[line]; struct uart_port *port = &up->port; - if (!console_suspend_enabled && uart_console(port) && - port->type != PORT_8250) { - unsigned char canary = 0xa5; + if (uart_console(port)) { + /* No irq_work may be queued when suspending */ + scoped_guard(uart_port_lock_irq, port) + up->console_msr_work_allow = false; + irq_work_sync(&up->console_msr_work); - serial_out(up, UART_SCR, canary); - if (serial_in(up, UART_SCR) == canary) - up->canary = canary; + if (!console_suspend_enabled && port->type != PORT_8250) { + unsigned char canary = 0xa5; + + serial_out(up, UART_SCR, canary); + if (serial_in(up, UART_SCR) == canary) + up->canary = canary; + } } uart_suspend_port(&serial8250_reg, port); @@ -620,6 +651,18 @@ void serial8250_resume_port(int line) port->uartclk = 921600*16; } uart_resume_port(&serial8250_reg, port); + + if (uart_console(port)) { + + guard(uart_port_lock_irq)(port); + + /* irq_work allowed again */ + up->console_msr_work_allow = true; + + /* Handle any pending MSR changes */ + if (up->msr_saved_flags) + serial8250_modem_status(up); + } } EXPORT_SYMBOL(serial8250_resume_port); diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index 5fba913f3301..51d026f20825 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -156,7 +156,7 @@ static int dw8250_idle_enter(struct uart_port *p) * * FIXME: frame_time delay is too long with very low baudrates. */ - serial8250_fifo_wait_for_lsr_thre(up, p->fifosize); + serial8250_fifo_wait_for_lsr_thre(up, NULL, p->fifosize); ndelay(p->frame_time); serial_port_out(p, UART_MCR, up->mcr | UART_MCR_LOOP); diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 8c241ec7f4f2..b9ea4f898474 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -704,7 +704,12 @@ static void serial8250_clear_interrupts(struct uart_port *port) serial_port_in(port, UART_MSR); } -static void serial8250_clear_IER(struct uart_8250_port *up) +/* + * Only to be directly used by serial8250_console_write() and + * serial8250_put_poll_char(), which do not require the port lock. + * Use serial8250_clear_IER() instead for all other cases. + */ +static void __serial8250_clear_IER(struct uart_8250_port *up) { if (up->capabilities & UART_CAP_UUE) serial_out(up, UART_IER, UART_IER_UUE); @@ -712,6 +717,11 @@ static void serial8250_clear_IER(struct uart_8250_port *up) serial_out(up, UART_IER, 0); } +static inline void serial8250_clear_IER(struct uart_8250_port *up) +{ + __serial8250_clear_IER(up); +} + /* * This is a quickie test to see how big the FIFO is. * It doesn't work at all the time, more's the pity. @@ -1284,9 +1294,6 @@ void serial8250_em485_stop_tx(struct uart_8250_port *p, bool toggle_ier) { unsigned char mcr = serial8250_in_MCR(p); - /* Port locked to synchronize UART_IER access against the console. */ - lockdep_assert_held_once(&p->port.lock); - if (p->port.rs485.flags & SER_RS485_RTS_AFTER_SEND) mcr |= UART_MCR_RTS; else @@ -1302,6 +1309,16 @@ void serial8250_em485_stop_tx(struct uart_8250_port *p, bool toggle_ier) serial8250_clear_and_reinit_fifos(p); if (toggle_ier) { + /* + * Port locked to synchronize UART_IER access against + * the console. The lockdep_assert must be restricted + * to this condition because only here is it + * guaranteed that the port lock is held. The other + * hardware access in this function is synchronized + * by console ownership. + */ + lockdep_assert_held_once(&p->port.lock); + p->ier |= UART_IER_RLSI | UART_IER_RDI; serial_port_out(&p->port, UART_IER, p->ier); } @@ -2053,10 +2070,13 @@ static void serial8250_put_poll_char(struct uart_port *port, guard(serial8250_rpm)(up); /* - * First save the IER then disable the interrupts + * First, save the IER, then disable the interrupts. The special + * variant to clear the IER is used because KDB/KGDB printing + * is synchronized via CPU quiescence without holding the port + * lock. */ ier = serial_port_in(port, UART_IER); - serial8250_clear_IER(up); + __serial8250_clear_IER(up); wait_for_xmitr(up, UART_LSR_BOTH_EMPTY); /* @@ -3198,13 +3218,32 @@ void serial8250_set_defaults(struct uart_8250_port *up) } EXPORT_SYMBOL_GPL(serial8250_set_defaults); -void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count) +void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, + unsigned int count) { unsigned int i; + /* + * For console writing, enter/exit an unsafe section for each byte + * in order to pass the ownership as quickly as possible if a higher + * priority context wants ownership. Otherwise, an attempt to take + * over the ownership might timeout. The new owner will wait for + * UART_LSR_THRE before reusing the fifo. + */ for (i = 0; i < count; i++) { - if (wait_for_lsr(up, UART_LSR_THRE)) + bool tx_ready; + + if (wctxt && !nbcon_enter_unsafe(wctxt)) return; + + tx_ready = wait_for_lsr(up, UART_LSR_THRE); + + if (wctxt) + nbcon_exit_unsafe(wctxt); + + if (tx_ready) + break; } } EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250"); @@ -3213,7 +3252,11 @@ EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250"); static void serial8250_console_putchar(struct uart_port *port, unsigned char ch) { + struct uart_8250_port *up = up_to_u8250p(port); + serial_port_out(port, UART_TX, ch); + + up->console_line_ended = (ch == '\n'); } static void serial8250_console_wait_putchar(struct uart_port *port, unsigned char ch) @@ -3256,8 +3299,9 @@ static void serial8250_console_restore(struct uart_8250_port *up) * It sends fifosize bytes and then waits for the fifo * to get empty. */ -static void serial8250_console_fifo_write(struct uart_8250_port *up, - const char *s, unsigned int count) +static void __serial8250_console_fifo_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, + const char *s, unsigned int count) { const char *end = s + count; unsigned int fifosize = up->tx_loadsz; @@ -3268,9 +3312,16 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up, while (s != end) { /* Allow timeout for each byte of a possibly full FIFO */ - serial8250_fifo_wait_for_lsr_thre(up, fifosize); + serial8250_fifo_wait_for_lsr_thre(up, wctxt, fifosize); + /* + * Fill the FIFO. If a handover or takeover occurs, writing + * must be aborted since the string data is no longer valid. + */ for (i = 0; i < fifosize && s != end; ++i) { + if (!nbcon_enter_unsafe(wctxt)) + return; + if (*s == '\n' && !cr_sent) { serial8250_console_putchar(port, '\r'); cr_sent = true; @@ -3278,6 +3329,8 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up, serial8250_console_putchar(port, *s++); cr_sent = false; } + + nbcon_exit_unsafe(wctxt); } tx_count = i; } @@ -3286,39 +3339,92 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up, * Allow timeout for each byte written since the caller will only wait * for UART_LSR_BOTH_EMPTY using the timeout of a single character */ - serial8250_fifo_wait_for_lsr_thre(up, tx_count); + serial8250_fifo_wait_for_lsr_thre(up, wctxt, tx_count); +} + +static void serial8250_console_fifo_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt) +{ + __serial8250_console_fifo_write(up, wctxt, wctxt->outbuf, wctxt->len); +} + +static void __serial8250_console_byte_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, + const char *s, unsigned int count) +{ + struct uart_port *port = &up->port; + const char *end = s + count; + + /* + * Write out the message. If a handover or takeover occurs, writing + * must be aborted since the string data is no longer valid. + */ + while (s != end) { + if (!nbcon_enter_unsafe(wctxt)) + return; + + uart_console_write(port, s++, 1, serial8250_console_wait_putchar); + + nbcon_exit_unsafe(wctxt); + } +} + +static void serial8250_console_byte_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt) +{ + __serial8250_console_byte_write(up, wctxt, wctxt->outbuf, wctxt->len); } /* - * Print a string to the serial port trying not to disturb - * any possible real use of the port... - * - * The console_lock must be held when we get here. - * - * Doing runtime PM is really a bad idea for the kernel console. - * Thus, we assume the function is called when device is powered up. + * Print the console line using the appropriate variant. If ownership is lost + * at any time during printing, the printing is aborted. */ -void serial8250_console_write(struct uart_8250_port *up, const char *s, - unsigned int count) +static void __serial8250_console_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, + bool use_fifo) +{ + /* + * If the console printer did not fully output the previous line, it + * must have been handed or taken over. Insert a newline in order to + * maintain clean output. + */ + if (!up->console_line_ended) { + if (use_fifo) + __serial8250_console_fifo_write(up, wctxt, "\n", 1); + else + __serial8250_console_byte_write(up, wctxt, "\n", 1); + } + + if (use_fifo) + serial8250_console_fifo_write(up, wctxt); + else + serial8250_console_byte_write(up, wctxt); +} + +/* + * Print a string to the serial port trying not to disturb + * any possible real use of the port... + */ +void serial8250_console_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, + bool is_atomic) { struct uart_8250_em485 *em485 = up->em485; struct uart_port *port = &up->port; - unsigned long flags; - unsigned int ier, use_fifo; - int locked = 1; + unsigned int ier; + bool use_fifo; - touch_nmi_watchdog(); - - if (oops_in_progress) - locked = uart_port_trylock_irqsave(port, &flags); - else - uart_port_lock_irqsave(port, &flags); + if (!nbcon_enter_unsafe(wctxt)) + return; /* - * First save the IER then disable the interrupts + * First, save the IER, then disable the interrupts. The special + * variant to clear the IER is used because emergency and panic + * printing is synchronized only by nbcon ownership without + * holding the port lock. */ ier = serial_port_in(port, UART_IER); - serial8250_clear_IER(up); + __serial8250_clear_IER(up); /* check scratch reg to see if port powered off during system sleep */ if (up->canary && (up->canary != serial_port_in(port, UART_SCR))) { @@ -3352,10 +3458,17 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s, */ !uart_console_hwflow_active(&up->port); - if (likely(use_fifo)) - serial8250_console_fifo_write(up, s, count); - else - uart_console_write(port, s, count, serial8250_console_wait_putchar); + nbcon_exit_unsafe(wctxt); + + __serial8250_console_write(up, wctxt, use_fifo); + + /* + * Re-enter an unsafe section in order to perform final actions + * (such as re-enabling interrupts). If ownership was lost, this + * context must reacquire ownership. + */ + while (!nbcon_enter_unsafe(wctxt)) + nbcon_reacquire_nobuf(wctxt); /* * Finally, wait for transmitter to become empty @@ -3365,10 +3478,21 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s, if (em485) { mdelay(port->rs485.delay_rts_after_send); + + /* Toggle unsafe after possibly long delay */ + nbcon_exit_unsafe(wctxt); + while (!nbcon_enter_unsafe(wctxt)) + nbcon_reacquire_nobuf(wctxt); + if (em485->tx_stopped) up->rs485_stop_tx(up, false); } + /* Toggle unsafe after possibly long delay */ + nbcon_exit_unsafe(wctxt); + while (!nbcon_enter_unsafe(wctxt)) + nbcon_reacquire_nobuf(wctxt); + serial_port_out(port, UART_IER, ier); /* @@ -3378,11 +3502,25 @@ void serial8250_console_write(struct uart_8250_port *up, const char *s, * call it if we have saved something in the saved flags * while processing with interrupts off. */ - if (up->msr_saved_flags) - serial8250_modem_status(up); + if (up->msr_saved_flags) { + if (is_atomic) { + /* + * For atomic, MSR handling must be deferred to + * irq_work because this may be a context that does + * not permit waking up tasks. + * + * But no irq_work may be queued when suspending. + * In that case, the MSR handling will occur during + * resume in serial8250_resume_port(). + */ + if (up->console_msr_work_allow) + irq_work_queue(&up->console_msr_work); + } else { + serial8250_modem_status(up); + } + } - if (locked) - uart_port_unlock_irqrestore(port, flags); + nbcon_exit_unsafe(wctxt); } static unsigned int probe_baud(struct uart_port *port) @@ -3400,8 +3538,24 @@ static unsigned int probe_baud(struct uart_port *port) return (port->uartclk / 16) / quot; } +/* + * irq_work handler to perform modem control during console output. + * Only triggered via ->write_atomic() callback because it may be + * in a scheduler or NMI context, unable to wake tasks. + */ +static void console_msr_handler(struct irq_work *iwp) +{ + struct uart_8250_port *up = container_of(iwp, struct uart_8250_port, console_msr_work); + struct uart_port *port = &up->port; + + guard(uart_port_lock)(port); + + serial8250_modem_status(up); +} + int serial8250_console_setup(struct uart_port *port, char *options, bool probe) { + struct uart_8250_port *up = up_to_u8250p(port); int baud = 9600; int bits = 8; int parity = 'n'; @@ -3411,6 +3565,10 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe) if (!port->iobase && !port->membase) return -ENODEV; + up->console_line_ended = true; + up->console_msr_work_allow = true; + init_irq_work(&up->console_msr_work, console_msr_handler); + if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); else if (probe) @@ -3431,6 +3589,10 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe) int serial8250_console_exit(struct uart_port *port) { + struct uart_8250_port *up = up_to_u8250p(port); + + irq_work_sync(&up->console_msr_work); + if (port->dev) pm_runtime_put_sync(port->dev); diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index a95b2d143d24..eba36710bc47 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -150,8 +150,20 @@ struct uart_8250_port { #define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS u16 lsr_saved_flags; u16 lsr_save_mask; + + /* + * Track when a console line has been fully written to the + * hardware, i.e. true when the most recent byte written to + * UART_TX by the console was '\n'. + */ + bool console_line_ended; + + /* Allow queuing irq_work for MSR handling */ + bool console_msr_work_allow; + #define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA unsigned char msr_saved_flags; + struct irq_work console_msr_work; struct uart_8250_dma *dma; const struct uart_8250_ops *ops; @@ -203,8 +215,8 @@ void serial8250_tx_chars(struct uart_8250_port *up); unsigned int serial8250_modem_status(struct uart_8250_port *up); void serial8250_init_port(struct uart_8250_port *up); void serial8250_set_defaults(struct uart_8250_port *up); -void serial8250_console_write(struct uart_8250_port *up, const char *s, - unsigned int count); +void serial8250_console_write(struct uart_8250_port *up, + struct nbcon_write_context *wctxt, bool in_atomic); int serial8250_console_setup(struct uart_port *port, char *options, bool probe); int serial8250_console_exit(struct uart_port *port); From bbbb07f9fad6518090b7171dd8f5f737d8ddb63b Mon Sep 17 00:00:00 2001 From: John Ogness Date: Wed, 29 Jul 2026 14:10:34 +0206 Subject: [PATCH 55/97] Revert "serial: 8250: drop lockdep annotation from serial8250_clear_IER()" This reverts commit 3d9e6f556e235ddcdc9f73600fdd46fe1736b090. The 8250 driver no longer depends on @oops_in_progress and will no longer violate the port->lock locking constraints. Signed-off-by: John Ogness Reviewed-by: Petr Mladek Link: https://patch.msgid.link/20260729120439.281252-3-john.ogness@linutronix.de Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_port.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index b9ea4f898474..6c10bff10970 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -719,6 +719,9 @@ static void __serial8250_clear_IER(struct uart_8250_port *up) static inline void serial8250_clear_IER(struct uart_8250_port *up) { + /* Port locked to synchronize UART_IER access against the console */ + lockdep_assert_held_once(&up->port.lock); + __serial8250_clear_IER(up); } From bd0e2d9dbbe934e9c9d972b2c0c06234212f8219 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 20 Jul 2026 15:51:44 -0400 Subject: [PATCH 56/97] serial: rsci: remove check for zero baud rate from uart_get_baud_rate() The minimum baud rate supported by this driver is 0, so even for the B0 case, uart_get_baud_rate() will return 9600, not zero. This check is no longer necessary since commit 16ae2a877bf4 ("serial: Fix crash if the minimum rate of the device is > 9600 baud") so remove it. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260720195147.3630241-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/rsci.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/tty/serial/rsci.c b/drivers/tty/serial/rsci.c index b00c9e385169..40db9daa4272 100644 --- a/drivers/tty/serial/rsci.c +++ b/drivers/tty/serial/rsci.c @@ -265,8 +265,6 @@ static void rsci_set_termios(struct uart_port *port, struct ktermios *termios, } baud = uart_get_baud_rate(port, termios, old, 0, max_freq); - if (!baud) - goto done; /* Divided Functional Clock using standard Bit Rate Register */ err = sci_scbrr_calc(s, baud, &brr1, &srr1, &cks1); @@ -278,7 +276,6 @@ static void rsci_set_termios(struct uart_port *port, struct ktermios *termios, cks = cks1; } -done: if (best_clk >= 0) dev_dbg(port->dev, "Using clk %pC for %u%+d bps\n", s->clks[best_clk], baud, min_err); From c982f96e868469a9ebdc5c3b2146f999b9b6f993 Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Mon, 20 Jul 2026 15:34:07 -0400 Subject: [PATCH 57/97] serial: sh-sci: remove check for zero baud rate from uart_get_baud_rate() The minimum baud rate supported by this driver is 0, so even for the B0 case, uart_get_baud_rate() will return 9600, not zero. This check is no longer necessary since commit 16ae2a877bf4 ("serial: Fix crash if the minimum rate of the device is > 9600 baud") so remove it. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260720193411.3517484-1-hugo@hugovil.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sh-sci.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 787e7cdc5e9c..92193eb26abf 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -2719,8 +2719,6 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios, max_freq = max(max_freq, s->clk_rates[i]); baud = uart_get_baud_rate(port, termios, old, 0, max_freq / min_sr(s)); - if (!baud) - goto done; /* * There can be multiple sources for the sampling clock. Find the one From b6932e768405eeada3862951339bf968fc1e725f Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Wed, 22 Jul 2026 10:55:53 +0100 Subject: [PATCH 58/97] dt-bindings: serial: snps-dw-apb-uart: Add "google,lga-uart" The Google Tensor G5 SoC (known as "laguna" and canonically written in code as "lga") has a UART based on Designware IP. The UART appears to work reasonably well, at least for serial console, with the existing driver in Linux. Add a compatible for this UART based on the canonical "lga" name for this SoC with a fallback to the existing "snps,dw-apb-uart". Signed-off-by: Douglas Anderson Reviewed-by: Krzysztof Kozlowski Signed-off-by: Peter Griffin Link: https://patch.msgid.link/20260722-contrib-pg-pixel10-initial-dts-v2-2-3abae9717feb@linaro.org 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 c0d0524458c1..664305de4986 100644 --- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml +++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml @@ -54,6 +54,7 @@ properties: - anlogic,dr1v90-uart - brcm,bcm11351-dw-apb-uart - brcm,bcm21664-dw-apb-uart + - google,lga-uart - rockchip,px30-uart - rockchip,rk1808-uart - rockchip,rk3036-uart From b1b89b3df638bc1c46dd71f4f8a29753245d99d6 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Thu, 23 Jul 2026 14:34:09 +0800 Subject: [PATCH 59/97] serial: bcm63xx-uart: silence false positive coccinelle warning on clk_put Coccinelle warns about missing clk_put on the error path after clk_get, but the error path returns with an ERR_PTR where clk_put must not be called. Restructure into a single if block so the logic is clear to silence false positive coccinelle warning. Commit 580d952e44de ("tty: serial: bcm63xx: fix missing clk_put() in bcm63xx_uart") previously tried to fix this same warning by adding a clk_put, which was reverted because it was wrong. Prevent anyone from making the same mistake again. Signed-off-by: Pei Xiao Link: https://patch.msgid.link/604886147edb67c3ed85b192eb3f7a4a6dd0f0ac.1784788388.git.xiaopei01@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/bcm63xx_uart.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/tty/serial/bcm63xx_uart.c b/drivers/tty/serial/bcm63xx_uart.c index 544695cb184c..1754cf252b7c 100644 --- a/drivers/tty/serial/bcm63xx_uart.c +++ b/drivers/tty/serial/bcm63xx_uart.c @@ -838,11 +838,12 @@ static int bcm_uart_probe(struct platform_device *pdev) port->irq = ret; clk = clk_get(&pdev->dev, "refclk"); - if (IS_ERR(clk) && pdev->dev.of_node) - clk = of_clk_get(pdev->dev.of_node, 0); - - if (IS_ERR(clk)) - return -ENODEV; + if (IS_ERR(clk)) { + if (pdev->dev.of_node) + clk = of_clk_get(pdev->dev.of_node, 0); + if (IS_ERR(clk)) + return -ENODEV; + } port->iotype = UPIO_MEM; port->ops = &bcm_uart_ops; From 1654731536344f88e9bfbc0641e32980bf3af94e Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 11:43:29 +0800 Subject: [PATCH 60/97] serial: 8250_bcm7271: Remove redundant dev_err_probe() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err_probe() call. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260722034342.316755-2-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_bcm7271.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/tty/serial/8250/8250_bcm7271.c b/drivers/tty/serial/8250/8250_bcm7271.c index 742004d63c6f..cb7d594d263e 100644 --- a/drivers/tty/serial/8250/8250_bcm7271.c +++ b/drivers/tty/serial/8250/8250_bcm7271.c @@ -1099,10 +1099,8 @@ static int brcmuart_probe(struct platform_device *pdev) } ret = devm_request_irq(dev, dma_irq, brcmuart_isr, IRQF_SHARED, "uart DMA irq", &new_port->port); - if (ret) { - dev_err_probe(dev, ret, "unable to register IRQ handler\n"); + if (ret) goto err1; - } } platform_set_drvdata(pdev, priv); brcmuart_init_debugfs(priv, dev_name(&pdev->dev)); From 651bc1d066184338f03625715767f31c053c6302 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 11:43:30 +0800 Subject: [PATCH 61/97] serial: imx: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260722034342.316755-3-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 251a50c8aa38..95fdb60e06a7 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -2602,34 +2602,23 @@ static int imx_uart_probe(struct platform_device *pdev) if (txirq > 0) { ret = devm_request_irq(&pdev->dev, rxirq, imx_uart_rxint, 0, dev_name(&pdev->dev), sport); - if (ret) { - dev_err(&pdev->dev, "failed to request rx irq: %d\n", - ret); + if (ret) goto err_clk; - } ret = devm_request_irq(&pdev->dev, txirq, imx_uart_txint, 0, dev_name(&pdev->dev), sport); - if (ret) { - dev_err(&pdev->dev, "failed to request tx irq: %d\n", - ret); + if (ret) goto err_clk; - } ret = devm_request_irq(&pdev->dev, rtsirq, imx_uart_rtsint, 0, dev_name(&pdev->dev), sport); - if (ret) { - dev_err(&pdev->dev, "failed to request rts irq: %d\n", - ret); + if (ret) goto err_clk; - } } else { ret = devm_request_irq(&pdev->dev, rxirq, imx_uart_int, 0, dev_name(&pdev->dev), sport); - if (ret) { - dev_err(&pdev->dev, "failed to request irq: %d\n", ret); + if (ret) goto err_clk; - } } imx_uart_ports[sport->port.line] = sport; From d5760123484ba05767913cd03cc7588939ec27a5 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 11:43:31 +0800 Subject: [PATCH 62/97] serial: mvebu-uart: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() calls. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260722034342.316755-4-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/mvebu-uart.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/tty/serial/mvebu-uart.c b/drivers/tty/serial/mvebu-uart.c index 8e52be2b34ea..33ea496c93b2 100644 --- a/drivers/tty/serial/mvebu-uart.c +++ b/drivers/tty/serial/mvebu-uart.c @@ -404,29 +404,21 @@ static int mvebu_uart_startup(struct uart_port *port) ret = devm_request_irq(port->dev, mvuart->irq[UART_IRQ_SUM], mvebu_uart_isr, port->irqflags, dev_name(port->dev), port); - if (ret) { - dev_err(port->dev, "unable to request IRQ %d\n", - mvuart->irq[UART_IRQ_SUM]); + if (ret) return ret; - } } else { /* New bindings with an IRQ for RX and TX (both UART) */ ret = devm_request_irq(port->dev, mvuart->irq[UART_RX_IRQ], mvebu_uart_rx_isr, port->irqflags, dev_name(port->dev), port); - if (ret) { - dev_err(port->dev, "unable to request IRQ %d\n", - mvuart->irq[UART_RX_IRQ]); + if (ret) return ret; - } ret = devm_request_irq(port->dev, mvuart->irq[UART_TX_IRQ], mvebu_uart_tx_isr, port->irqflags, dev_name(port->dev), port); if (ret) { - dev_err(port->dev, "unable to request IRQ %d\n", - mvuart->irq[UART_TX_IRQ]); devm_free_irq(port->dev, mvuart->irq[UART_RX_IRQ], port); return ret; From 4890e5d48d4f62a1f8e0547854b7577e98d336f2 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 11:43:32 +0800 Subject: [PATCH 63/97] serial: mctrl_gpio: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260722034342.316755-5-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_mctrl_gpio.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/tty/serial/serial_mctrl_gpio.c b/drivers/tty/serial/serial_mctrl_gpio.c index 7b02c5ca4afd..dff13a9f2614 100644 --- a/drivers/tty/serial/serial_mctrl_gpio.c +++ b/drivers/tty/serial/serial_mctrl_gpio.c @@ -256,9 +256,6 @@ struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx) gpios); if (ret) { /* alternatively implement polling */ - dev_err(port->dev, - "failed to request irq for %s (idx=%d, err=%d)\n", - mctrl_gpios_desc[i].name, idx, ret); return ERR_PTR(ret); } } From 846a6422d2a8dc4e8d20744b9ae9b681a96b36cb Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 11:43:33 +0800 Subject: [PATCH 64/97] serial: sprd: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Link: https://patch.msgid.link/20260722034342.316755-6-panchuang@vivo.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/sprd_serial.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c index 092755f35683..3fdf5939a62c 100644 --- a/drivers/tty/serial/sprd_serial.c +++ b/drivers/tty/serial/sprd_serial.c @@ -741,11 +741,8 @@ static int sprd_startup(struct uart_port *port) ret = devm_request_irq(port->dev, port->irq, sprd_handle_irq, IRQF_SHARED, sp->name, port); - if (ret) { - dev_err(port->dev, "fail to request serial irq %d, ret=%d\n", - port->irq, ret); + if (ret) return ret; - } fc = serial_in(port, SPRD_CTL1); fc |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF; serial_out(port, SPRD_CTL1, fc); From 1145d1a0703f44687e599f8608c035cea95e6ae3 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 28 Jul 2026 16:44:50 +0200 Subject: [PATCH 65/97] serial: 8250_exar: use platform_device_register_full() This driver doesn't really need to split the registration of the GPIO chip into stages, as platform_device_info already provides fields for the firmware node, parent device and the software node. Use platform_device_register_full() and simplify the code. This also addresses the problem with incorrect reference count of the assigned firmware node. Signed-off-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260728-exar-pdev-reg-full-v1-1-7a96e77309e1@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_exar.c | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/drivers/tty/serial/8250/8250_exar.c b/drivers/tty/serial/8250/8250_exar.c index f9a14eaa13cb..836792e861bf 100644 --- a/drivers/tty/serial/8250/8250_exar.c +++ b/drivers/tty/serial/8250/8250_exar.c @@ -1140,22 +1140,15 @@ static void setup_gpio(struct pci_dev *pcidev, u8 __iomem *p) static struct platform_device *__xr17v35x_register_gpio(struct pci_dev *pcidev, const struct software_node *node) { - struct platform_device *pdev; + struct platform_device_info pdevinfo = { + .name = "gpio_exar", + .id = PLATFORM_DEVID_AUTO, + .parent = &pcidev->dev, + .fwnode = dev_fwnode(&pcidev->dev), + .swnode = node, + }; - pdev = platform_device_alloc("gpio_exar", PLATFORM_DEVID_AUTO); - if (!pdev) - return NULL; - - pdev->dev.parent = &pcidev->dev; - device_set_node(&pdev->dev, dev_fwnode(&pcidev->dev)); - - if (device_add_software_node(&pdev->dev, node) < 0 || - platform_device_add(pdev) < 0) { - platform_device_put(pdev); - return NULL; - } - - return pdev; + return platform_device_register_full(&pdevinfo); } static void __xr17v35x_unregister_gpio(struct platform_device *pdev) From aee1f94dab13552d77456f387ac189ef5036aa8b Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Mon, 20 Jul 2026 14:27:52 +0530 Subject: [PATCH 66/97] serial: qcom-geni: remove .pm callback, use runtime PM in startup/shutdown The driver currently relies on qcom_geni_serial_pm() through the uart_ops.pm callback to manage runtime PM references. However, the callback has a void return type, so failures from pm_runtime_resume_and_get() cannot be propagated to the caller. As a result, startup() may continue and access hardware even when the runtime PM resume operation failed, leading to register accesses while the device is not powered. Move runtime PM acquisition to qcom_geni_serial_startup() and release it to qcom_geni_serial_shutdown(). Since startup() can return an error, PM resume failures are now detected and propagated before any hardware initialization is performed. The startup/shutdown pair also provides a natural place to balance runtime PM references for normal port usage. During probe, uart_add_one_port() may configure the port before any user opens the TTY, meaning startup() has not yet been called. To keep the hardware powered during port registration, acquire a runtime PM reference with pm_runtime_resume_and_get() before uart_add_one_port() and release it with pm_runtime_put() afterwards. By moving runtime PM handling out of uart_ops.pm, resume failures are no longer silently ignored and all hardware accesses are guaranteed to occur while the device is powered. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260720-remove_uart_change_state-v2-1-30153ce4333b@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 37 +++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 67b14fda4ff9..9fc6819f7f61 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -1190,6 +1190,8 @@ static void qcom_geni_serial_shutdown(struct uart_port *uport) qcom_geni_serial_cancel_tx_cmd(uport); uart_port_unlock_irq(uport); + + pm_runtime_put_sync(uport->dev); } static void qcom_geni_serial_flush_buffer(struct uart_port *uport) @@ -1263,10 +1265,18 @@ static int qcom_geni_serial_startup(struct uart_port *uport) struct qcom_geni_serial_port *port = to_dev_port(uport); struct tty_port *tport = &uport->state->port; + ret = pm_runtime_resume_and_get(uport->dev); + if (ret < 0) { + dev_err(uport->dev, "Failed to resume and get %d\n", ret); + return ret; + } + if (!port->setup) { ret = qcom_geni_serial_port_setup(uport); - if (ret) + if (ret) { + pm_runtime_put_sync(uport->dev); return ret; + } } /* @@ -1752,22 +1762,6 @@ static int geni_serial_resource_init(struct uart_port *uport) return 0; } -static void qcom_geni_serial_pm(struct uart_port *uport, - unsigned int new_state, unsigned int old_state) -{ - - /* If we've never been called, treat it as off */ - if (old_state == UART_PM_STATE_UNDEFINED) - old_state = UART_PM_STATE_OFF; - - if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) - pm_runtime_resume_and_get(uport->dev); - else if (new_state == UART_PM_STATE_OFF && - old_state == UART_PM_STATE_ON) - pm_runtime_put_sync(uport->dev); - -} - /** * qcom_geni_rs485_config - Configure RS485 settings for the UART port * @uport: Pointer to the UART port structure @@ -1806,7 +1800,6 @@ static const struct uart_ops qcom_geni_console_pops = { .poll_put_char = qcom_geni_serial_poll_put_char, .poll_init = qcom_geni_serial_poll_init, #endif - .pm = qcom_geni_serial_pm, }; static const struct uart_ops qcom_geni_uart_pops = { @@ -1823,7 +1816,6 @@ static const struct uart_ops qcom_geni_uart_pops = { .type = qcom_geni_serial_get_type, .set_mctrl = qcom_geni_serial_set_mctrl, .get_mctrl = qcom_geni_serial_get_mctrl, - .pm = qcom_geni_serial_pm, }; static int qcom_geni_serial_panic_notifier(struct notifier_block *nb, @@ -1973,7 +1965,14 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) devm_pm_runtime_enable(port->se.dev); + ret = pm_runtime_resume_and_get(uport->dev); + if (ret < 0) { + dev_err(uport->dev, "Failed to resume and get %d\n", ret); + goto error; + } + ret = uart_add_one_port(drv, uport); + pm_runtime_put(uport->dev); if (ret) goto error; From 74658868da2a8bf1f1189aab133d9c530aef08f8 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 29 Jul 2026 21:44:55 +0000 Subject: [PATCH 67/97] serial: qcom-geni: Convert console to nbcon The legacy GENI console writer serializes every message around a synchronous polled M-side transfer. It blocks printk callers for UART wire time and cannot provide atomic output while normal console output is active. Convert the console to nbcon threaded and atomic writers. Use the UART port lock as the device lock and bound threaded M-side commands so urgent diagnostics can take over atomic output. The threaded writer is batching the output in 32-source-byte commands, a value chosen to balance the command setup overhead with atomic-handoff latency. Atomic output can cancel an active normal TX command. Use irq_work to resume queued TTY output afterward, honor flow control, and prevent the deferred restart from accessing the port during shutdown or removal. Assisted-by: OpenCode:GPT-5.5 Signed-off-by: Bjorn Andersson Link: https://patch.msgid.link/20260729-qcom-geni-nbcon-v1-1-3053b96465ed@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 170 +++++++++++++++++++++----- 1 file changed, 142 insertions(+), 28 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 9fc6819f7f61..14eeca7d9b51 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,7 @@ #define DEF_TX_WM 2 #define DEF_FIFO_WIDTH_BITS 32 #define UART_RX_WM 2 +#define CONSOLE_TX_CHUNK_SIZE 32 /* SE_UART_LOOPBACK_CFG */ #define RX_TX_SORTED BIT(0) @@ -153,7 +155,8 @@ struct qcom_geni_serial_port { bool rx_tx_swap; bool cts_rts_swap; bool manual_flow; - + bool tx_kick_enabled; + struct irq_work tx_kick; struct qcom_geni_private_data private_data; const struct qcom_geni_device_data *dev_data; struct dev_pm_domain_list *pd_list; @@ -168,6 +171,8 @@ static struct uart_driver qcom_geni_uart_driver; static void __qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static int qcom_geni_serial_port_setup(struct uart_port *uport); +static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport); +static void qcom_geni_serial_resume_tx(struct uart_port *uport); static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport) { @@ -539,49 +544,123 @@ __qcom_geni_serial_console_write(struct uart_port *uport, const char *s, qcom_geni_serial_poll_tx_done(uport); } -static void qcom_geni_serial_console_write(struct console *co, const char *s, - unsigned int count) +static void qcom_geni_serial_console_takeover(struct uart_port *uport, + bool preserve_tx) { - struct uart_port *uport; - struct qcom_geni_serial_port *port; - u32 m_irq_en, s_irq_en; - bool locked = true; - unsigned long flags; + if (qcom_geni_serial_main_active(uport)) { + struct qcom_geni_serial_port *port = to_dev_port(uport); - WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS); + if (preserve_tx) { + if (port->tx_remaining == 0) + qcom_geni_serial_poll_tx_done(uport); + else + qcom_geni_serial_drain_fifo(uport); + } + + qcom_geni_serial_cancel_tx_cmd(uport); + } +} + +static void qcom_geni_serial_console_write_thread(struct console *co, + struct nbcon_write_context *wctxt) +{ + struct qcom_geni_serial_port *port; + struct uart_port *uport; + unsigned int offset = 0; port = get_port_from_line(co->index, true, NULL); if (IS_ERR(port)) return; uport = &port->uport; - if (oops_in_progress) - locked = uart_port_trylock_irqsave(uport, &flags); - else - uart_port_lock_irqsave(uport, &flags); + if (!nbcon_enter_unsafe(wctxt)) + return; + + qcom_geni_serial_console_takeover(uport, true); + if (!nbcon_exit_unsafe(wctxt)) + return; + + while (offset < wctxt->len) { + /* + * Printk records can be much larger than the FIFO. Limit one + * M-side command to 32 source bytes so atomic console output can + * take over between commands instead of waiting for the record. + */ + unsigned int count = min_t(unsigned int, wctxt->len - offset, + CONSOLE_TX_CHUNK_SIZE); + + if (!nbcon_enter_unsafe(wctxt)) + return; + + __qcom_geni_serial_console_write(uport, wctxt->outbuf + offset, + count); + offset += count; + + if (!nbcon_exit_unsafe(wctxt)) + return; + } + + if (!nbcon_enter_unsafe(wctxt)) + return; + + qcom_geni_serial_resume_tx(uport); + nbcon_exit_unsafe(wctxt); +} + +static void qcom_geni_serial_console_write_atomic(struct console *co, + struct nbcon_write_context *wctxt) +{ + struct qcom_geni_serial_port *port; + struct uart_port *uport; + u32 m_irq_en, s_irq_en; + + port = get_port_from_line(co->index, true, NULL); + if (IS_ERR(port)) + return; + + uport = &port->uport; + if (!nbcon_enter_unsafe(wctxt)) + return; m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); s_irq_en = readl(uport->membase + SE_GENI_S_IRQ_EN); writel(0, uport->membase + SE_GENI_M_IRQ_EN); writel(0, uport->membase + SE_GENI_S_IRQ_EN); - if (qcom_geni_serial_main_active(uport)) { - /* Wait for completion or drain FIFO */ - if (!locked || port->tx_remaining == 0) - qcom_geni_serial_poll_tx_done(uport); - else - qcom_geni_serial_drain_fifo(uport); - - qcom_geni_serial_cancel_tx_cmd(uport); - } - - __qcom_geni_serial_console_write(uport, s, count); + qcom_geni_serial_console_takeover(uport, false); + __qcom_geni_serial_console_write(uport, wctxt->outbuf, wctxt->len); writel(m_irq_en, uport->membase + SE_GENI_M_IRQ_EN); writel(s_irq_en, uport->membase + SE_GENI_S_IRQ_EN); + nbcon_exit_unsafe(wctxt); - if (locked) - uart_port_unlock_irqrestore(uport, flags); + /* Restart TTY data left queued when atomic output canceled M TX. */ + if (READ_ONCE(port->tx_kick_enabled)) + irq_work_queue(&port->tx_kick); +} + +static void qcom_geni_serial_console_device_lock(struct console *co, + unsigned long *flags) +{ + struct qcom_geni_serial_port *port; + + port = get_port_from_line(co->index, true, NULL); + if (IS_ERR(port)) + return; + + __uart_port_lock_irqsave(&port->uport, flags); +} + +static void qcom_geni_serial_console_device_unlock(struct console *co, + unsigned long flags) +{ + struct qcom_geni_serial_port *port; + + port = get_port_from_line(co->index, true, NULL); + if (IS_ERR(port)) + return; + + __uart_port_unlock_irqrestore(&port->uport, flags); } static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) @@ -738,6 +817,29 @@ static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport) writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN); } +/* Caller holds the UART port lock. */ +static void qcom_geni_serial_resume_tx(struct uart_port *uport) +{ + if (!uart_tx_stopped(uport) && + !kfifo_is_empty(&uport->state->port.xmit_fifo)) + qcom_geni_serial_start_tx_fifo(uport); +} + +static void qcom_geni_serial_restart_tx(struct irq_work *work) +{ + struct qcom_geni_serial_port *port = container_of(work, + struct qcom_geni_serial_port, tx_kick); + struct uart_port *uport = &port->uport; + + if (!READ_ONCE(port->tx_kick_enabled) || !uport->state || uport->suspended) + return; + + uart_port_lock(uport); + if (READ_ONCE(port->tx_kick_enabled) && uport->state && !uport->suspended) + qcom_geni_serial_resume_tx(uport); + uart_port_unlock(uport); +} + static void qcom_geni_serial_stop_tx_fifo(struct uart_port *uport) { u32 irq_en; @@ -1182,6 +1284,11 @@ static int setup_fifos(struct qcom_geni_serial_port *port) static void qcom_geni_serial_shutdown(struct uart_port *uport) { + struct qcom_geni_serial_port *port = to_dev_port(uport); + + /* Atomic console output queues tx_kick without taking the port lock. */ + WRITE_ONCE(port->tx_kick_enabled, false); + irq_work_sync(&port->tx_kick); disable_irq(uport->irq); uart_port_lock_irq(uport); @@ -1291,6 +1398,7 @@ static int qcom_geni_serial_startup(struct uart_port *uport) uart_port_unlock_irq(uport); enable_irq(uport->irq); + WRITE_ONCE(port->tx_kick_enabled, true); return 0; } @@ -1637,10 +1745,13 @@ static void console_unregister(struct uart_driver *drv) static struct console cons_ops = { .name = "ttyMSM", - .write = qcom_geni_serial_console_write, + .write_atomic = qcom_geni_serial_console_write_atomic, + .write_thread = qcom_geni_serial_console_write_thread, + .device_lock = qcom_geni_serial_console_device_lock, + .device_unlock = qcom_geni_serial_console_device_unlock, .device = uart_console_device, .setup = qcom_geni_console_setup, - .flags = CON_PRINTBUFFER, + .flags = CON_PRINTBUFFER | CON_NBCON, .index = -1, .data = &qcom_geni_console_driver, }; @@ -1938,6 +2049,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) port->private_data.drv = drv; uport->private_data = &port->private_data; + init_irq_work(&port->tx_kick, qcom_geni_serial_restart_tx); platform_set_drvdata(pdev, port); irq_set_status_flags(uport->irq, IRQ_NOAUTOEN); @@ -1999,6 +2111,8 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) atomic_notifier_chain_unregister(&panic_notifier_list, &port->panic_nb); + WRITE_ONCE(port->tx_kick_enabled, false); + 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); From 8200871327d1bd4c08daf248a8693a3093982fa4 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 29 Jul 2026 21:44:56 +0000 Subject: [PATCH 68/97] serial: qcom-geni: Keep FIFO RX active during console TX The GENI main sequencer handles console TX while the secondary sequencer handles FIFO RX. Before nbcon, the legacy console writer disabled both interrupt domains while it performed a long polled M-side transfer. This left the small S-side FIFO unserviced, allowing console input to overrun and be lost. The nbcon conversion replaces IRQ masking with the UART port lock, but a threaded console write still prevents the RX handler from draining the FIFO. Keep S-side RX enabled independently of M-side TX and drain it while refilling each bounded console command. This preserves interactive input during console output. Atomic output masks only M-side TX state, leaving FIFO RX handling independent. The threaded writer can also detect a SysRq character while it drains RX, so defer delivery until device_unlock() drops the UART port lock, as the existing IRQ path does with uart_unlock_and_check_sysrq(). Assisted-by: OpenCode:GPT-5.5 Signed-off-by: Bjorn Andersson Link: https://patch.msgid.link/20260729-qcom-geni-nbcon-v1-2-3053b96465ed@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 112 +++++++++++++++++--------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 14eeca7d9b51..fa4adb543562 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -173,6 +173,7 @@ static void qcom_geni_serial_cancel_tx_cmd(struct uart_port *uport); static int qcom_geni_serial_port_setup(struct uart_port *uport); static void qcom_geni_serial_start_tx_fifo(struct uart_port *uport); static void qcom_geni_serial_resume_tx(struct uart_port *uport); +static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport); static inline struct qcom_geni_serial_port *to_dev_port(struct uart_port *uport) { @@ -493,7 +494,7 @@ static void qcom_geni_serial_wr_char(struct uart_port *uport, unsigned char ch) static void __qcom_geni_serial_console_write(struct uart_port *uport, const char *s, - unsigned int count) + unsigned int count, bool poll_rx) { struct qcom_geni_private_data *private_data = uport->private_data; @@ -525,6 +526,8 @@ __qcom_geni_serial_console_write(struct uart_port *uport, const char *s, if (!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS, M_TX_FIFO_WATERMARK_EN, true)) break; + if (poll_rx) + qcom_geni_serial_poll_rx_fifo_locked(uport); chars_to_write = min_t(size_t, count - i, avail / 2); uart_console_write(uport, s + i, chars_to_write, qcom_geni_serial_wr_char); @@ -593,7 +596,7 @@ static void qcom_geni_serial_console_write_thread(struct console *co, return; __qcom_geni_serial_console_write(uport, wctxt->outbuf + offset, - count); + count, true); offset += count; if (!nbcon_exit_unsafe(wctxt)) @@ -612,7 +615,7 @@ static void qcom_geni_serial_console_write_atomic(struct console *co, { struct qcom_geni_serial_port *port; struct uart_port *uport; - u32 m_irq_en, s_irq_en; + u32 m_irq_en; port = get_port_from_line(co->index, true, NULL); if (IS_ERR(port)) @@ -623,15 +626,14 @@ static void qcom_geni_serial_console_write_atomic(struct console *co, return; m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); - s_irq_en = readl(uport->membase + SE_GENI_S_IRQ_EN); - writel(0, uport->membase + SE_GENI_M_IRQ_EN); - writel(0, uport->membase + SE_GENI_S_IRQ_EN); + writel(m_irq_en & ~(M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN), + uport->membase + SE_GENI_M_IRQ_EN); + /* Atomic console output takes priority over an active normal TX command. */ qcom_geni_serial_console_takeover(uport, false); - __qcom_geni_serial_console_write(uport, wctxt->outbuf, wctxt->len); + __qcom_geni_serial_console_write(uport, wctxt->outbuf, wctxt->len, false); writel(m_irq_en, uport->membase + SE_GENI_M_IRQ_EN); - writel(s_irq_en, uport->membase + SE_GENI_S_IRQ_EN); nbcon_exit_unsafe(wctxt); /* Restart TTY data left queued when atomic output canceled M TX. */ @@ -655,12 +657,25 @@ static void qcom_geni_serial_console_device_unlock(struct console *co, unsigned long flags) { struct qcom_geni_serial_port *port; +#ifdef CONFIG_MAGIC_SYSRQ_SERIAL + u8 sysrq_ch; +#endif port = get_port_from_line(co->index, true, NULL); if (IS_ERR(port)) return; +#ifdef CONFIG_MAGIC_SYSRQ_SERIAL + /* The threaded console writer can receive a SysRq character. */ + sysrq_ch = port->uport.sysrq_ch; + port->uport.sysrq_ch = 0; +#endif __uart_port_unlock_irqrestore(&port->uport, flags); + +#ifdef CONFIG_MAGIC_SYSRQ_SERIAL + if (sysrq_ch) + handle_sysrq(sysrq_ch); +#endif } static void handle_rx_console(struct uart_port *uport, u32 bytes, bool drop) @@ -902,6 +917,34 @@ static void qcom_geni_serial_handle_rx_fifo(struct uart_port *uport, bool drop) handle_rx_console(uport, total_bytes, drop); } +/* Caller holds the UART port lock. */ +static void qcom_geni_serial_poll_rx_fifo_locked(struct uart_port *uport) +{ + struct qcom_geni_serial_port *port = to_dev_port(uport); + struct tty_port *tport = &uport->state->port; + u32 s_irq_status; + bool drop_rx = false; + + s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS); + writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); + + if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { + uport->icount.overrun++; + tty_insert_flip_char(tport, 0, TTY_OVERRUN); + } + + if (s_irq_status & (S_GP_IRQ_0_EN | S_GP_IRQ_1_EN)) { + if (s_irq_status & S_GP_IRQ_0_EN) + uport->icount.parity++; + drop_rx = true; + } else if (s_irq_status & (S_GP_IRQ_2_EN | S_GP_IRQ_3_EN)) { + uport->icount.brk++; + port->brk = true; + } + + qcom_geni_serial_handle_rx_fifo(uport, drop_rx); +} + static void qcom_geni_serial_stop_rx_fifo(struct uart_port *uport) { u32 irq_en; @@ -912,10 +955,6 @@ static void qcom_geni_serial_stop_rx_fifo(struct uart_port *uport) irq_en &= ~(S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN); writel(irq_en, uport->membase + SE_GENI_S_IRQ_EN); - irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); - irq_en &= ~(M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN); - writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN); - if (!qcom_geni_serial_secondary_active(uport)) return; @@ -949,10 +988,6 @@ static void qcom_geni_serial_start_rx_fifo(struct uart_port *uport) irq_en = readl(uport->membase + SE_GENI_S_IRQ_EN); irq_en |= S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN; writel(irq_en, uport->membase + SE_GENI_S_IRQ_EN); - - irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); - irq_en |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN; - writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN); } static void qcom_geni_serial_stop_rx_dma(struct uart_port *uport) @@ -1182,25 +1217,11 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) uart_port_lock(uport); - m_irq_status = readl(uport->membase + SE_GENI_M_IRQ_STATUS); s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS); - dma_tx_status = readl(uport->membase + SE_DMA_TX_IRQ_STAT); dma_rx_status = readl(uport->membase + SE_DMA_RX_IRQ_STAT); - geni_status = readl(uport->membase + SE_GENI_STATUS); - dma = readl(uport->membase + SE_GENI_DMA_MODE_EN); - m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); - - trace_geni_serial_irq(uport->dev, m_irq_status, s_irq_status, - dma_tx_status, dma_rx_status); - - writel(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR); writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR); - writel(dma_tx_status, uport->membase + SE_DMA_TX_IRQ_CLR); writel(dma_rx_status, uport->membase + SE_DMA_RX_IRQ_CLR); - if (WARN_ON(m_irq_status & M_ILLEGAL_CMD_EN)) - goto out_unlock; - if (s_irq_status & S_RX_FIFO_WR_ERR_EN) { uport->icount.overrun++; tty_insert_flip_char(tport, 0, TTY_OVERRUN); @@ -1215,12 +1236,35 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) port->brk = true; } + m_irq_status = readl(uport->membase + SE_GENI_M_IRQ_STATUS); + dma_tx_status = readl(uport->membase + SE_DMA_TX_IRQ_STAT); + geni_status = readl(uport->membase + SE_GENI_STATUS); + dma = readl(uport->membase + SE_GENI_DMA_MODE_EN); + m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN); + + trace_geni_serial_irq(uport->dev, m_irq_status, s_irq_status, + dma_tx_status, dma_rx_status); + + writel(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR); + writel(dma_tx_status, uport->membase + SE_DMA_TX_IRQ_CLR); + + if (WARN_ON(m_irq_status & M_ILLEGAL_CMD_EN)) + goto handle_rx; + if (dma) { if (dma_tx_status & TX_DMA_DONE) { qcom_geni_serial_handle_tx_dma(uport); qcom_geni_set_rs485_mode(uport, SER_RS485_RTS_AFTER_SEND); + } + } else if (m_irq_status & m_irq_en & + (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN)) { + qcom_geni_serial_handle_tx_fifo(uport, + m_irq_status & M_CMD_DONE_EN, + geni_status & M_GENI_CMD_ACTIVE); } +handle_rx: + if (dma) { if (dma_rx_status) { if (dma_rx_status & RX_RESET_DONE) goto out_unlock; @@ -1237,12 +1281,6 @@ static irqreturn_t qcom_geni_serial_isr(int isr, void *dev) qcom_geni_serial_handle_rx_dma(uport, drop_rx); } } else { - if (m_irq_status & m_irq_en & - (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN)) - qcom_geni_serial_handle_tx_fifo(uport, - m_irq_status & M_CMD_DONE_EN, - geni_status & M_GENI_CMD_ACTIVE); - if (s_irq_status & (S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN)) qcom_geni_serial_handle_rx_fifo(uport, drop_rx); } @@ -1642,7 +1680,7 @@ static void qcom_geni_serial_earlycon_write(struct console *con, { struct earlycon_device *dev = con->data; - __qcom_geni_serial_console_write(&dev->port, s, n); + __qcom_geni_serial_console_write(&dev->port, s, n, false); } #ifdef CONFIG_CONSOLE_POLL From e62745dd1172e475c101de3f11e39bf192d35ae5 Mon Sep 17 00:00:00 2001 From: Wang Zihan Date: Sun, 3 May 2026 03:46:15 +0800 Subject: [PATCH 69/97] vt: add mode validation in vt_setactivate The vt_setactivate() function accepts any mode value without validation, while VT_SETMODE correctly rejects invalid values (only VT_AUTO and VT_PROCESS are valid). This allows users to set invalid mode values (e.g., 0xFF) which bypasses VT_PROCESS signal handling and causes undefined VT switching behavior. Fix this by adding the same validation as VT_SETMODE. Signed-off-by: Wang Zihan Link: https://patch.msgid.link/tencent_6A7DAE2E1288663D23AACBE2950D6E535007@qq.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt_ioctl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/vt/vt_ioctl.c b/drivers/tty/vt/vt_ioctl.c index 28993a3d0acb..b8787283a0fa 100644 --- a/drivers/tty/vt/vt_ioctl.c +++ b/drivers/tty/vt/vt_ioctl.c @@ -596,6 +596,8 @@ static int vt_setactivate(struct vt_setactivate __user *sa) return -EFAULT; if (vsa.console == 0 || vsa.console > MAX_NR_CONSOLES) return -ENXIO; + if (vsa.mode.mode != VT_AUTO && vsa.mode.mode != VT_PROCESS) + return -EINVAL; vsa.console--; vsa.console = array_index_nospec(vsa.console, MAX_NR_CONSOLES); From 355b329694c9dd064d45286e56d5ef9482692b1b Mon Sep 17 00:00:00 2001 From: Qingfang Deng Date: Mon, 3 Aug 2026 17:19:18 +0800 Subject: [PATCH 70/97] tty: remove the ipwireless driver The last mentions of this driver on local Linux and telco support forums date back to 2011. This does not prove that no users remain, but the likelihood is too low to justify continuing to carry the driver in the kernel. The maintainer agreed that the driver should be removed and noted that it is simple enough to build as an external module if a user resurfaces [1]. Remove the driver along with its Kconfig, defconfig, build and MAINTAINERS entries. [1] https://lore.kernel.org/linux-serial/20260218152330.GI26902@suse.cz/ Assisted-by: Codex:GPT-5.6 Signed-off-by: Qingfang Deng Reviewed-by: David Sterba Acked-by: David Sterba Acked-by: Jiri Kosina Link: https://patch.msgid.link/20260803091938.259944-1-qingfang.deng@linux.dev Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 6 - arch/powerpc/configs/ppc6xx_defconfig | 1 - drivers/tty/Kconfig | 9 - drivers/tty/Makefile | 2 - drivers/tty/ipwireless/Makefile | 9 - drivers/tty/ipwireless/hardware.c | 1770 ----------------------- drivers/tty/ipwireless/hardware.h | 63 - drivers/tty/ipwireless/main.c | 356 ----- drivers/tty/ipwireless/main.h | 66 - drivers/tty/ipwireless/network.c | 517 ------- drivers/tty/ipwireless/network.h | 54 - drivers/tty/ipwireless/setup_protocol.h | 109 -- drivers/tty/ipwireless/tty.c | 627 -------- drivers/tty/ipwireless/tty.h | 46 - 14 files changed, 3635 deletions(-) delete mode 100644 drivers/tty/ipwireless/Makefile delete mode 100644 drivers/tty/ipwireless/hardware.c delete mode 100644 drivers/tty/ipwireless/hardware.h delete mode 100644 drivers/tty/ipwireless/main.c delete mode 100644 drivers/tty/ipwireless/main.h delete mode 100644 drivers/tty/ipwireless/network.c delete mode 100644 drivers/tty/ipwireless/network.h delete mode 100644 drivers/tty/ipwireless/setup_protocol.h delete mode 100644 drivers/tty/ipwireless/tty.c delete mode 100644 drivers/tty/ipwireless/tty.h diff --git a/MAINTAINERS b/MAINTAINERS index 716acfc3d7c1..ce74c0fe88ff 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13699,12 +13699,6 @@ F: include/net/ip_vs.h F: include/uapi/linux/ip_vs.h F: net/netfilter/ipvs/ -IPWIRELESS DRIVER -M: Jiri Kosina -M: David Sterba -S: Odd Fixes -F: drivers/tty/ipwireless/ - IRON DEVICE AUDIO CODEC DRIVERS M: Kiseok Jo L: linux-sound@vger.kernel.org diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index 06e3cc55cb0d..eb0e76da151d 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -572,7 +572,6 @@ CONFIG_PPDEV=m CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_VIRTIO=m CONFIG_NVRAM=y -CONFIG_IPWIRELESS=m CONFIG_I2C_CHARDEV=m CONFIG_I2C_HYDRA=m CONFIG_I2C_MPC=m diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index df6832a4c237..5d6b0dd4d423 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -291,15 +291,6 @@ config GOLDFISH_TTY_EARLY_CONSOLE default y if GOLDFISH_TTY=y select SERIAL_EARLYCON -config IPWIRELESS - tristate "IPWireless 3G UMTS PCMCIA card support" - depends on PCMCIA && NETDEVICES && HAS_IOPORT - select PPP - help - This is a driver for 3G UMTS PCMCIA card from IPWireless company. In - some countries (for example Czech Republic, T-Mobile ISP) this card - is shipped for service called UMTS 4G. - config N_GSM tristate "GSM MUX line discipline support (EXPERIMENTAL)" depends on NET diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 8ca1a0a2229f..c6a063a541fd 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -26,5 +26,3 @@ obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o obj-$(CONFIG_VCC) += vcc.o obj-$(CONFIG_RPMSG_TTY) += rpmsg_tty.o - -obj-y += ipwireless/ diff --git a/drivers/tty/ipwireless/Makefile b/drivers/tty/ipwireless/Makefile deleted file mode 100644 index a665d021e24d..000000000000 --- a/drivers/tty/ipwireless/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -# -# Makefile for the IPWireless driver -# - -obj-$(CONFIG_IPWIRELESS) += ipwireless.o - -ipwireless-y := hardware.o main.o network.o tty.o - diff --git a/drivers/tty/ipwireless/hardware.c b/drivers/tty/ipwireless/hardware.c deleted file mode 100644 index 0bfcbca6e2df..000000000000 --- a/drivers/tty/ipwireless/hardware.c +++ /dev/null @@ -1,1770 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#include -#include -#include -#include -#include -#include - -#include "hardware.h" -#include "setup_protocol.h" -#include "network.h" -#include "main.h" - -static void ipw_send_setup_packet(struct ipw_hardware *hw); -static void handle_received_SETUP_packet(struct ipw_hardware *ipw, - unsigned int address, - const unsigned char *data, int len, - int is_last); -static void ipwireless_setup_timer(struct timer_list *t); -static void handle_received_CTRL_packet(struct ipw_hardware *hw, - unsigned int channel_idx, const unsigned char *data, int len); - -/*#define TIMING_DIAGNOSTICS*/ - -#ifdef TIMING_DIAGNOSTICS - -static struct timing_stats { - unsigned long last_report_time; - unsigned long read_time; - unsigned long write_time; - unsigned long read_bytes; - unsigned long write_bytes; - unsigned long start_time; -}; - -static void start_timing(void) -{ - timing_stats.start_time = jiffies; -} - -static void end_read_timing(unsigned length) -{ - timing_stats.read_time += (jiffies - start_time); - timing_stats.read_bytes += length + 2; - report_timing(); -} - -static void end_write_timing(unsigned length) -{ - timing_stats.write_time += (jiffies - start_time); - timing_stats.write_bytes += length + 2; - report_timing(); -} - -static void report_timing(void) -{ - unsigned long since = jiffies - timing_stats.last_report_time; - - /* If it's been more than one second... */ - if (since >= HZ) { - int first = (timing_stats.last_report_time == 0); - - timing_stats.last_report_time = jiffies; - if (!first) - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": %u us elapsed - read %lu bytes in %u us, wrote %lu bytes in %u us\n", - jiffies_to_usecs(since), - timing_stats.read_bytes, - jiffies_to_usecs(timing_stats.read_time), - timing_stats.write_bytes, - jiffies_to_usecs(timing_stats.write_time)); - - timing_stats.read_time = 0; - timing_stats.write_time = 0; - timing_stats.read_bytes = 0; - timing_stats.write_bytes = 0; - } -} -#else -static void start_timing(void) { } -static void end_read_timing(unsigned length) { } -static void end_write_timing(unsigned length) { } -#endif - -/* Imported IPW definitions */ - -#define LL_MTU_V1 318 -#define LL_MTU_V2 250 -#define LL_MTU_MAX (LL_MTU_V1 > LL_MTU_V2 ? LL_MTU_V1 : LL_MTU_V2) - -#define PRIO_DATA 2 -#define PRIO_CTRL 1 -#define PRIO_SETUP 0 - -/* Addresses */ -#define ADDR_SETUP_PROT 0 - -/* Protocol ids */ -enum { - /* Identifier for the Com Data protocol */ - TL_PROTOCOLID_COM_DATA = 0, - - /* Identifier for the Com Control protocol */ - TL_PROTOCOLID_COM_CTRL = 1, - - /* Identifier for the Setup protocol */ - TL_PROTOCOLID_SETUP = 2 -}; - -/* Number of bytes in NL packet header (cannot do - * sizeof(nl_packet_header) since it's a bitfield) */ -#define NL_FIRST_PACKET_HEADER_SIZE 3 - -/* Number of bytes in NL packet header (cannot do - * sizeof(nl_packet_header) since it's a bitfield) */ -#define NL_FOLLOWING_PACKET_HEADER_SIZE 1 - -struct nl_first_packet_header { - unsigned char protocol:3; - unsigned char address:3; - unsigned char packet_rank:2; - unsigned char length_lsb; - unsigned char length_msb; -}; - -struct nl_packet_header { - unsigned char protocol:3; - unsigned char address:3; - unsigned char packet_rank:2; -}; - -/* Value of 'packet_rank' above */ -#define NL_INTERMEDIATE_PACKET 0x0 -#define NL_LAST_PACKET 0x1 -#define NL_FIRST_PACKET 0x2 - -union nl_packet { - /* Network packet header of the first packet (a special case) */ - struct nl_first_packet_header hdr_first; - /* Network packet header of the following packets (if any) */ - struct nl_packet_header hdr; - /* Complete network packet (header + data) */ - unsigned char rawpkt[LL_MTU_MAX]; -} __attribute__ ((__packed__)); - -#define HW_VERSION_UNKNOWN -1 -#define HW_VERSION_1 1 -#define HW_VERSION_2 2 - -/* IPW I/O ports */ -#define IOIER 0x00 /* Interrupt Enable Register */ -#define IOIR 0x02 /* Interrupt Source/ACK register */ -#define IODCR 0x04 /* Data Control Register */ -#define IODRR 0x06 /* Data Read Register */ -#define IODWR 0x08 /* Data Write Register */ -#define IOESR 0x0A /* Embedded Driver Status Register */ -#define IORXR 0x0C /* Rx Fifo Register (Host to Embedded) */ -#define IOTXR 0x0E /* Tx Fifo Register (Embedded to Host) */ - -/* I/O ports and bit definitions for version 1 of the hardware */ - -/* IER bits*/ -#define IER_RXENABLED 0x1 -#define IER_TXENABLED 0x2 - -/* ISR bits */ -#define IR_RXINTR 0x1 -#define IR_TXINTR 0x2 - -/* DCR bits */ -#define DCR_RXDONE 0x1 -#define DCR_TXDONE 0x2 -#define DCR_RXRESET 0x4 -#define DCR_TXRESET 0x8 - -/* I/O ports and bit definitions for version 2 of the hardware */ - -struct MEMCCR { - unsigned short reg_config_option; /* PCCOR: Configuration Option Register */ - unsigned short reg_config_and_status; /* PCCSR: Configuration and Status Register */ - unsigned short reg_pin_replacement; /* PCPRR: Pin Replacemant Register */ - unsigned short reg_socket_and_copy; /* PCSCR: Socket and Copy Register */ - unsigned short reg_ext_status; /* PCESR: Extendend Status Register */ - unsigned short reg_io_base; /* PCIOB: I/O Base Register */ -}; - -struct MEMINFREG { - unsigned short memreg_tx_old; /* TX Register (R/W) */ - unsigned short pad1; - unsigned short memreg_rx_done; /* RXDone Register (R/W) */ - unsigned short pad2; - unsigned short memreg_rx; /* RX Register (R/W) */ - unsigned short pad3; - unsigned short memreg_pc_interrupt_ack; /* PC intr Ack Register (W) */ - unsigned short pad4; - unsigned long memreg_card_present;/* Mask for Host to check (R) for - * CARD_PRESENT_VALUE */ - unsigned short memreg_tx_new; /* TX2 (new) Register (R/W) */ -}; - -#define CARD_PRESENT_VALUE (0xBEEFCAFEUL) - -#define MEMTX_TX 0x0001 -#define MEMRX_RX 0x0001 -#define MEMRX_RX_DONE 0x0001 -#define MEMRX_PCINTACKK 0x0001 - -#define NL_NUM_OF_PRIORITIES 3 -#define NL_NUM_OF_PROTOCOLS 3 -#define NL_NUM_OF_ADDRESSES NO_OF_IPW_CHANNELS - -struct ipw_hardware { - unsigned int base_port; - short hw_version; - unsigned short ll_mtu; - spinlock_t lock; - - int initializing; - int init_loops; - struct timer_list setup_timer; - - /* Flag if hw is ready to send next packet */ - int tx_ready; - /* Count of pending packets to be sent */ - int tx_queued; - struct list_head tx_queue[NL_NUM_OF_PRIORITIES]; - - int rx_bytes_queued; - struct list_head rx_queue; - /* Pool of rx_packet structures that are not currently used. */ - struct list_head rx_pool; - int rx_pool_size; - /* True if reception of data is blocked while userspace processes it. */ - int blocking_rx; - /* True if there is RX data ready on the hardware. */ - int rx_ready; - unsigned short last_memtx_serial; - /* - * Newer versions of the V2 card firmware send serial numbers in the - * MemTX register. 'serial_number_detected' is set true when we detect - * a non-zero serial number (indicating the new firmware). Thereafter, - * the driver can safely ignore the Timer Recovery re-sends to avoid - * out-of-sync problems. - */ - int serial_number_detected; - struct work_struct work_rx; - - /* True if we are to send the set-up data to the hardware. */ - int to_setup; - - /* Card has been removed */ - int removed; - /* Saved irq value when we disable the interrupt. */ - int irq; - /* True if this driver is shutting down. */ - int shutting_down; - /* Modem control lines */ - unsigned int control_lines[NL_NUM_OF_ADDRESSES]; - struct ipw_rx_packet *packet_assembler[NL_NUM_OF_ADDRESSES]; - - struct tasklet_struct tasklet; - - /* The handle for the network layer, for the sending of events to it. */ - struct ipw_network *network; - struct MEMINFREG __iomem *memory_info_regs; - struct MEMCCR __iomem *memregs_CCR; - void (*reboot_callback) (void *data); - void *reboot_callback_data; - - unsigned short __iomem *memreg_tx; -}; - -/* - * Packet info structure for tx packets. - * Note: not all the fields defined here are required for all protocols - */ -struct ipw_tx_packet { - struct list_head queue; - /* channel idx + 1 */ - unsigned char dest_addr; - /* SETUP, CTRL or DATA */ - unsigned char protocol; - /* Length of data block, which starts at the end of this structure */ - unsigned short length; - /* Sending state */ - /* Offset of where we've sent up to so far */ - unsigned long offset; - /* Count of packet fragments, starting at 0 */ - int fragment_count; - - /* Called after packet is sent and before is freed */ - void (*packet_callback) (void *cb_data, unsigned int packet_length); - void *callback_data; -}; - -/* Signals from DTE */ -#define COMCTRL_RTS 0 -#define COMCTRL_DTR 1 - -/* Signals from DCE */ -#define COMCTRL_CTS 2 -#define COMCTRL_DCD 3 -#define COMCTRL_DSR 4 -#define COMCTRL_RI 5 - -struct ipw_control_packet_body { - /* DTE signal or DCE signal */ - unsigned char sig_no; - /* 0: set signal, 1: clear signal */ - unsigned char value; -} __attribute__ ((__packed__)); - -struct ipw_control_packet { - struct ipw_tx_packet header; - struct ipw_control_packet_body body; -}; - -struct ipw_rx_packet { - struct list_head queue; - unsigned int capacity; - unsigned int length; - unsigned int protocol; - unsigned int channel_idx; -}; - -static char *data_type(const unsigned char *buf, unsigned length) -{ - struct nl_packet_header *hdr = (struct nl_packet_header *) buf; - - if (length == 0) - return " "; - - if (hdr->packet_rank & NL_FIRST_PACKET) { - switch (hdr->protocol) { - case TL_PROTOCOLID_COM_DATA: return "DATA "; - case TL_PROTOCOLID_COM_CTRL: return "CTRL "; - case TL_PROTOCOLID_SETUP: return "SETUP"; - default: return "???? "; - } - } else - return " "; -} - -#define DUMP_MAX_BYTES 64 - -static void dump_data_bytes(const char *type, const unsigned char *data, - unsigned length) -{ - char prefix[56]; - - sprintf(prefix, IPWIRELESS_PCCARD_NAME ": %s %s ", - type, data_type(data, length)); - print_hex_dump_bytes(prefix, 0, (void *)data, - length < DUMP_MAX_BYTES ? length : DUMP_MAX_BYTES); -} - -static void swap_packet_bitfield_to_le(unsigned char *data) -{ -#ifdef __BIG_ENDIAN_BITFIELD - unsigned char tmp = *data, ret = 0; - - /* - * transform bits from aa.bbb.ccc to ccc.bbb.aa - */ - ret |= (tmp & 0xc0) >> 6; - ret |= (tmp & 0x38) >> 1; - ret |= (tmp & 0x07) << 5; - *data = ret & 0xff; -#endif -} - -static void swap_packet_bitfield_from_le(unsigned char *data) -{ -#ifdef __BIG_ENDIAN_BITFIELD - unsigned char tmp = *data, ret = 0; - - /* - * transform bits from ccc.bbb.aa to aa.bbb.ccc - */ - ret |= (tmp & 0xe0) >> 5; - ret |= (tmp & 0x1c) << 1; - ret |= (tmp & 0x03) << 6; - *data = ret & 0xff; -#endif -} - -static void do_send_fragment(struct ipw_hardware *hw, unsigned char *data, - unsigned length) -{ - unsigned i; - unsigned long flags; - - start_timing(); - BUG_ON(length > hw->ll_mtu); - - if (ipwireless_debug) - dump_data_bytes("send", data, length); - - spin_lock_irqsave(&hw->lock, flags); - - hw->tx_ready = 0; - swap_packet_bitfield_to_le(data); - - if (hw->hw_version == HW_VERSION_1) { - outw((unsigned short) length, hw->base_port + IODWR); - - for (i = 0; i < length; i += 2) { - unsigned short d = data[i]; - __le16 raw_data; - - if (i + 1 < length) - d |= data[i + 1] << 8; - raw_data = cpu_to_le16(d); - outw(raw_data, hw->base_port + IODWR); - } - - outw(DCR_TXDONE, hw->base_port + IODCR); - } else if (hw->hw_version == HW_VERSION_2) { - outw((unsigned short) length, hw->base_port); - - for (i = 0; i < length; i += 2) { - unsigned short d = data[i]; - __le16 raw_data; - - if (i + 1 < length) - d |= data[i + 1] << 8; - raw_data = cpu_to_le16(d); - outw(raw_data, hw->base_port); - } - while ((i & 3) != 2) { - outw((unsigned short) 0xDEAD, hw->base_port); - i += 2; - } - writew(MEMRX_RX, &hw->memory_info_regs->memreg_rx); - } - - spin_unlock_irqrestore(&hw->lock, flags); - - end_write_timing(length); -} - -static void do_send_packet(struct ipw_hardware *hw, struct ipw_tx_packet *packet) -{ - unsigned short fragment_data_len; - unsigned short data_left = packet->length - packet->offset; - unsigned short header_size; - union nl_packet pkt; - - header_size = - (packet->fragment_count == 0) - ? NL_FIRST_PACKET_HEADER_SIZE - : NL_FOLLOWING_PACKET_HEADER_SIZE; - fragment_data_len = hw->ll_mtu - header_size; - if (data_left < fragment_data_len) - fragment_data_len = data_left; - - /* - * hdr_first is now in machine bitfield order, which will be swapped - * to le just before it goes to hw - */ - pkt.hdr_first.protocol = packet->protocol; - pkt.hdr_first.address = packet->dest_addr; - pkt.hdr_first.packet_rank = 0; - - /* First packet? */ - if (packet->fragment_count == 0) { - pkt.hdr_first.packet_rank |= NL_FIRST_PACKET; - pkt.hdr_first.length_lsb = (unsigned char) packet->length; - pkt.hdr_first.length_msb = - (unsigned char) (packet->length >> 8); - } - - memcpy(pkt.rawpkt + header_size, - ((unsigned char *) packet) + sizeof(struct ipw_tx_packet) + - packet->offset, fragment_data_len); - packet->offset += fragment_data_len; - packet->fragment_count++; - - /* Last packet? (May also be first packet.) */ - if (packet->offset == packet->length) - pkt.hdr_first.packet_rank |= NL_LAST_PACKET; - do_send_fragment(hw, pkt.rawpkt, header_size + fragment_data_len); - - /* If this packet has unsent data, then re-queue it. */ - if (packet->offset < packet->length) { - /* - * Re-queue it at the head of the highest priority queue so - * it goes before all other packets - */ - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - list_add(&packet->queue, &hw->tx_queue[0]); - hw->tx_queued++; - spin_unlock_irqrestore(&hw->lock, flags); - } else { - if (packet->packet_callback) - packet->packet_callback(packet->callback_data, - packet->length); - kfree(packet); - } -} - -static void ipw_setup_hardware(struct ipw_hardware *hw) -{ - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - if (hw->hw_version == HW_VERSION_1) { - /* Reset RX FIFO */ - outw(DCR_RXRESET, hw->base_port + IODCR); - /* SB: Reset TX FIFO */ - outw(DCR_TXRESET, hw->base_port + IODCR); - - /* Enable TX and RX interrupts. */ - outw(IER_TXENABLED | IER_RXENABLED, hw->base_port + IOIER); - } else { - /* - * Set INTRACK bit (bit 0), which means we must explicitly - * acknowledge interrupts by clearing bit 2 of reg_config_and_status. - */ - unsigned short csr = readw(&hw->memregs_CCR->reg_config_and_status); - - csr |= 1; - writew(csr, &hw->memregs_CCR->reg_config_and_status); - } - spin_unlock_irqrestore(&hw->lock, flags); -} - -/* - * If 'packet' is NULL, then this function allocates a new packet, setting its - * length to 0 and ensuring it has the specified minimum amount of free space. - * - * If 'packet' is not NULL, then this function enlarges it if it doesn't - * have the specified minimum amount of free space. - * - */ -static struct ipw_rx_packet *pool_allocate(struct ipw_hardware *hw, - struct ipw_rx_packet *packet, - int minimum_free_space) -{ - - if (!packet) { - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - if (!list_empty(&hw->rx_pool)) { - packet = list_first_entry(&hw->rx_pool, - struct ipw_rx_packet, queue); - hw->rx_pool_size--; - spin_unlock_irqrestore(&hw->lock, flags); - list_del(&packet->queue); - } else { - const int min_capacity = - ipwireless_ppp_mru(hw->network) + 2; - int new_capacity; - - spin_unlock_irqrestore(&hw->lock, flags); - new_capacity = - (minimum_free_space > min_capacity - ? minimum_free_space - : min_capacity); - packet = kmalloc(sizeof(struct ipw_rx_packet) - + new_capacity, GFP_ATOMIC); - if (!packet) - return NULL; - packet->capacity = new_capacity; - } - packet->length = 0; - } - - if (packet->length + minimum_free_space > packet->capacity) { - struct ipw_rx_packet *old_packet = packet; - - packet = kmalloc(sizeof(struct ipw_rx_packet) + - old_packet->length + minimum_free_space, - GFP_ATOMIC); - if (!packet) { - kfree(old_packet); - return NULL; - } - memcpy(packet, old_packet, - sizeof(struct ipw_rx_packet) - + old_packet->length); - packet->capacity = old_packet->length + minimum_free_space; - kfree(old_packet); - } - - return packet; -} - -static void pool_free(struct ipw_hardware *hw, struct ipw_rx_packet *packet) -{ - if (hw->rx_pool_size > 6) - kfree(packet); - else { - hw->rx_pool_size++; - list_add(&packet->queue, &hw->rx_pool); - } -} - -static void queue_received_packet(struct ipw_hardware *hw, - unsigned int protocol, - unsigned int address, - const unsigned char *data, int length, - int is_last) -{ - unsigned int channel_idx = address - 1; - struct ipw_rx_packet *packet = NULL; - unsigned long flags; - - /* Discard packet if channel index is out of range. */ - if (channel_idx >= NL_NUM_OF_ADDRESSES) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": data packet has bad address %u\n", address); - return; - } - - /* - * ->packet_assembler is safe to touch unlocked, this is the only place - */ - if (protocol == TL_PROTOCOLID_COM_DATA) { - struct ipw_rx_packet **assem = - &hw->packet_assembler[channel_idx]; - - /* - * Create a new packet, or assembler already contains one - * enlarge it by 'length' bytes. - */ - (*assem) = pool_allocate(hw, *assem, length); - if (!(*assem)) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": no memory for incoming data packet, dropped!\n"); - return; - } - (*assem)->protocol = protocol; - (*assem)->channel_idx = channel_idx; - - /* Append this packet data onto existing data. */ - memcpy((unsigned char *)(*assem) + - sizeof(struct ipw_rx_packet) - + (*assem)->length, data, length); - (*assem)->length += length; - if (is_last) { - packet = *assem; - *assem = NULL; - /* Count queued DATA bytes only */ - spin_lock_irqsave(&hw->lock, flags); - hw->rx_bytes_queued += packet->length; - spin_unlock_irqrestore(&hw->lock, flags); - } - } else { - /* If it's a CTRL packet, don't assemble, just queue it. */ - packet = pool_allocate(hw, NULL, length); - if (!packet) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": no memory for incoming ctrl packet, dropped!\n"); - return; - } - packet->protocol = protocol; - packet->channel_idx = channel_idx; - memcpy((unsigned char *)packet + sizeof(struct ipw_rx_packet), - data, length); - packet->length = length; - } - - /* - * If this is the last packet, then send the assembled packet on to the - * network layer. - */ - if (packet) { - spin_lock_irqsave(&hw->lock, flags); - list_add_tail(&packet->queue, &hw->rx_queue); - /* Block reception of incoming packets if queue is full. */ - hw->blocking_rx = - (hw->rx_bytes_queued >= IPWIRELESS_RX_QUEUE_SIZE); - - spin_unlock_irqrestore(&hw->lock, flags); - schedule_work(&hw->work_rx); - } -} - -/* - * Workqueue callback - */ -static void ipw_receive_data_work(struct work_struct *work_rx) -{ - struct ipw_hardware *hw = - container_of(work_rx, struct ipw_hardware, work_rx); - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - while (!list_empty(&hw->rx_queue)) { - struct ipw_rx_packet *packet = - list_first_entry(&hw->rx_queue, - struct ipw_rx_packet, queue); - - if (hw->shutting_down) - break; - list_del(&packet->queue); - - /* - * Note: ipwireless_network_packet_received must be called in a - * process context (i.e. via schedule_work) because the tty - * output code can sleep in the tty_flip_buffer_push call. - */ - if (packet->protocol == TL_PROTOCOLID_COM_DATA) { - if (hw->network != NULL) { - /* If the network hasn't been disconnected. */ - spin_unlock_irqrestore(&hw->lock, flags); - /* - * This must run unlocked due to tty processing - * and mutex locking - */ - ipwireless_network_packet_received( - hw->network, - packet->channel_idx, - (unsigned char *)packet - + sizeof(struct ipw_rx_packet), - packet->length); - spin_lock_irqsave(&hw->lock, flags); - } - /* Count queued DATA bytes only */ - hw->rx_bytes_queued -= packet->length; - } else { - /* - * This is safe to be called locked, callchain does - * not block - */ - handle_received_CTRL_packet(hw, packet->channel_idx, - (unsigned char *)packet - + sizeof(struct ipw_rx_packet), - packet->length); - } - pool_free(hw, packet); - /* - * Unblock reception of incoming packets if queue is no longer - * full. - */ - hw->blocking_rx = - hw->rx_bytes_queued >= IPWIRELESS_RX_QUEUE_SIZE; - if (hw->shutting_down) - break; - } - spin_unlock_irqrestore(&hw->lock, flags); -} - -static void handle_received_CTRL_packet(struct ipw_hardware *hw, - unsigned int channel_idx, - const unsigned char *data, int len) -{ - const struct ipw_control_packet_body *body = - (const struct ipw_control_packet_body *) data; - unsigned int changed_mask; - - if (len != sizeof(struct ipw_control_packet_body)) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": control packet was %d bytes - wrong size!\n", - len); - return; - } - - switch (body->sig_no) { - case COMCTRL_CTS: - changed_mask = IPW_CONTROL_LINE_CTS; - break; - case COMCTRL_DCD: - changed_mask = IPW_CONTROL_LINE_DCD; - break; - case COMCTRL_DSR: - changed_mask = IPW_CONTROL_LINE_DSR; - break; - case COMCTRL_RI: - changed_mask = IPW_CONTROL_LINE_RI; - break; - default: - changed_mask = 0; - } - - if (changed_mask != 0) { - if (body->value) - hw->control_lines[channel_idx] |= changed_mask; - else - hw->control_lines[channel_idx] &= ~changed_mask; - if (hw->network) - ipwireless_network_notify_control_line_change( - hw->network, - channel_idx, - hw->control_lines[channel_idx], - changed_mask); - } -} - -static void handle_received_packet(struct ipw_hardware *hw, - const union nl_packet *packet, - unsigned short len) -{ - unsigned int protocol = packet->hdr.protocol; - unsigned int address = packet->hdr.address; - unsigned int header_length; - const unsigned char *data; - unsigned int data_len; - int is_last = packet->hdr.packet_rank & NL_LAST_PACKET; - - if (packet->hdr.packet_rank & NL_FIRST_PACKET) - header_length = NL_FIRST_PACKET_HEADER_SIZE; - else - header_length = NL_FOLLOWING_PACKET_HEADER_SIZE; - - data = packet->rawpkt + header_length; - data_len = len - header_length; - switch (protocol) { - case TL_PROTOCOLID_COM_DATA: - case TL_PROTOCOLID_COM_CTRL: - queue_received_packet(hw, protocol, address, data, data_len, - is_last); - break; - case TL_PROTOCOLID_SETUP: - handle_received_SETUP_packet(hw, address, data, data_len, - is_last); - break; - } -} - -static void acknowledge_data_read(struct ipw_hardware *hw) -{ - if (hw->hw_version == HW_VERSION_1) - outw(DCR_RXDONE, hw->base_port + IODCR); - else - writew(MEMRX_PCINTACKK, - &hw->memory_info_regs->memreg_pc_interrupt_ack); -} - -/* - * Retrieve a packet from the IPW hardware. - */ -static void do_receive_packet(struct ipw_hardware *hw) -{ - unsigned len; - unsigned i; - unsigned char pkt[LL_MTU_MAX]; - - start_timing(); - - if (hw->hw_version == HW_VERSION_1) { - len = inw(hw->base_port + IODRR); - if (len > hw->ll_mtu) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": received a packet of %u bytes - longer than the MTU!\n", len); - outw(DCR_RXDONE | DCR_RXRESET, hw->base_port + IODCR); - return; - } - - for (i = 0; i < len; i += 2) { - __le16 raw_data = inw(hw->base_port + IODRR); - unsigned short data = le16_to_cpu(raw_data); - - pkt[i] = (unsigned char) data; - pkt[i + 1] = (unsigned char) (data >> 8); - } - } else { - len = inw(hw->base_port); - if (len > hw->ll_mtu) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": received a packet of %u bytes - longer than the MTU!\n", len); - writew(MEMRX_PCINTACKK, - &hw->memory_info_regs->memreg_pc_interrupt_ack); - return; - } - - for (i = 0; i < len; i += 2) { - __le16 raw_data = inw(hw->base_port); - unsigned short data = le16_to_cpu(raw_data); - - pkt[i] = (unsigned char) data; - pkt[i + 1] = (unsigned char) (data >> 8); - } - - while ((i & 3) != 2) { - inw(hw->base_port); - i += 2; - } - } - - acknowledge_data_read(hw); - - swap_packet_bitfield_from_le(pkt); - - if (ipwireless_debug) - dump_data_bytes("recv", pkt, len); - - handle_received_packet(hw, (union nl_packet *) pkt, len); - - end_read_timing(len); -} - -static int get_current_packet_priority(struct ipw_hardware *hw) -{ - /* - * If we're initializing, don't send anything of higher priority than - * PRIO_SETUP. The network layer therefore need not care about - * hardware initialization - any of its stuff will simply be queued - * until setup is complete. - */ - return (hw->to_setup || hw->initializing - ? PRIO_SETUP + 1 : NL_NUM_OF_PRIORITIES); -} - -/* - * return 1 if something has been received from hw - */ -static int get_packets_from_hw(struct ipw_hardware *hw) -{ - int received = 0; - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - while (hw->rx_ready && !hw->blocking_rx) { - received = 1; - hw->rx_ready--; - spin_unlock_irqrestore(&hw->lock, flags); - - do_receive_packet(hw); - - spin_lock_irqsave(&hw->lock, flags); - } - spin_unlock_irqrestore(&hw->lock, flags); - - return received; -} - -/* - * Send pending packet up to given priority, prioritize SETUP data until - * hardware is fully setup. - * - * return 1 if more packets can be sent - */ -static int send_pending_packet(struct ipw_hardware *hw, int priority_limit) -{ - int more_to_send = 0; - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - if (hw->tx_queued && hw->tx_ready) { - int priority; - struct ipw_tx_packet *packet = NULL; - - /* Pick a packet */ - for (priority = 0; priority < priority_limit; priority++) { - if (!list_empty(&hw->tx_queue[priority])) { - packet = list_first_entry( - &hw->tx_queue[priority], - struct ipw_tx_packet, - queue); - - hw->tx_queued--; - list_del(&packet->queue); - - break; - } - } - if (!packet) { - hw->tx_queued = 0; - spin_unlock_irqrestore(&hw->lock, flags); - return 0; - } - - spin_unlock_irqrestore(&hw->lock, flags); - - /* Send */ - do_send_packet(hw, packet); - - /* Check if more to send */ - spin_lock_irqsave(&hw->lock, flags); - for (priority = 0; priority < priority_limit; priority++) - if (!list_empty(&hw->tx_queue[priority])) { - more_to_send = 1; - break; - } - - if (!more_to_send) - hw->tx_queued = 0; - } - spin_unlock_irqrestore(&hw->lock, flags); - - return more_to_send; -} - -/* - * Send and receive all queued packets. - */ -static void ipwireless_do_tasklet(struct tasklet_struct *t) -{ - struct ipw_hardware *hw = from_tasklet(hw, t, tasklet); - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - if (hw->shutting_down) { - spin_unlock_irqrestore(&hw->lock, flags); - return; - } - - if (hw->to_setup == 1) { - /* - * Initial setup data sent to hardware - */ - hw->to_setup = 2; - spin_unlock_irqrestore(&hw->lock, flags); - - ipw_setup_hardware(hw); - ipw_send_setup_packet(hw); - - send_pending_packet(hw, PRIO_SETUP + 1); - get_packets_from_hw(hw); - } else { - int priority_limit = get_current_packet_priority(hw); - int again; - - spin_unlock_irqrestore(&hw->lock, flags); - - do { - again = send_pending_packet(hw, priority_limit); - again |= get_packets_from_hw(hw); - } while (again); - } -} - -/* - * return true if the card is physically present. - */ -static int is_card_present(struct ipw_hardware *hw) -{ - if (hw->hw_version == HW_VERSION_1) - return inw(hw->base_port + IOIR) != 0xFFFF; - else - return readl(&hw->memory_info_regs->memreg_card_present) == - CARD_PRESENT_VALUE; -} - -static irqreturn_t ipwireless_handle_v1_interrupt(int irq, - struct ipw_hardware *hw) -{ - unsigned short irqn; - - irqn = inw(hw->base_port + IOIR); - - /* Check if card is present */ - if (irqn == 0xFFFF) - return IRQ_NONE; - else if (irqn != 0) { - unsigned short ack = 0; - unsigned long flags; - - /* Transmit complete. */ - if (irqn & IR_TXINTR) { - ack |= IR_TXINTR; - spin_lock_irqsave(&hw->lock, flags); - hw->tx_ready = 1; - spin_unlock_irqrestore(&hw->lock, flags); - } - /* Received data */ - if (irqn & IR_RXINTR) { - ack |= IR_RXINTR; - spin_lock_irqsave(&hw->lock, flags); - hw->rx_ready++; - spin_unlock_irqrestore(&hw->lock, flags); - } - if (ack != 0) { - outw(ack, hw->base_port + IOIR); - tasklet_schedule(&hw->tasklet); - } - return IRQ_HANDLED; - } - return IRQ_NONE; -} - -static void acknowledge_pcmcia_interrupt(struct ipw_hardware *hw) -{ - unsigned short csr = readw(&hw->memregs_CCR->reg_config_and_status); - - csr &= 0xfffd; - writew(csr, &hw->memregs_CCR->reg_config_and_status); -} - -static irqreturn_t ipwireless_handle_v2_v3_interrupt(int irq, - struct ipw_hardware *hw) -{ - int tx = 0; - int rx = 0; - int rx_repeat = 0; - int try_mem_tx_old; - unsigned long flags; - - do { - - unsigned short memtx = readw(hw->memreg_tx); - unsigned short memtx_serial; - unsigned short memrxdone = - readw(&hw->memory_info_regs->memreg_rx_done); - - try_mem_tx_old = 0; - - /* check whether the interrupt was generated by ipwireless card */ - if (!(memtx & MEMTX_TX) && !(memrxdone & MEMRX_RX_DONE)) { - - /* check if the card uses memreg_tx_old register */ - if (hw->memreg_tx == &hw->memory_info_regs->memreg_tx_new) { - memtx = readw(&hw->memory_info_regs->memreg_tx_old); - if (memtx & MEMTX_TX) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": Using memreg_tx_old\n"); - hw->memreg_tx = - &hw->memory_info_regs->memreg_tx_old; - } else { - return IRQ_NONE; - } - } else - return IRQ_NONE; - } - - /* - * See if the card is physically present. Note that while it is - * powering up, it appears not to be present. - */ - if (!is_card_present(hw)) { - acknowledge_pcmcia_interrupt(hw); - return IRQ_HANDLED; - } - - memtx_serial = memtx & (unsigned short) 0xff00; - if (memtx & MEMTX_TX) { - writew(memtx_serial, hw->memreg_tx); - - if (hw->serial_number_detected) { - if (memtx_serial != hw->last_memtx_serial) { - hw->last_memtx_serial = memtx_serial; - spin_lock_irqsave(&hw->lock, flags); - hw->rx_ready++; - spin_unlock_irqrestore(&hw->lock, flags); - rx = 1; - } else - /* Ignore 'Timer Recovery' duplicates. */ - rx_repeat = 1; - } else { - /* - * If a non-zero serial number is seen, then enable - * serial number checking. - */ - if (memtx_serial != 0) { - hw->serial_number_detected = 1; - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME - ": memreg_tx serial num detected\n"); - - spin_lock_irqsave(&hw->lock, flags); - hw->rx_ready++; - spin_unlock_irqrestore(&hw->lock, flags); - } - rx = 1; - } - } - if (memrxdone & MEMRX_RX_DONE) { - writew(0, &hw->memory_info_regs->memreg_rx_done); - spin_lock_irqsave(&hw->lock, flags); - hw->tx_ready = 1; - spin_unlock_irqrestore(&hw->lock, flags); - tx = 1; - } - if (tx) - writew(MEMRX_PCINTACKK, - &hw->memory_info_regs->memreg_pc_interrupt_ack); - - acknowledge_pcmcia_interrupt(hw); - - if (tx || rx) - tasklet_schedule(&hw->tasklet); - else if (!rx_repeat) { - if (hw->memreg_tx == &hw->memory_info_regs->memreg_tx_new) { - if (hw->serial_number_detected) - printk(KERN_WARNING IPWIRELESS_PCCARD_NAME - ": spurious interrupt - new_tx mode\n"); - else { - printk(KERN_WARNING IPWIRELESS_PCCARD_NAME - ": no valid memreg_tx value - switching to the old memreg_tx\n"); - hw->memreg_tx = - &hw->memory_info_regs->memreg_tx_old; - try_mem_tx_old = 1; - } - } else - printk(KERN_WARNING IPWIRELESS_PCCARD_NAME - ": spurious interrupt - old_tx mode\n"); - } - - } while (try_mem_tx_old == 1); - - return IRQ_HANDLED; -} - -irqreturn_t ipwireless_interrupt(int irq, void *dev_id) -{ - struct ipw_dev *ipw = dev_id; - - if (ipw->hardware->hw_version == HW_VERSION_1) - return ipwireless_handle_v1_interrupt(irq, ipw->hardware); - else - return ipwireless_handle_v2_v3_interrupt(irq, ipw->hardware); -} - -static void flush_packets_to_hw(struct ipw_hardware *hw) -{ - int priority_limit; - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - priority_limit = get_current_packet_priority(hw); - spin_unlock_irqrestore(&hw->lock, flags); - - while (send_pending_packet(hw, priority_limit)); -} - -static void send_packet(struct ipw_hardware *hw, int priority, - struct ipw_tx_packet *packet) -{ - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - list_add_tail(&packet->queue, &hw->tx_queue[priority]); - hw->tx_queued++; - spin_unlock_irqrestore(&hw->lock, flags); - - flush_packets_to_hw(hw); -} - -/* Create data packet, non-atomic allocation */ -static void *alloc_data_packet(int data_size, - unsigned char dest_addr, - unsigned char protocol) -{ - struct ipw_tx_packet *packet = kzalloc( - sizeof(struct ipw_tx_packet) + data_size, - GFP_ATOMIC); - - if (!packet) - return NULL; - - INIT_LIST_HEAD(&packet->queue); - packet->dest_addr = dest_addr; - packet->protocol = protocol; - packet->length = data_size; - - return packet; -} - -static void *alloc_ctrl_packet(int header_size, - unsigned char dest_addr, - unsigned char protocol, - unsigned char sig_no) -{ - /* - * sig_no is located right after ipw_tx_packet struct in every - * CTRL or SETUP packets, we can use ipw_control_packet as a - * common struct - */ - struct ipw_control_packet *packet = kzalloc(header_size, GFP_ATOMIC); - - if (!packet) - return NULL; - - INIT_LIST_HEAD(&packet->header.queue); - packet->header.dest_addr = dest_addr; - packet->header.protocol = protocol; - packet->header.length = header_size - sizeof(struct ipw_tx_packet); - packet->body.sig_no = sig_no; - - return packet; -} - -int ipwireless_send_packet(struct ipw_hardware *hw, unsigned int channel_idx, - const u8 *data, unsigned int length, - void (*callback) (void *cb, unsigned int length), - void *callback_data) -{ - struct ipw_tx_packet *packet; - - packet = alloc_data_packet(length, (channel_idx + 1), - TL_PROTOCOLID_COM_DATA); - if (!packet) - return -ENOMEM; - packet->packet_callback = callback; - packet->callback_data = callback_data; - memcpy((unsigned char *) packet + sizeof(struct ipw_tx_packet), data, - length); - - send_packet(hw, PRIO_DATA, packet); - return 0; -} - -static int set_control_line(struct ipw_hardware *hw, int prio, - unsigned int channel_idx, int line, int state) -{ - struct ipw_control_packet *packet; - int protocolid = TL_PROTOCOLID_COM_CTRL; - - if (prio == PRIO_SETUP) - protocolid = TL_PROTOCOLID_SETUP; - - packet = alloc_ctrl_packet(sizeof(struct ipw_control_packet), - (channel_idx + 1), protocolid, line); - if (!packet) - return -ENOMEM; - packet->header.length = sizeof(struct ipw_control_packet_body); - packet->body.value = (state == 0 ? 0 : 1); - send_packet(hw, prio, &packet->header); - return 0; -} - - -static int set_DTR(struct ipw_hardware *hw, int priority, - unsigned int channel_idx, int state) -{ - if (state != 0) - hw->control_lines[channel_idx] |= IPW_CONTROL_LINE_DTR; - else - hw->control_lines[channel_idx] &= ~IPW_CONTROL_LINE_DTR; - - return set_control_line(hw, priority, channel_idx, COMCTRL_DTR, state); -} - -static int set_RTS(struct ipw_hardware *hw, int priority, - unsigned int channel_idx, int state) -{ - if (state != 0) - hw->control_lines[channel_idx] |= IPW_CONTROL_LINE_RTS; - else - hw->control_lines[channel_idx] &= ~IPW_CONTROL_LINE_RTS; - - return set_control_line(hw, priority, channel_idx, COMCTRL_RTS, state); -} - -int ipwireless_set_DTR(struct ipw_hardware *hw, unsigned int channel_idx, - int state) -{ - return set_DTR(hw, PRIO_CTRL, channel_idx, state); -} - -int ipwireless_set_RTS(struct ipw_hardware *hw, unsigned int channel_idx, - int state) -{ - return set_RTS(hw, PRIO_CTRL, channel_idx, state); -} - -struct ipw_setup_get_version_query_packet { - struct ipw_tx_packet header; - struct tl_setup_get_version_qry body; -}; - -struct ipw_setup_config_packet { - struct ipw_tx_packet header; - struct tl_setup_config_msg body; -}; - -struct ipw_setup_config_done_packet { - struct ipw_tx_packet header; - struct tl_setup_config_done_msg body; -}; - -struct ipw_setup_open_packet { - struct ipw_tx_packet header; - struct tl_setup_open_msg body; -}; - -struct ipw_setup_info_packet { - struct ipw_tx_packet header; - struct tl_setup_info_msg body; -}; - -struct ipw_setup_reboot_msg_ack { - struct ipw_tx_packet header; - struct TlSetupRebootMsgAck body; -}; - -/* This handles the actual initialization of the card */ -static void __handle_setup_get_version_rsp(struct ipw_hardware *hw) -{ - struct ipw_setup_config_packet *config_packet; - struct ipw_setup_config_done_packet *config_done_packet; - struct ipw_setup_open_packet *open_packet; - struct ipw_setup_info_packet *info_packet; - int port; - unsigned int channel_idx; - - /* generate config packet */ - for (port = 1; port <= NL_NUM_OF_ADDRESSES; port++) { - config_packet = alloc_ctrl_packet( - sizeof(struct ipw_setup_config_packet), - ADDR_SETUP_PROT, - TL_PROTOCOLID_SETUP, - TL_SETUP_SIGNO_CONFIG_MSG); - if (!config_packet) - goto exit_nomem; - config_packet->header.length = sizeof(struct tl_setup_config_msg); - config_packet->body.port_no = port; - config_packet->body.prio_data = PRIO_DATA; - config_packet->body.prio_ctrl = PRIO_CTRL; - send_packet(hw, PRIO_SETUP, &config_packet->header); - } - config_done_packet = alloc_ctrl_packet( - sizeof(struct ipw_setup_config_done_packet), - ADDR_SETUP_PROT, - TL_PROTOCOLID_SETUP, - TL_SETUP_SIGNO_CONFIG_DONE_MSG); - if (!config_done_packet) - goto exit_nomem; - config_done_packet->header.length = sizeof(struct tl_setup_config_done_msg); - send_packet(hw, PRIO_SETUP, &config_done_packet->header); - - /* generate open packet */ - for (port = 1; port <= NL_NUM_OF_ADDRESSES; port++) { - open_packet = alloc_ctrl_packet( - sizeof(struct ipw_setup_open_packet), - ADDR_SETUP_PROT, - TL_PROTOCOLID_SETUP, - TL_SETUP_SIGNO_OPEN_MSG); - if (!open_packet) - goto exit_nomem; - open_packet->header.length = sizeof(struct tl_setup_open_msg); - open_packet->body.port_no = port; - send_packet(hw, PRIO_SETUP, &open_packet->header); - } - for (channel_idx = 0; - channel_idx < NL_NUM_OF_ADDRESSES; channel_idx++) { - int ret; - - ret = set_DTR(hw, PRIO_SETUP, channel_idx, - (hw->control_lines[channel_idx] & - IPW_CONTROL_LINE_DTR) != 0); - if (ret) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": error setting DTR (%d)\n", ret); - return; - } - - ret = set_RTS(hw, PRIO_SETUP, channel_idx, - (hw->control_lines [channel_idx] & - IPW_CONTROL_LINE_RTS) != 0); - if (ret) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": error setting RTS (%d)\n", ret); - return; - } - } - /* - * For NDIS we assume that we are using sync PPP frames, for COM async. - * This driver uses NDIS mode too. We don't bother with translation - * from async -> sync PPP. - */ - info_packet = alloc_ctrl_packet(sizeof(struct ipw_setup_info_packet), - ADDR_SETUP_PROT, - TL_PROTOCOLID_SETUP, - TL_SETUP_SIGNO_INFO_MSG); - if (!info_packet) - goto exit_nomem; - info_packet->header.length = sizeof(struct tl_setup_info_msg); - info_packet->body.driver_type = NDISWAN_DRIVER; - info_packet->body.major_version = NDISWAN_DRIVER_MAJOR_VERSION; - info_packet->body.minor_version = NDISWAN_DRIVER_MINOR_VERSION; - send_packet(hw, PRIO_SETUP, &info_packet->header); - - /* Initialization is now complete, so we clear the 'to_setup' flag */ - hw->to_setup = 0; - - return; - -exit_nomem: - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": not enough memory to alloc control packet\n"); - hw->to_setup = -1; -} - -static void handle_setup_get_version_rsp(struct ipw_hardware *hw, - unsigned char vers_no) -{ - timer_delete(&hw->setup_timer); - hw->initializing = 0; - printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": card is ready.\n"); - - if (vers_no == TL_SETUP_VERSION) - __handle_setup_get_version_rsp(hw); - else - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": invalid hardware version no %u\n", - (unsigned int) vers_no); -} - -static void ipw_send_setup_packet(struct ipw_hardware *hw) -{ - struct ipw_setup_get_version_query_packet *ver_packet; - - ver_packet = alloc_ctrl_packet( - sizeof(struct ipw_setup_get_version_query_packet), - ADDR_SETUP_PROT, TL_PROTOCOLID_SETUP, - TL_SETUP_SIGNO_GET_VERSION_QRY); - if (!ver_packet) - return; - ver_packet->header.length = sizeof(struct tl_setup_get_version_qry); - - /* - * Response is handled in handle_received_SETUP_packet - */ - send_packet(hw, PRIO_SETUP, &ver_packet->header); -} - -static void handle_received_SETUP_packet(struct ipw_hardware *hw, - unsigned int address, - const unsigned char *data, int len, - int is_last) -{ - const union ipw_setup_rx_msg *rx_msg = (const union ipw_setup_rx_msg *) data; - - if (address != ADDR_SETUP_PROT) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": setup packet has bad address %d\n", address); - return; - } - - switch (rx_msg->sig_no) { - case TL_SETUP_SIGNO_GET_VERSION_RSP: - if (hw->to_setup) - handle_setup_get_version_rsp(hw, - rx_msg->version_rsp_msg.version); - break; - - case TL_SETUP_SIGNO_OPEN_MSG: - if (ipwireless_debug) { - unsigned int channel_idx = rx_msg->open_msg.port_no - 1; - - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": OPEN_MSG [channel %u] reply received\n", - channel_idx); - } - break; - - case TL_SETUP_SIGNO_INFO_MSG_ACK: - if (ipwireless_debug) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME - ": card successfully configured as NDISWAN\n"); - break; - - case TL_SETUP_SIGNO_REBOOT_MSG: - if (hw->to_setup) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME - ": Setup not completed - ignoring reboot msg\n"); - else { - struct ipw_setup_reboot_msg_ack *packet; - - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME - ": Acknowledging REBOOT message\n"); - packet = alloc_ctrl_packet( - sizeof(struct ipw_setup_reboot_msg_ack), - ADDR_SETUP_PROT, TL_PROTOCOLID_SETUP, - TL_SETUP_SIGNO_REBOOT_MSG_ACK); - if (!packet) { - pr_err(IPWIRELESS_PCCARD_NAME - ": Not enough memory to send reboot packet"); - break; - } - packet->header.length = - sizeof(struct TlSetupRebootMsgAck); - send_packet(hw, PRIO_SETUP, &packet->header); - if (hw->reboot_callback) - hw->reboot_callback(hw->reboot_callback_data); - } - break; - - default: - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": unknown setup message %u received\n", - (unsigned int) rx_msg->sig_no); - } -} - -static void do_close_hardware(struct ipw_hardware *hw) -{ - unsigned int irqn; - - if (hw->hw_version == HW_VERSION_1) { - /* Disable TX and RX interrupts. */ - outw(0, hw->base_port + IOIER); - - /* Acknowledge any outstanding interrupt requests */ - irqn = inw(hw->base_port + IOIR); - if (irqn & IR_TXINTR) - outw(IR_TXINTR, hw->base_port + IOIR); - if (irqn & IR_RXINTR) - outw(IR_RXINTR, hw->base_port + IOIR); - - synchronize_irq(hw->irq); - } -} - -struct ipw_hardware *ipwireless_hardware_create(void) -{ - int i; - struct ipw_hardware *hw = - kzalloc_obj(struct ipw_hardware); - - if (!hw) - return NULL; - - hw->irq = -1; - hw->initializing = 1; - hw->tx_ready = 1; - hw->rx_bytes_queued = 0; - hw->rx_pool_size = 0; - hw->last_memtx_serial = (unsigned short) 0xffff; - for (i = 0; i < NL_NUM_OF_PRIORITIES; i++) - INIT_LIST_HEAD(&hw->tx_queue[i]); - - INIT_LIST_HEAD(&hw->rx_queue); - INIT_LIST_HEAD(&hw->rx_pool); - spin_lock_init(&hw->lock); - tasklet_setup(&hw->tasklet, ipwireless_do_tasklet); - INIT_WORK(&hw->work_rx, ipw_receive_data_work); - timer_setup(&hw->setup_timer, ipwireless_setup_timer, 0); - - return hw; -} - -void ipwireless_init_hardware_v1(struct ipw_hardware *hw, - unsigned int base_port, - void __iomem *attr_memory, - void __iomem *common_memory, - int is_v2_card, - void (*reboot_callback) (void *data), - void *reboot_callback_data) -{ - if (hw->removed) { - hw->removed = 0; - enable_irq(hw->irq); - } - hw->base_port = base_port; - hw->hw_version = (is_v2_card ? HW_VERSION_2 : HW_VERSION_1); - hw->ll_mtu = (hw->hw_version == HW_VERSION_1 ? LL_MTU_V1 : LL_MTU_V2); - hw->memregs_CCR = (struct MEMCCR __iomem *) - ((unsigned short __iomem *) attr_memory + 0x200); - hw->memory_info_regs = (struct MEMINFREG __iomem *) common_memory; - hw->memreg_tx = &hw->memory_info_regs->memreg_tx_new; - hw->reboot_callback = reboot_callback; - hw->reboot_callback_data = reboot_callback_data; -} - -void ipwireless_init_hardware_v2_v3(struct ipw_hardware *hw) -{ - hw->initializing = 1; - hw->init_loops = 0; - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": waiting for card to start up...\n"); - ipwireless_setup_timer(&hw->setup_timer); -} - -static void ipwireless_setup_timer(struct timer_list *t) -{ - struct ipw_hardware *hw = timer_container_of(hw, t, setup_timer); - - hw->init_loops++; - - if (hw->init_loops == TL_SETUP_MAX_VERSION_QRY && - hw->hw_version == HW_VERSION_2 && - hw->memreg_tx == &hw->memory_info_regs->memreg_tx_new) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": failed to startup using TX2, trying TX\n"); - - hw->memreg_tx = &hw->memory_info_regs->memreg_tx_old; - hw->init_loops = 0; - } - /* Give up after a certain number of retries */ - if (hw->init_loops == TL_SETUP_MAX_VERSION_QRY) { - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": card failed to start up!\n"); - hw->initializing = 0; - } else { - /* Do not attempt to write to the board if it is not present. */ - if (is_card_present(hw)) { - unsigned long flags; - - spin_lock_irqsave(&hw->lock, flags); - hw->to_setup = 1; - hw->tx_ready = 1; - spin_unlock_irqrestore(&hw->lock, flags); - tasklet_schedule(&hw->tasklet); - } - - mod_timer(&hw->setup_timer, - jiffies + msecs_to_jiffies(TL_SETUP_VERSION_QRY_TMO)); - } -} - -/* - * Stop any interrupts from executing so that, once this function returns, - * other layers of the driver can be sure they won't get any more callbacks. - * Thus must be called on a proper process context. - */ -void ipwireless_stop_interrupts(struct ipw_hardware *hw) -{ - if (!hw->shutting_down) { - /* Tell everyone we are going down. */ - hw->shutting_down = 1; - timer_delete(&hw->setup_timer); - - /* Prevent the hardware from sending any more interrupts */ - do_close_hardware(hw); - } -} - -void ipwireless_hardware_free(struct ipw_hardware *hw) -{ - int i; - struct ipw_rx_packet *rp, *rq; - struct ipw_tx_packet *tp, *tq; - - ipwireless_stop_interrupts(hw); - - flush_work(&hw->work_rx); - - for (i = 0; i < NL_NUM_OF_ADDRESSES; i++) - kfree(hw->packet_assembler[i]); - - for (i = 0; i < NL_NUM_OF_PRIORITIES; i++) - list_for_each_entry_safe(tp, tq, &hw->tx_queue[i], queue) { - list_del(&tp->queue); - kfree(tp); - } - - list_for_each_entry_safe(rp, rq, &hw->rx_queue, queue) { - list_del(&rp->queue); - kfree(rp); - } - - list_for_each_entry_safe(rp, rq, &hw->rx_pool, queue) { - list_del(&rp->queue); - kfree(rp); - } - kfree(hw); -} - -/* - * Associate the specified network with this hardware, so it will receive events - * from it. - */ -void ipwireless_associate_network(struct ipw_hardware *hw, - struct ipw_network *network) -{ - hw->network = network; -} diff --git a/drivers/tty/ipwireless/hardware.h b/drivers/tty/ipwireless/hardware.h deleted file mode 100644 index e524a8fcc2ad..000000000000 --- a/drivers/tty/ipwireless/hardware.h +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#ifndef _IPWIRELESS_CS_HARDWARE_H_ -#define _IPWIRELESS_CS_HARDWARE_H_ - -#include -#include -#include - -#define IPW_CONTROL_LINE_CTS 0x0001 -#define IPW_CONTROL_LINE_DCD 0x0002 -#define IPW_CONTROL_LINE_DSR 0x0004 -#define IPW_CONTROL_LINE_RI 0x0008 -#define IPW_CONTROL_LINE_DTR 0x0010 -#define IPW_CONTROL_LINE_RTS 0x0020 - -struct ipw_hardware; -struct ipw_network; - -struct ipw_hardware *ipwireless_hardware_create(void); -void ipwireless_hardware_free(struct ipw_hardware *hw); -irqreturn_t ipwireless_interrupt(int irq, void *dev_id); -int ipwireless_set_DTR(struct ipw_hardware *hw, unsigned int channel_idx, - int state); -int ipwireless_set_RTS(struct ipw_hardware *hw, unsigned int channel_idx, - int state); -int ipwireless_send_packet(struct ipw_hardware *hw, - unsigned int channel_idx, - const unsigned char *data, - unsigned int length, - void (*packet_sent_callback) (void *cb, - unsigned int length), - void *sent_cb_data); -void ipwireless_associate_network(struct ipw_hardware *hw, - struct ipw_network *net); -void ipwireless_stop_interrupts(struct ipw_hardware *hw); -void ipwireless_init_hardware_v1(struct ipw_hardware *hw, - unsigned int base_port, - void __iomem *attr_memory, - void __iomem *common_memory, - int is_v2_card, - void (*reboot_cb) (void *data), - void *reboot_cb_data); -void ipwireless_init_hardware_v2_v3(struct ipw_hardware *hw); -void ipwireless_sleep(unsigned int tenths); - -#endif diff --git a/drivers/tty/ipwireless/main.c b/drivers/tty/ipwireless/main.c deleted file mode 100644 index a2875823290e..000000000000 --- a/drivers/tty/ipwireless/main.c +++ /dev/null @@ -1,356 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#include "hardware.h" -#include "network.h" -#include "main.h" -#include "tty.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static const struct pcmcia_device_id ipw_ids[] = { - PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0100), - PCMCIA_DEVICE_MANF_CARD(0x02f2, 0x0200), - PCMCIA_DEVICE_NULL -}; -MODULE_DEVICE_TABLE(pcmcia, ipw_ids); - -static void ipwireless_detach(struct pcmcia_device *link); - -/* - * Module params - */ -/* Debug mode: more verbose, print sent/recv bytes */ -int ipwireless_debug; -int ipwireless_loopback; -int ipwireless_out_queue = 10; - -module_param_named(debug, ipwireless_debug, int, 0); -module_param_named(loopback, ipwireless_loopback, int, 0); -module_param_named(out_queue, ipwireless_out_queue, int, 0); -MODULE_PARM_DESC(debug, "switch on debug messages [0]"); -MODULE_PARM_DESC(loopback, - "debug: enable ras_raw channel [0]"); -MODULE_PARM_DESC(out_queue, "debug: set size of outgoing PPP queue [10]"); - -/* Executes in process context. */ -static void signalled_reboot_work(struct work_struct *work_reboot) -{ - struct ipw_dev *ipw = container_of(work_reboot, struct ipw_dev, - work_reboot); - struct pcmcia_device *link = ipw->link; - pcmcia_reset_card(link->socket); -} - -static void signalled_reboot_callback(void *callback_data) -{ - struct ipw_dev *ipw = (struct ipw_dev *) callback_data; - - /* Delegate to process context. */ - schedule_work(&ipw->work_reboot); -} - -static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) -{ - struct ipw_dev *ipw = priv_data; - int ret; - - p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; - p_dev->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; - - /* 0x40 causes it to generate level mode interrupts. */ - /* 0x04 enables IREQ pin. */ - p_dev->config_index |= 0x44; - p_dev->io_lines = 16; - ret = pcmcia_request_io(p_dev); - if (ret) - return ret; - - if (!request_region(p_dev->resource[0]->start, - resource_size(p_dev->resource[0]), - IPWIRELESS_PCCARD_NAME)) { - ret = -EBUSY; - goto exit; - } - - p_dev->resource[2]->flags |= - WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE; - - ret = pcmcia_request_window(p_dev, p_dev->resource[2], 0); - if (ret != 0) - goto exit1; - - ret = pcmcia_map_mem_page(p_dev, p_dev->resource[2], p_dev->card_addr); - if (ret != 0) - goto exit1; - - ipw->is_v2_card = resource_size(p_dev->resource[2]) == 0x100; - - ipw->common_memory = ioremap(p_dev->resource[2]->start, - resource_size(p_dev->resource[2])); - if (!ipw->common_memory) { - ret = -ENOMEM; - goto exit1; - } - if (!request_mem_region(p_dev->resource[2]->start, - resource_size(p_dev->resource[2]), - IPWIRELESS_PCCARD_NAME)) { - ret = -EBUSY; - goto exit2; - } - - p_dev->resource[3]->flags |= WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_AM | - WIN_ENABLE; - p_dev->resource[3]->end = 0; /* this used to be 0x1000 */ - ret = pcmcia_request_window(p_dev, p_dev->resource[3], 0); - if (ret != 0) - goto exit3; - - ret = pcmcia_map_mem_page(p_dev, p_dev->resource[3], 0); - if (ret != 0) - goto exit3; - - ipw->attr_memory = ioremap(p_dev->resource[3]->start, - resource_size(p_dev->resource[3])); - if (!ipw->attr_memory) { - ret = -ENOMEM; - goto exit3; - } - if (!request_mem_region(p_dev->resource[3]->start, - resource_size(p_dev->resource[3]), - IPWIRELESS_PCCARD_NAME)) { - ret = -EBUSY; - goto exit4; - } - - return 0; - -exit4: - iounmap(ipw->attr_memory); -exit3: - release_mem_region(p_dev->resource[2]->start, - resource_size(p_dev->resource[2])); -exit2: - iounmap(ipw->common_memory); -exit1: - release_region(p_dev->resource[0]->start, - resource_size(p_dev->resource[0])); -exit: - pcmcia_disable_device(p_dev); - return ret; -} - -static int config_ipwireless(struct ipw_dev *ipw) -{ - struct pcmcia_device *link = ipw->link; - int ret = 0; - - ipw->is_v2_card = 0; - link->config_flags |= CONF_AUTO_SET_IO | CONF_AUTO_SET_IOMEM | - CONF_ENABLE_IRQ; - - ret = pcmcia_loop_config(link, ipwireless_probe, ipw); - if (ret != 0) - return ret; - - INIT_WORK(&ipw->work_reboot, signalled_reboot_work); - - ipwireless_init_hardware_v1(ipw->hardware, link->resource[0]->start, - ipw->attr_memory, ipw->common_memory, - ipw->is_v2_card, signalled_reboot_callback, - ipw); - - ret = pcmcia_request_irq(link, ipwireless_interrupt); - if (ret != 0) - goto exit; - - printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": Card type %s\n", - ipw->is_v2_card ? "V2/V3" : "V1"); - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": I/O ports %pR, irq %d\n", link->resource[0], - (unsigned int) link->irq); - if (ipw->attr_memory && ipw->common_memory) - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": attr memory %pR, common memory %pR\n", - link->resource[3], - link->resource[2]); - - ipw->network = ipwireless_network_create(ipw->hardware); - if (!ipw->network) - goto exit; - - ipw->tty = ipwireless_tty_create(ipw->hardware, ipw->network); - if (!ipw->tty) - goto exit; - - ipwireless_init_hardware_v2_v3(ipw->hardware); - - /* - * Do the RequestConfiguration last, because it enables interrupts. - * Then we don't get any interrupts before we're ready for them. - */ - ret = pcmcia_enable_device(link); - if (ret != 0) - goto exit; - - return 0; - -exit: - if (ipw->common_memory) { - release_mem_region(link->resource[2]->start, - resource_size(link->resource[2])); - iounmap(ipw->common_memory); - } - if (ipw->attr_memory) { - release_mem_region(link->resource[3]->start, - resource_size(link->resource[3])); - iounmap(ipw->attr_memory); - } - pcmcia_disable_device(link); - return -1; -} - -static void release_ipwireless(struct ipw_dev *ipw) -{ - release_region(ipw->link->resource[0]->start, - resource_size(ipw->link->resource[0])); - if (ipw->common_memory) { - release_mem_region(ipw->link->resource[2]->start, - resource_size(ipw->link->resource[2])); - iounmap(ipw->common_memory); - } - if (ipw->attr_memory) { - release_mem_region(ipw->link->resource[3]->start, - resource_size(ipw->link->resource[3])); - iounmap(ipw->attr_memory); - } - pcmcia_disable_device(ipw->link); -} - -/* - * ipwireless_attach() creates an "instance" of the driver, allocating - * local data structures for one device (one interface). The device - * is registered with Card Services. - * - * The pcmcia_device structure is initialized, but we don't actually - * configure the card at this point -- we wait until we receive a - * card insertion event. - */ -static int ipwireless_attach(struct pcmcia_device *link) -{ - struct ipw_dev *ipw; - int ret; - - ipw = kzalloc_obj(struct ipw_dev); - if (!ipw) - return -ENOMEM; - - ipw->link = link; - link->priv = ipw; - - ipw->hardware = ipwireless_hardware_create(); - if (!ipw->hardware) { - kfree(ipw); - return -ENOMEM; - } - /* RegisterClient will call config_ipwireless */ - - ret = config_ipwireless(ipw); - - if (ret != 0) { - ipwireless_detach(link); - return ret; - } - - return 0; -} - -/* - * This deletes a driver "instance". The device is de-registered with - * Card Services. If it has been released, all local data structures - * are freed. Otherwise, the structures will be freed when the device - * is released. - */ -static void ipwireless_detach(struct pcmcia_device *link) -{ - struct ipw_dev *ipw = link->priv; - - release_ipwireless(ipw); - - if (ipw->tty != NULL) - ipwireless_tty_free(ipw->tty); - if (ipw->network != NULL) - ipwireless_network_free(ipw->network); - if (ipw->hardware != NULL) - ipwireless_hardware_free(ipw->hardware); - kfree(ipw); -} - -static struct pcmcia_driver me = { - .owner = THIS_MODULE, - .probe = ipwireless_attach, - .remove = ipwireless_detach, - .name = IPWIRELESS_PCCARD_NAME, - .id_table = ipw_ids -}; - -/* - * Module insertion : initialisation of the module. - * Register the card with cardmgr... - */ -static int __init init_ipwireless(void) -{ - int ret; - - ret = ipwireless_tty_init(); - if (ret != 0) - return ret; - - ret = pcmcia_register_driver(&me); - if (ret != 0) - ipwireless_tty_release(); - - return ret; -} - -/* - * Module removal - */ -static void __exit exit_ipwireless(void) -{ - pcmcia_unregister_driver(&me); - ipwireless_tty_release(); -} - -module_init(init_ipwireless); -module_exit(exit_ipwireless); - -MODULE_AUTHOR(IPWIRELESS_PCMCIA_AUTHOR); -MODULE_DESCRIPTION(IPWIRELESS_PCCARD_NAME " " IPWIRELESS_PCMCIA_VERSION); -MODULE_LICENSE("GPL"); diff --git a/drivers/tty/ipwireless/main.h b/drivers/tty/ipwireless/main.h deleted file mode 100644 index a5728a5b3f83..000000000000 --- a/drivers/tty/ipwireless/main.h +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#ifndef _IPWIRELESS_CS_H_ -#define _IPWIRELESS_CS_H_ - -#include -#include - -#include -#include - -#include "hardware.h" - -#define IPWIRELESS_PCCARD_NAME "ipwireless" -#define IPWIRELESS_PCMCIA_VERSION "1.1" -#define IPWIRELESS_PCMCIA_AUTHOR \ - "Stephen Blackheath, Ben Martel, Jiri Kosina and David Sterba" - -#define IPWIRELESS_TX_QUEUE_SIZE 262144 -#define IPWIRELESS_RX_QUEUE_SIZE 262144 - -#define IPWIRELESS_STATE_DEBUG - -struct ipw_hardware; -struct ipw_network; -struct ipw_tty; - -struct ipw_dev { - struct pcmcia_device *link; - int is_v2_card; - - void __iomem *attr_memory; - - void __iomem *common_memory; - - /* Hardware context */ - struct ipw_hardware *hardware; - /* Network layer context */ - struct ipw_network *network; - /* TTY device context */ - struct ipw_tty *tty; - struct work_struct work_reboot; -}; - -/* Module parametres */ -extern int ipwireless_debug; -extern int ipwireless_loopback; -extern int ipwireless_out_queue; - -#endif diff --git a/drivers/tty/ipwireless/network.c b/drivers/tty/ipwireless/network.c deleted file mode 100644 index ad2c5157a018..000000000000 --- a/drivers/tty/ipwireless/network.c +++ /dev/null @@ -1,517 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "network.h" -#include "hardware.h" -#include "main.h" -#include "tty.h" - -#define MAX_ASSOCIATED_TTYS 2 - -#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP) - -struct ipw_network { - /* Hardware context, used for calls to hardware layer. */ - struct ipw_hardware *hardware; - /* Context for kernel 'generic_ppp' functionality */ - struct ppp_channel *ppp_channel; - /* tty context connected with IPW console */ - struct ipw_tty *associated_ttys[NO_OF_IPW_CHANNELS][MAX_ASSOCIATED_TTYS]; - /* True if ppp needs waking up once we're ready to xmit */ - int ppp_blocked; - /* Number of packets queued up in hardware module. */ - int outgoing_packets_queued; - /* Spinlock to avoid interrupts during shutdown */ - spinlock_t lock; - struct mutex close_lock; - - /* PPP ioctl data, not actually used anywere */ - unsigned int flags; - unsigned int rbits; - u32 xaccm[8]; - u32 raccm; - int mru; - - int shutting_down; - unsigned int ras_control_lines; - - struct work_struct work_go_online; - struct work_struct work_go_offline; -}; - -static void notify_packet_sent(void *callback_data, unsigned int packet_length) -{ - struct ipw_network *network = callback_data; - unsigned long flags; - - spin_lock_irqsave(&network->lock, flags); - network->outgoing_packets_queued--; - if (network->ppp_channel != NULL) { - if (network->ppp_blocked) { - network->ppp_blocked = 0; - spin_unlock_irqrestore(&network->lock, flags); - ppp_output_wakeup(network->ppp_channel); - if (ipwireless_debug) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME - ": ppp unblocked\n"); - } else - spin_unlock_irqrestore(&network->lock, flags); - } else - spin_unlock_irqrestore(&network->lock, flags); -} - -/* - * Called by the ppp system when it has a packet to send to the hardware. - */ -static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel, - struct sk_buff *skb) -{ - struct ipw_network *network = ppp_channel->private; - unsigned long flags; - - spin_lock_irqsave(&network->lock, flags); - if (network->outgoing_packets_queued < ipwireless_out_queue) { - unsigned char *buf; - static unsigned char header[] = { - PPP_ALLSTATIONS, /* 0xff */ - PPP_UI, /* 0x03 */ - }; - int ret; - - network->outgoing_packets_queued++; - spin_unlock_irqrestore(&network->lock, flags); - - /* - * If we have the requested amount of headroom in the skb we - * were handed, then we can add the header efficiently. - */ - if (skb_headroom(skb) >= 2) { - memcpy(skb_push(skb, 2), header, 2); - ret = ipwireless_send_packet(network->hardware, - IPW_CHANNEL_RAS, skb->data, - skb->len, - notify_packet_sent, - network); - if (ret < 0) { - skb_pull(skb, 2); - return 0; - } - } else { - /* Otherwise (rarely) we do it inefficiently. */ - buf = kmalloc(skb->len + 2, GFP_ATOMIC); - if (!buf) - return 0; - memcpy(buf + 2, skb->data, skb->len); - memcpy(buf, header, 2); - ret = ipwireless_send_packet(network->hardware, - IPW_CHANNEL_RAS, buf, - skb->len + 2, - notify_packet_sent, - network); - kfree(buf); - if (ret < 0) - return 0; - } - kfree_skb(skb); - return 1; - } else { - /* - * Otherwise reject the packet, and flag that the ppp system - * needs to be unblocked once we are ready to send. - */ - network->ppp_blocked = 1; - spin_unlock_irqrestore(&network->lock, flags); - if (ipwireless_debug) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": ppp blocked\n"); - return 0; - } -} - -/* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */ -static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel, - unsigned int cmd, unsigned long arg) -{ - struct ipw_network *network = ppp_channel->private; - int err, val; - u32 accm[8]; - int __user *user_arg = (int __user *) arg; - - err = -EFAULT; - switch (cmd) { - case PPPIOCGFLAGS: - val = network->flags | network->rbits; - if (put_user(val, user_arg)) - break; - err = 0; - break; - - case PPPIOCSFLAGS: - if (get_user(val, user_arg)) - break; - network->flags = val & ~SC_RCV_BITS; - network->rbits = val & SC_RCV_BITS; - err = 0; - break; - - case PPPIOCGASYNCMAP: - if (put_user(network->xaccm[0], user_arg)) - break; - err = 0; - break; - - case PPPIOCSASYNCMAP: - if (get_user(network->xaccm[0], user_arg)) - break; - err = 0; - break; - - case PPPIOCGRASYNCMAP: - if (put_user(network->raccm, user_arg)) - break; - err = 0; - break; - - case PPPIOCSRASYNCMAP: - if (get_user(network->raccm, user_arg)) - break; - err = 0; - break; - - case PPPIOCGXASYNCMAP: - if (copy_to_user((void __user *) arg, network->xaccm, - sizeof(network->xaccm))) - break; - err = 0; - break; - - case PPPIOCSXASYNCMAP: - if (copy_from_user(accm, (void __user *) arg, sizeof(accm))) - break; - accm[2] &= ~0x40000000U; /* can't escape 0x5e */ - accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */ - memcpy(network->xaccm, accm, sizeof(network->xaccm)); - err = 0; - break; - - case PPPIOCGMRU: - if (put_user(network->mru, user_arg)) - break; - err = 0; - break; - - case PPPIOCSMRU: - if (get_user(val, user_arg)) - break; - if (val < PPP_MRU) - val = PPP_MRU; - network->mru = val; - err = 0; - break; - - default: - err = -ENOTTY; - } - - return err; -} - -static const struct ppp_channel_ops ipwireless_ppp_channel_ops = { - .start_xmit = ipwireless_ppp_start_xmit, - .ioctl = ipwireless_ppp_ioctl -}; - -static void do_go_online(struct work_struct *work_go_online) -{ - struct ipw_network *network = - container_of(work_go_online, struct ipw_network, - work_go_online); - unsigned long flags; - - spin_lock_irqsave(&network->lock, flags); - if (!network->ppp_channel) { - struct ppp_channel *channel; - - spin_unlock_irqrestore(&network->lock, flags); - channel = kzalloc_obj(struct ppp_channel); - if (!channel) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": unable to allocate PPP channel\n"); - return; - } - channel->private = network; - channel->mtu = 16384; /* Wild guess */ - channel->hdrlen = 2; - channel->ops = &ipwireless_ppp_channel_ops; - - network->flags = 0; - network->rbits = 0; - network->mru = PPP_MRU; - memset(network->xaccm, 0, sizeof(network->xaccm)); - network->xaccm[0] = ~0U; - network->xaccm[3] = 0x60000000U; - network->raccm = ~0U; - if (ppp_register_channel(channel) < 0) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": unable to register PPP channel\n"); - kfree(channel); - return; - } - spin_lock_irqsave(&network->lock, flags); - network->ppp_channel = channel; - } - spin_unlock_irqrestore(&network->lock, flags); -} - -static void do_go_offline(struct work_struct *work_go_offline) -{ - struct ipw_network *network = - container_of(work_go_offline, struct ipw_network, - work_go_offline); - unsigned long flags; - - mutex_lock(&network->close_lock); - spin_lock_irqsave(&network->lock, flags); - if (network->ppp_channel != NULL) { - struct ppp_channel *channel = network->ppp_channel; - - network->ppp_channel = NULL; - spin_unlock_irqrestore(&network->lock, flags); - mutex_unlock(&network->close_lock); - ppp_unregister_channel(channel); - } else { - spin_unlock_irqrestore(&network->lock, flags); - mutex_unlock(&network->close_lock); - } -} - -void ipwireless_network_notify_control_line_change(struct ipw_network *network, - unsigned int channel_idx, - unsigned int control_lines, - unsigned int changed_mask) -{ - int i; - - if (channel_idx == IPW_CHANNEL_RAS) - network->ras_control_lines = control_lines; - - for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { - struct ipw_tty *tty = - network->associated_ttys[channel_idx][i]; - - /* - * If it's associated with a tty (other than the RAS channel - * when we're online), then send the data to that tty. The RAS - * channel's data is handled above - it always goes through - * ppp_generic. - */ - if (tty) - ipwireless_tty_notify_control_line_change(tty, - channel_idx, - control_lines, - changed_mask); - } -} - -/* - * Some versions of firmware stuff packets with 0xff 0x03 (PPP: ALLSTATIONS, UI) - * bytes, which are required on sent packet, but not always present on received - * packets - */ -static struct sk_buff *ipw_packet_received_skb(unsigned char *data, - unsigned int length) -{ - struct sk_buff *skb; - - if (length > 2 && data[0] == PPP_ALLSTATIONS && data[1] == PPP_UI) { - length -= 2; - data += 2; - } - - skb = dev_alloc_skb(length + 4); - if (skb == NULL) - return NULL; - skb_reserve(skb, 2); - skb_put_data(skb, data, length); - - return skb; -} - -void ipwireless_network_packet_received(struct ipw_network *network, - unsigned int channel_idx, - unsigned char *data, - unsigned int length) -{ - int i; - unsigned long flags; - - for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) { - struct ipw_tty *tty = network->associated_ttys[channel_idx][i]; - - if (!tty) - continue; - - /* - * If it's associated with a tty (other than the RAS channel - * when we're online), then send the data to that tty. The RAS - * channel's data is handled above - it always goes through - * ppp_generic. - */ - if (channel_idx == IPW_CHANNEL_RAS - && (network->ras_control_lines & - IPW_CONTROL_LINE_DCD) != 0 - && ipwireless_tty_is_modem(tty)) { - /* - * If data came in on the RAS channel and this tty is - * the modem tty, and we are online, then we send it to - * the PPP layer. - */ - mutex_lock(&network->close_lock); - spin_lock_irqsave(&network->lock, flags); - if (network->ppp_channel != NULL) { - struct sk_buff *skb; - - spin_unlock_irqrestore(&network->lock, - flags); - - /* Send the data to the ppp_generic module. */ - skb = ipw_packet_received_skb(data, length); - if (skb) - ppp_input(network->ppp_channel, skb); - } else - spin_unlock_irqrestore(&network->lock, - flags); - mutex_unlock(&network->close_lock); - } - /* Otherwise we send it out the tty. */ - else - ipwireless_tty_received(tty, data, length); - } -} - -struct ipw_network *ipwireless_network_create(struct ipw_hardware *hw) -{ - struct ipw_network *network = - kzalloc_obj(struct ipw_network); - - if (!network) - return NULL; - - spin_lock_init(&network->lock); - mutex_init(&network->close_lock); - - network->hardware = hw; - - INIT_WORK(&network->work_go_online, do_go_online); - INIT_WORK(&network->work_go_offline, do_go_offline); - - ipwireless_associate_network(hw, network); - - return network; -} - -void ipwireless_network_free(struct ipw_network *network) -{ - network->shutting_down = 1; - - ipwireless_ppp_close(network); - flush_work(&network->work_go_online); - flush_work(&network->work_go_offline); - - ipwireless_stop_interrupts(network->hardware); - ipwireless_associate_network(network->hardware, NULL); - - kfree(network); -} - -void ipwireless_associate_network_tty(struct ipw_network *network, - unsigned int channel_idx, - struct ipw_tty *tty) -{ - int i; - - for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) - if (network->associated_ttys[channel_idx][i] == NULL) { - network->associated_ttys[channel_idx][i] = tty; - break; - } -} - -void ipwireless_disassociate_network_ttys(struct ipw_network *network, - unsigned int channel_idx) -{ - int i; - - for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) - network->associated_ttys[channel_idx][i] = NULL; -} - -void ipwireless_ppp_open(struct ipw_network *network) -{ - if (ipwireless_debug) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n"); - schedule_work(&network->work_go_online); -} - -void ipwireless_ppp_close(struct ipw_network *network) -{ - /* Disconnect from the wireless network. */ - if (ipwireless_debug) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n"); - schedule_work(&network->work_go_offline); -} - -int ipwireless_ppp_channel_index(struct ipw_network *network) -{ - int ret = -1; - unsigned long flags; - - spin_lock_irqsave(&network->lock, flags); - if (network->ppp_channel != NULL) - ret = ppp_channel_index(network->ppp_channel); - spin_unlock_irqrestore(&network->lock, flags); - - return ret; -} - -int ipwireless_ppp_unit_number(struct ipw_network *network) -{ - int ret = -1; - unsigned long flags; - - spin_lock_irqsave(&network->lock, flags); - if (network->ppp_channel != NULL) - ret = ppp_unit_number(network->ppp_channel); - spin_unlock_irqrestore(&network->lock, flags); - - return ret; -} - -int ipwireless_ppp_mru(const struct ipw_network *network) -{ - return network->mru; -} diff --git a/drivers/tty/ipwireless/network.h b/drivers/tty/ipwireless/network.h deleted file mode 100644 index 784932a59a73..000000000000 --- a/drivers/tty/ipwireless/network.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#ifndef _IPWIRELESS_CS_NETWORK_H_ -#define _IPWIRELESS_CS_NETWORK_H_ - -#include - -struct ipw_network; -struct ipw_tty; -struct ipw_hardware; - -/* Definitions of the different channels on the PCMCIA UE */ -#define IPW_CHANNEL_RAS 0 -#define IPW_CHANNEL_DIALLER 1 -#define IPW_CHANNEL_CONSOLE 2 -#define NO_OF_IPW_CHANNELS 5 - -void ipwireless_network_notify_control_line_change(struct ipw_network *net, - unsigned int channel_idx, unsigned int control_lines, - unsigned int control_mask); -void ipwireless_network_packet_received(struct ipw_network *net, - unsigned int channel_idx, unsigned char *data, - unsigned int length); -struct ipw_network *ipwireless_network_create(struct ipw_hardware *hw); -void ipwireless_network_free(struct ipw_network *net); -void ipwireless_associate_network_tty(struct ipw_network *net, - unsigned int channel_idx, struct ipw_tty *tty); -void ipwireless_disassociate_network_ttys(struct ipw_network *net, - unsigned int channel_idx); - -void ipwireless_ppp_open(struct ipw_network *net); - -void ipwireless_ppp_close(struct ipw_network *net); -int ipwireless_ppp_channel_index(struct ipw_network *net); -int ipwireless_ppp_unit_number(struct ipw_network *net); -int ipwireless_ppp_mru(const struct ipw_network *net); - -#endif diff --git a/drivers/tty/ipwireless/setup_protocol.h b/drivers/tty/ipwireless/setup_protocol.h deleted file mode 100644 index d4a7ae257ca5..000000000000 --- a/drivers/tty/ipwireless/setup_protocol.h +++ /dev/null @@ -1,109 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#ifndef _IPWIRELESS_CS_SETUP_PROTOCOL_H_ -#define _IPWIRELESS_CS_SETUP_PROTOCOL_H_ - -/* Version of the setup protocol and transport protocols */ -#define TL_SETUP_VERSION 1 - -#define TL_SETUP_VERSION_QRY_TMO 1000 -#define TL_SETUP_MAX_VERSION_QRY 30 - -/* Message numbers 0-9 are obsoleted and must not be reused! */ -#define TL_SETUP_SIGNO_GET_VERSION_QRY 10 -#define TL_SETUP_SIGNO_GET_VERSION_RSP 11 -#define TL_SETUP_SIGNO_CONFIG_MSG 12 -#define TL_SETUP_SIGNO_CONFIG_DONE_MSG 13 -#define TL_SETUP_SIGNO_OPEN_MSG 14 -#define TL_SETUP_SIGNO_CLOSE_MSG 15 - -#define TL_SETUP_SIGNO_INFO_MSG 20 -#define TL_SETUP_SIGNO_INFO_MSG_ACK 21 - -#define TL_SETUP_SIGNO_REBOOT_MSG 22 -#define TL_SETUP_SIGNO_REBOOT_MSG_ACK 23 - -/* Synchronous start-messages */ -struct tl_setup_get_version_qry { - unsigned char sig_no; /* TL_SETUP_SIGNO_GET_VERSION_QRY */ -} __attribute__ ((__packed__)); - -struct tl_setup_get_version_rsp { - unsigned char sig_no; /* TL_SETUP_SIGNO_GET_VERSION_RSP */ - unsigned char version; /* TL_SETUP_VERSION */ -} __attribute__ ((__packed__)); - -struct tl_setup_config_msg { - unsigned char sig_no; /* TL_SETUP_SIGNO_CONFIG_MSG */ - unsigned char port_no; - unsigned char prio_data; - unsigned char prio_ctrl; -} __attribute__ ((__packed__)); - -struct tl_setup_config_done_msg { - unsigned char sig_no; /* TL_SETUP_SIGNO_CONFIG_DONE_MSG */ -} __attribute__ ((__packed__)); - -/* Asynchronous messages */ -struct tl_setup_open_msg { - unsigned char sig_no; /* TL_SETUP_SIGNO_OPEN_MSG */ - unsigned char port_no; -} __attribute__ ((__packed__)); - -struct tl_setup_close_msg { - unsigned char sig_no; /* TL_SETUP_SIGNO_CLOSE_MSG */ - unsigned char port_no; -} __attribute__ ((__packed__)); - -/* Driver type - for use in tl_setup_info_msg.driver_type */ -#define COMM_DRIVER 0 -#define NDISWAN_DRIVER 1 -#define NDISWAN_DRIVER_MAJOR_VERSION 2 -#define NDISWAN_DRIVER_MINOR_VERSION 0 - -/* - * It should not matter when this message comes over as we just store the - * results and send the ACK. - */ -struct tl_setup_info_msg { - unsigned char sig_no; /* TL_SETUP_SIGNO_INFO_MSG */ - unsigned char driver_type; - unsigned char major_version; - unsigned char minor_version; -} __attribute__ ((__packed__)); - -struct tl_setup_info_msgAck { - unsigned char sig_no; /* TL_SETUP_SIGNO_INFO_MSG_ACK */ -} __attribute__ ((__packed__)); - -struct TlSetupRebootMsgAck { - unsigned char sig_no; /* TL_SETUP_SIGNO_REBOOT_MSG_ACK */ -} __attribute__ ((__packed__)); - -/* Define a union of all the msgs that the driver can receive from the card.*/ -union ipw_setup_rx_msg { - unsigned char sig_no; - struct tl_setup_get_version_rsp version_rsp_msg; - struct tl_setup_open_msg open_msg; - struct tl_setup_close_msg close_msg; - struct tl_setup_info_msg InfoMsg; - struct tl_setup_info_msgAck info_msg_ack; -} __attribute__ ((__packed__)); - -#endif /* _IPWIRELESS_CS_SETUP_PROTOCOL_H_ */ diff --git a/drivers/tty/ipwireless/tty.c b/drivers/tty/ipwireless/tty.c deleted file mode 100644 index ffdbcdd041a3..000000000000 --- a/drivers/tty/ipwireless/tty.c +++ /dev/null @@ -1,627 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "tty.h" -#include "network.h" -#include "hardware.h" -#include "main.h" - -#define IPWIRELESS_PCMCIA_START (0) -#define IPWIRELESS_PCMCIA_MINORS (24) -#define IPWIRELESS_PCMCIA_MINOR_RANGE (8) - -#define TTYTYPE_MODEM (0) -#define TTYTYPE_MONITOR (1) -#define TTYTYPE_RAS_RAW (2) - -struct ipw_tty { - struct tty_port port; - int index; - struct ipw_hardware *hardware; - unsigned int channel_idx; - unsigned int secondary_channel_idx; - int tty_type; - struct ipw_network *network; - unsigned int control_lines; - struct mutex ipw_tty_mutex; - int tx_bytes_queued; -}; - -static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS]; - -static struct tty_driver *ipw_tty_driver; - -static char *tty_type_name(int tty_type) -{ - static char *channel_names[] = { - "modem", - "monitor", - "RAS-raw" - }; - - return channel_names[tty_type]; -} - -static struct ipw_tty *get_tty(int index) -{ - /* - * The 'ras_raw' channel is only available when 'loopback' mode - * is enabled. - * Number of minor starts with 16 (_RANGE * _RAS_RAW). - */ - if (!ipwireless_loopback && index >= - IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW) - return NULL; - - return ttys[index]; -} - -static int ipw_open(struct tty_struct *linux_tty, struct file *filp) -{ - struct ipw_tty *tty = get_tty(linux_tty->index); - - if (!tty) - return -ENODEV; - - mutex_lock(&tty->ipw_tty_mutex); - if (tty->port.count == 0) - tty->tx_bytes_queued = 0; - - tty->port.count++; - - tty->port.tty = linux_tty; - linux_tty->driver_data = tty; - - if (tty->tty_type == TTYTYPE_MODEM) - ipwireless_ppp_open(tty->network); - - mutex_unlock(&tty->ipw_tty_mutex); - - return 0; -} - -static void do_ipw_close(struct ipw_tty *tty) -{ - tty->port.count--; - - if (tty->port.count == 0) { - struct tty_struct *linux_tty = tty->port.tty; - - if (linux_tty != NULL) { - tty->port.tty = NULL; - linux_tty->driver_data = NULL; - - if (tty->tty_type == TTYTYPE_MODEM) - ipwireless_ppp_close(tty->network); - } - } -} - -static void ipw_hangup(struct tty_struct *linux_tty) -{ - struct ipw_tty *tty = linux_tty->driver_data; - - if (!tty) - return; - - mutex_lock(&tty->ipw_tty_mutex); - if (tty->port.count == 0) { - mutex_unlock(&tty->ipw_tty_mutex); - return; - } - - do_ipw_close(tty); - - mutex_unlock(&tty->ipw_tty_mutex); -} - -static void ipw_close(struct tty_struct *linux_tty, struct file *filp) -{ - ipw_hangup(linux_tty); -} - -/* Take data received from hardware, and send it out the tty */ -void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data, - unsigned int length) -{ - int work = 0; - - mutex_lock(&tty->ipw_tty_mutex); - - if (!tty->port.count) { - mutex_unlock(&tty->ipw_tty_mutex); - return; - } - mutex_unlock(&tty->ipw_tty_mutex); - - work = tty_insert_flip_string(&tty->port, data, length); - - if (work != length) - printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME - ": %d chars not inserted to flip buffer!\n", - length - work); - - if (work) - tty_flip_buffer_push(&tty->port); -} - -static void ipw_write_packet_sent_callback(void *callback_data, - unsigned int packet_length) -{ - struct ipw_tty *tty = callback_data; - - /* - * Packet has been sent, so we subtract the number of bytes from our - * tally of outstanding TX bytes. - */ - tty->tx_bytes_queued -= packet_length; -} - -static ssize_t ipw_write(struct tty_struct *linux_tty, const u8 *buf, - size_t count) -{ - struct ipw_tty *tty = linux_tty->driver_data; - int room, ret; - - if (!tty) - return -ENODEV; - - mutex_lock(&tty->ipw_tty_mutex); - if (!tty->port.count) { - mutex_unlock(&tty->ipw_tty_mutex); - return -EINVAL; - } - - room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; - if (room < 0) - room = 0; - /* Don't allow caller to write any more than we have room for */ - if (count > room) - count = room; - - if (count == 0) { - mutex_unlock(&tty->ipw_tty_mutex); - return 0; - } - - ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS, - buf, count, - ipw_write_packet_sent_callback, tty); - if (ret < 0) { - mutex_unlock(&tty->ipw_tty_mutex); - return 0; - } - - tty->tx_bytes_queued += count; - mutex_unlock(&tty->ipw_tty_mutex); - - return count; -} - -static unsigned int ipw_write_room(struct tty_struct *linux_tty) -{ - struct ipw_tty *tty = linux_tty->driver_data; - int room; - - /* FIXME: Exactly how is the tty object locked here .. */ - if (!tty) - return 0; - - if (!tty->port.count) - return 0; - - room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; - if (room < 0) - room = 0; - - return room; -} - -static int ipwireless_get_serial_info(struct tty_struct *linux_tty, - struct serial_struct *ss) -{ - struct ipw_tty *tty = linux_tty->driver_data; - - if (!tty) - return -ENODEV; - - if (!tty->port.count) - return -EINVAL; - - ss->type = PORT_UNKNOWN; - ss->line = tty->index; - ss->baud_base = 115200; - return 0; -} - -static int ipwireless_set_serial_info(struct tty_struct *linux_tty, - struct serial_struct *ss) -{ - return 0; /* Keeps the PCMCIA scripts happy. */ -} - -static unsigned int ipw_chars_in_buffer(struct tty_struct *linux_tty) -{ - struct ipw_tty *tty = linux_tty->driver_data; - - if (!tty) - return 0; - - if (!tty->port.count) - return 0; - - return tty->tx_bytes_queued; -} - -static int get_control_lines(struct ipw_tty *tty) -{ - unsigned int my = tty->control_lines; - unsigned int out = 0; - - if (my & IPW_CONTROL_LINE_RTS) - out |= TIOCM_RTS; - if (my & IPW_CONTROL_LINE_DTR) - out |= TIOCM_DTR; - if (my & IPW_CONTROL_LINE_CTS) - out |= TIOCM_CTS; - if (my & IPW_CONTROL_LINE_DSR) - out |= TIOCM_DSR; - if (my & IPW_CONTROL_LINE_DCD) - out |= TIOCM_CD; - - return out; -} - -static int set_control_lines(struct ipw_tty *tty, unsigned int set, - unsigned int clear) -{ - int ret; - - if (set & TIOCM_RTS) { - ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1); - if (ret) - return ret; - if (tty->secondary_channel_idx != -1) { - ret = ipwireless_set_RTS(tty->hardware, - tty->secondary_channel_idx, 1); - if (ret) - return ret; - } - } - if (set & TIOCM_DTR) { - ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1); - if (ret) - return ret; - if (tty->secondary_channel_idx != -1) { - ret = ipwireless_set_DTR(tty->hardware, - tty->secondary_channel_idx, 1); - if (ret) - return ret; - } - } - if (clear & TIOCM_RTS) { - ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0); - if (tty->secondary_channel_idx != -1) { - ret = ipwireless_set_RTS(tty->hardware, - tty->secondary_channel_idx, 0); - if (ret) - return ret; - } - } - if (clear & TIOCM_DTR) { - ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0); - if (tty->secondary_channel_idx != -1) { - ret = ipwireless_set_DTR(tty->hardware, - tty->secondary_channel_idx, 0); - if (ret) - return ret; - } - } - return 0; -} - -static int ipw_tiocmget(struct tty_struct *linux_tty) -{ - struct ipw_tty *tty = linux_tty->driver_data; - /* FIXME: Exactly how is the tty object locked here .. */ - - if (!tty) - return -ENODEV; - - if (!tty->port.count) - return -EINVAL; - - return get_control_lines(tty); -} - -static int -ipw_tiocmset(struct tty_struct *linux_tty, - unsigned int set, unsigned int clear) -{ - struct ipw_tty *tty = linux_tty->driver_data; - /* FIXME: Exactly how is the tty object locked here .. */ - - if (!tty) - return -ENODEV; - - if (!tty->port.count) - return -EINVAL; - - return set_control_lines(tty, set, clear); -} - -static int ipw_ioctl(struct tty_struct *linux_tty, - unsigned int cmd, unsigned long arg) -{ - struct ipw_tty *tty = linux_tty->driver_data; - - if (!tty) - return -ENODEV; - - if (!tty->port.count) - return -EINVAL; - - /* FIXME: Exactly how is the tty object locked here .. */ - if (tty->tty_type == TTYTYPE_MODEM) { - switch (cmd) { - case PPPIOCGCHAN: - { - int chan = ipwireless_ppp_channel_index( - tty->network); - - if (chan < 0) - return -ENODEV; - if (put_user(chan, (int __user *) arg)) - return -EFAULT; - } - return 0; - - case PPPIOCGUNIT: - { - int unit = ipwireless_ppp_unit_number( - tty->network); - - if (unit < 0) - return -ENODEV; - if (put_user(unit, (int __user *) arg)) - return -EFAULT; - } - return 0; - - case FIONREAD: - { - int val = 0; - - if (put_user(val, (int __user *) arg)) - return -EFAULT; - } - return 0; - case TCFLSH: - return tty_perform_flush(linux_tty, arg); - } - } - return -ENOIOCTLCMD; -} - -static int add_tty(int j, - struct ipw_hardware *hardware, - struct ipw_network *network, int channel_idx, - int secondary_channel_idx, int tty_type) -{ - ttys[j] = kzalloc_obj(struct ipw_tty); - if (!ttys[j]) - return -ENOMEM; - ttys[j]->index = j; - ttys[j]->hardware = hardware; - ttys[j]->channel_idx = channel_idx; - ttys[j]->secondary_channel_idx = secondary_channel_idx; - ttys[j]->network = network; - ttys[j]->tty_type = tty_type; - mutex_init(&ttys[j]->ipw_tty_mutex); - tty_port_init(&ttys[j]->port); - - tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL); - ipwireless_associate_network_tty(network, channel_idx, ttys[j]); - - if (secondary_channel_idx != -1) - ipwireless_associate_network_tty(network, - secondary_channel_idx, - ttys[j]); - /* check if we provide raw device (if loopback is enabled) */ - if (get_tty(j)) - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": registering %s device ttyIPWp%d\n", - tty_type_name(tty_type), j); - - return 0; -} - -struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware, - struct ipw_network *network) -{ - int i, j; - - for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) { - int allfree = 1; - - for (j = i; j < IPWIRELESS_PCMCIA_MINORS; - j += IPWIRELESS_PCMCIA_MINOR_RANGE) - if (ttys[j] != NULL) { - allfree = 0; - break; - } - - if (allfree) { - j = i; - - if (add_tty(j, hardware, network, - IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS, - TTYTYPE_MODEM)) - return NULL; - - j += IPWIRELESS_PCMCIA_MINOR_RANGE; - if (add_tty(j, hardware, network, - IPW_CHANNEL_DIALLER, -1, - TTYTYPE_MONITOR)) - return NULL; - - j += IPWIRELESS_PCMCIA_MINOR_RANGE; - if (add_tty(j, hardware, network, - IPW_CHANNEL_RAS, -1, - TTYTYPE_RAS_RAW)) - return NULL; - - return ttys[i]; - } - } - return NULL; -} - -/* - * Must be called before ipwireless_network_free(). - */ -void ipwireless_tty_free(struct ipw_tty *tty) -{ - int j; - struct ipw_network *network = ttys[tty->index]->network; - - for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS; - j += IPWIRELESS_PCMCIA_MINOR_RANGE) { - struct ipw_tty *ttyj = ttys[j]; - - if (ttyj) { - mutex_lock(&ttyj->ipw_tty_mutex); - if (get_tty(j)) - printk(KERN_INFO IPWIRELESS_PCCARD_NAME - ": deregistering %s device ttyIPWp%d\n", - tty_type_name(ttyj->tty_type), j); - if (ttyj->port.tty != NULL) { - mutex_unlock(&ttyj->ipw_tty_mutex); - tty_vhangup(ttyj->port.tty); - /* FIXME: Exactly how is the tty object locked here - against a parallel ioctl etc */ - /* FIXME2: hangup does not mean all processes - * are gone */ - mutex_lock(&ttyj->ipw_tty_mutex); - } - while (ttyj->port.count) - do_ipw_close(ttyj); - ipwireless_disassociate_network_ttys(network, - ttyj->channel_idx); - tty_unregister_device(ipw_tty_driver, j); - tty_port_destroy(&ttyj->port); - ttys[j] = NULL; - mutex_unlock(&ttyj->ipw_tty_mutex); - kfree(ttyj); - } - } -} - -static const struct tty_operations tty_ops = { - .open = ipw_open, - .close = ipw_close, - .hangup = ipw_hangup, - .write = ipw_write, - .write_room = ipw_write_room, - .ioctl = ipw_ioctl, - .chars_in_buffer = ipw_chars_in_buffer, - .tiocmget = ipw_tiocmget, - .tiocmset = ipw_tiocmset, - .set_serial = ipwireless_set_serial_info, - .get_serial = ipwireless_get_serial_info, -}; - -int ipwireless_tty_init(void) -{ - int result; - - ipw_tty_driver = tty_alloc_driver(IPWIRELESS_PCMCIA_MINORS, - TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); - if (IS_ERR(ipw_tty_driver)) - return PTR_ERR(ipw_tty_driver); - - ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME; - ipw_tty_driver->name = "ttyIPWp"; - ipw_tty_driver->major = 0; - ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START; - ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; - ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL; - ipw_tty_driver->init_termios = tty_std_termios; - ipw_tty_driver->init_termios.c_cflag = - B9600 | CS8 | CREAD | HUPCL | CLOCAL; - ipw_tty_driver->init_termios.c_ispeed = 9600; - ipw_tty_driver->init_termios.c_ospeed = 9600; - tty_set_operations(ipw_tty_driver, &tty_ops); - result = tty_register_driver(ipw_tty_driver); - if (result) { - printk(KERN_ERR IPWIRELESS_PCCARD_NAME - ": failed to register tty driver\n"); - tty_driver_kref_put(ipw_tty_driver); - return result; - } - - return 0; -} - -void ipwireless_tty_release(void) -{ - tty_unregister_driver(ipw_tty_driver); - tty_driver_kref_put(ipw_tty_driver); -} - -int ipwireless_tty_is_modem(struct ipw_tty *tty) -{ - return tty->tty_type == TTYTYPE_MODEM; -} - -void -ipwireless_tty_notify_control_line_change(struct ipw_tty *tty, - unsigned int channel_idx, - unsigned int control_lines, - unsigned int changed_mask) -{ - unsigned int old_control_lines = tty->control_lines; - - tty->control_lines = (tty->control_lines & ~changed_mask) - | (control_lines & changed_mask); - - /* - * If DCD is de-asserted, we close the tty so pppd can tell that we - * have gone offline. - */ - if ((old_control_lines & IPW_CONTROL_LINE_DCD) - && !(tty->control_lines & IPW_CONTROL_LINE_DCD) - && tty->port.tty) { - tty_hangup(tty->port.tty); - } -} - diff --git a/drivers/tty/ipwireless/tty.h b/drivers/tty/ipwireless/tty.h deleted file mode 100644 index ec698d9f338b..000000000000 --- a/drivers/tty/ipwireless/tty.h +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * IPWireless 3G PCMCIA Network Driver - * - * Original code - * by Stephen Blackheath , - * Ben Martel - * - * Copyrighted as follows: - * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) - * - * Various driver changes and rewrites, port to new kernels - * Copyright (C) 2006-2007 Jiri Kosina - * - * Misc code cleanups and updates - * Copyright (C) 2007 David Sterba - */ - -#ifndef _IPWIRELESS_CS_TTY_H_ -#define _IPWIRELESS_CS_TTY_H_ - -#include -#include - -#include -#include - -struct ipw_tty; -struct ipw_network; -struct ipw_hardware; - -int ipwireless_tty_init(void); -void ipwireless_tty_release(void); - -struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hw, - struct ipw_network *net); -void ipwireless_tty_free(struct ipw_tty *tty); -void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data, - unsigned int length); -int ipwireless_tty_is_modem(struct ipw_tty *tty); -void ipwireless_tty_notify_control_line_change(struct ipw_tty *tty, - unsigned int channel_idx, - unsigned int control_lines, - unsigned int changed_mask); - -#endif From 57d0a99e93a2936b21793109b454e2cc77ecb3a0 Mon Sep 17 00:00:00 2001 From: Carlo Caione Date: Mon, 27 Jul 2026 09:38:08 +0200 Subject: [PATCH 71/97] arm64: dts: mediatek: alias all enabled serial ports On most MediaTek boards only serial0 is aliased, so the remaining enabled uarts get first-free ttyS line numbers from the 8250 core in probe order. When the console uart defers on its clocks another uart can win line 0: console=ttyS0 then ends up on the wrong port and the boot looks like a hang on the serial console, depending on probe order. Signed-off-by: Carlo Caione Reviewed-by: Chen-Yu Tsai # kukui & asurada-hayato Link: https://patch.msgid.link/20260727-ccaione-genio-serial-aliases-v2-1-0f2ae41a8e89@baylibre.com Signed-off-by: Greg Kroah-Hartman --- arch/arm64/boot/dts/mediatek/mt6795-sony-xperia-m5.dts | 1 + arch/arm64/boot/dts/mediatek/mt7622-rfb1.dts | 1 + arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts | 2 ++ arch/arm64/boot/dts/mediatek/mt7986a-rfb.dts | 2 ++ arch/arm64/boot/dts/mediatek/mt8183-kukui.dtsi | 1 + arch/arm64/boot/dts/mediatek/mt8192-asurada-hayato-r1.dts | 4 ++++ arch/arm64/boot/dts/mediatek/mt8195-demo.dts | 1 + arch/arm64/boot/dts/mediatek/mt8365-evk.dts | 2 ++ arch/arm64/boot/dts/mediatek/mt8390-genio-common.dtsi | 2 ++ arch/arm64/boot/dts/mediatek/mt8390-grinn-genio-sbc.dtsi | 2 ++ arch/arm64/boot/dts/mediatek/mt8390-tungsten-smarc.dtsi | 2 ++ arch/arm64/boot/dts/mediatek/mt8395-genio-common.dtsi | 1 + 12 files changed, 21 insertions(+) diff --git a/arch/arm64/boot/dts/mediatek/mt6795-sony-xperia-m5.dts b/arch/arm64/boot/dts/mediatek/mt6795-sony-xperia-m5.dts index 0e086dd487d9..63659e240b1d 100644 --- a/arch/arm64/boot/dts/mediatek/mt6795-sony-xperia-m5.dts +++ b/arch/arm64/boot/dts/mediatek/mt6795-sony-xperia-m5.dts @@ -20,6 +20,7 @@ aliases { mmc2 = &mmc2; serial0 = &uart0; serial1 = &uart1; + serial2 = &uart2; }; backlight_lcd0: backlight { diff --git a/arch/arm64/boot/dts/mediatek/mt7622-rfb1.dts b/arch/arm64/boot/dts/mediatek/mt7622-rfb1.dts index 8c3e2e2578bc..7bccad118d24 100644 --- a/arch/arm64/boot/dts/mediatek/mt7622-rfb1.dts +++ b/arch/arm64/boot/dts/mediatek/mt7622-rfb1.dts @@ -20,6 +20,7 @@ / { aliases { serial0 = &uart0; + serial2 = &uart2; }; chosen { diff --git a/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts b/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts index 19f538d160ab..637e5567c1e0 100644 --- a/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts +++ b/arch/arm64/boot/dts/mediatek/mt7986a-bananapi-bpi-r3.dts @@ -21,6 +21,8 @@ / { aliases { serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; ethernet0 = &gmac0; ethernet1 = &gmac1; }; diff --git a/arch/arm64/boot/dts/mediatek/mt7986a-rfb.dts b/arch/arm64/boot/dts/mediatek/mt7986a-rfb.dts index 5d8e3d3f6c20..9dfd882ca484 100644 --- a/arch/arm64/boot/dts/mediatek/mt7986a-rfb.dts +++ b/arch/arm64/boot/dts/mediatek/mt7986a-rfb.dts @@ -16,6 +16,8 @@ / { aliases { serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; }; chosen { diff --git a/arch/arm64/boot/dts/mediatek/mt8183-kukui.dtsi b/arch/arm64/boot/dts/mediatek/mt8183-kukui.dtsi index a8e257b21a88..ae957deac8df 100644 --- a/arch/arm64/boot/dts/mediatek/mt8183-kukui.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8183-kukui.dtsi @@ -13,6 +13,7 @@ / { aliases { serial0 = &uart0; + serial1 = &uart1; mmc0 = &mmc0; mmc1 = &mmc1; }; diff --git a/arch/arm64/boot/dts/mediatek/mt8192-asurada-hayato-r1.dts b/arch/arm64/boot/dts/mediatek/mt8192-asurada-hayato-r1.dts index ac2673e56fb8..233711cb4556 100644 --- a/arch/arm64/boot/dts/mediatek/mt8192-asurada-hayato-r1.dts +++ b/arch/arm64/boot/dts/mediatek/mt8192-asurada-hayato-r1.dts @@ -9,6 +9,10 @@ / { model = "Google Hayato rev1"; chassis-type = "convertible"; compatible = "google,hayato-rev1", "google,hayato", "mediatek,mt8192"; + + aliases { + serial1 = &uart1; + }; }; &keyboard_controller { diff --git a/arch/arm64/boot/dts/mediatek/mt8195-demo.dts b/arch/arm64/boot/dts/mediatek/mt8195-demo.dts index 1f59b5786b81..13132a35080f 100644 --- a/arch/arm64/boot/dts/mediatek/mt8195-demo.dts +++ b/arch/arm64/boot/dts/mediatek/mt8195-demo.dts @@ -19,6 +19,7 @@ / { aliases { serial0 = &uart0; + serial1 = &uart1; }; chosen { diff --git a/arch/arm64/boot/dts/mediatek/mt8365-evk.dts b/arch/arm64/boot/dts/mediatek/mt8365-evk.dts index a30ee523b0b5..c535de34dfac 100644 --- a/arch/arm64/boot/dts/mediatek/mt8365-evk.dts +++ b/arch/arm64/boot/dts/mediatek/mt8365-evk.dts @@ -24,6 +24,8 @@ aliases { mmc0 = &mmc0; mmc1 = &mmc1; serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; }; chosen { diff --git a/arch/arm64/boot/dts/mediatek/mt8390-genio-common.dtsi b/arch/arm64/boot/dts/mediatek/mt8390-genio-common.dtsi index b0c97930a0e6..26e24431d467 100644 --- a/arch/arm64/boot/dts/mediatek/mt8390-genio-common.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8390-genio-common.dtsi @@ -33,6 +33,8 @@ aliases { mmc0 = &mmc0; mmc1 = &mmc1; serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; }; backlight_lcm1: backlight-lcm1 { diff --git a/arch/arm64/boot/dts/mediatek/mt8390-grinn-genio-sbc.dtsi b/arch/arm64/boot/dts/mediatek/mt8390-grinn-genio-sbc.dtsi index 888248a75e93..51f12328a1d6 100644 --- a/arch/arm64/boot/dts/mediatek/mt8390-grinn-genio-sbc.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8390-grinn-genio-sbc.dtsi @@ -17,6 +17,8 @@ aliases { i2c5 = &i2c5; i2c6 = &i2c6; serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; }; chosen { diff --git a/arch/arm64/boot/dts/mediatek/mt8390-tungsten-smarc.dtsi b/arch/arm64/boot/dts/mediatek/mt8390-tungsten-smarc.dtsi index 9f5a0ec563e8..668985428fc2 100644 --- a/arch/arm64/boot/dts/mediatek/mt8390-tungsten-smarc.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8390-tungsten-smarc.dtsi @@ -31,6 +31,8 @@ aliases { rtc0 = &rv3028; rtc1 = &mt6359rtc; serial0 = &uart0; + serial1 = &uart1; + serial2 = &uart2; }; backlight_lcd0: backlight-lcd0 { diff --git a/arch/arm64/boot/dts/mediatek/mt8395-genio-common.dtsi b/arch/arm64/boot/dts/mediatek/mt8395-genio-common.dtsi index edc5539bebde..86e487fb1832 100644 --- a/arch/arm64/boot/dts/mediatek/mt8395-genio-common.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8395-genio-common.dtsi @@ -22,6 +22,7 @@ aliases { mmc0 = &mmc0; mmc1 = &mmc1; serial0 = &uart0; + serial1 = &uart1; }; chosen { From 47cadc98c322a839c31463b8ec452b392072968c Mon Sep 17 00:00:00 2001 From: Carlo Caione Date: Mon, 27 Jul 2026 09:38:09 +0200 Subject: [PATCH 72/97] serial: 8250_mtk: honor DT serial aliases The Genio board DTs provide serial aliases for all enabled UARTs, but the MTK 8250 driver still registered every port with the default line number. If uart0 deferred and another UART probed first, the 8250 core could still assign ttyS0 to the wrong port despite the DT aliases. Read the serial alias during OF probe and seed uart.port.line before registering the port so the 8250 core reserves the requested ttyS slot. Signed-off-by: Carlo Caione Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20260727-ccaione-genio-serial-aliases-v2-2-0f2ae41a8e89@baylibre.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mtk.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_mtk.c b/drivers/tty/serial/8250/8250_mtk.c index e6a56cf54ae0..841d64f6a707 100644 --- a/drivers/tty/serial/8250/8250_mtk.c +++ b/drivers/tty/serial/8250/8250_mtk.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -521,7 +522,7 @@ static int mtk8250_probe(struct platform_device *pdev) struct uart_8250_port uart = {}; struct mtk8250_data *data; struct resource *regs; - int irq, err; + int irq, err, line; struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); irq = platform_get_irq(pdev, 0); @@ -575,6 +576,10 @@ static int mtk8250_probe(struct platform_device *pdev) #endif if (is_of_node(fwnode)) { + line = of_alias_get_id(pdev->dev.of_node, "serial"); + if (line >= 0) + uart.port.line = line; + /* Disable Rate Fix function */ writel(0x0, uart.port.membase + (MTK_UART_RATE_FIX << uart.port.regshift)); From 2339b2c6fba79349092636b9a59405e1939252a0 Mon Sep 17 00:00:00 2001 From: Yu-Che Hsieh Date: Tue, 21 Jul 2026 09:44:23 +0800 Subject: [PATCH 73/97] dt-bindings: serial: 8250: Add Aspeed AST2600 and AST2700 uart compatible The AST2600 and AST2700 VUART controllers are identical to the AST2500 VUART controller. Add the "aspeed,ast2600-vuart" and "aspeed,ast2700-vuart" compatible strings and fall back to "aspeed,ast2500-vuart" for compatibility with the existing driver. Signed-off-by: Yu-Che Hsieh Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260721-upstream-ast2700-vuart-support-v1-1-b4ed612b1aa2@aspeedtech.com Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/serial/8250.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/serial/8250.yaml b/Documentation/devicetree/bindings/serial/8250.yaml index bb7b9c87a807..8c2c177faf89 100644 --- a/Documentation/devicetree/bindings/serial/8250.yaml +++ b/Documentation/devicetree/bindings/serial/8250.yaml @@ -23,7 +23,8 @@ allOf: then: properties: compatible: - const: aspeed,ast2500-vuart + contains: + const: aspeed,ast2500-vuart - if: properties: compatible: @@ -188,6 +189,11 @@ properties: - loongson,ls2k2000-uart - const: loongson,ls2k1500-uart - const: ns16550a + - items: + - enum: + - aspeed,ast2600-vuart + - aspeed,ast2700-vuart + - const: aspeed,ast2500-vuart reg: maxItems: 1 From 7f93da9d78d433c37836d85de475c5d884ad58ed Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 24 Jul 2026 23:33:47 +0200 Subject: [PATCH 74/97] serial: amba-pl011: unprepare console clock on unregister pl011_console_setup() calls clk_prepare() on the UART clock, but the console provides no matching teardown, so the clock is never unprepared when the console is unregistered -- via the sysfs "console" attribute or a driver unbind. Each re-registration prepares the clock again, leaking one prepare reference per cycle. Even where preparing the clock has no hardware effect, the stale reference leaves the clock framework's prepare count unbalanced. For providers with prepare/unprepare operations or runtime-PM integration, it may also retain resources after the console is unregistered. Add a console .exit() callback that clk_unprepare()s the clock, balancing the clk_prepare() in pl011_console_setup(). Fixes: 4b4851c65d92 ("clk: amba-pl011: convert to clk_prepare()/clk_unprepare()") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260724213348.77418-2-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8ed91e1da22b..1aa43994a3cd 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2552,6 +2552,15 @@ static int pl011_console_setup(struct console *co, char *options) return uart_set_options(&uap->port, co, baud, parity, bits, flow); } +static int pl011_console_exit(struct console *co) +{ + struct uart_amba_port *uap = amba_ports[co->index]; + + clk_unprepare(uap->clk); + + return 0; +} + /** * pl011_console_match - non-standard console matching * @co: registering console @@ -2705,6 +2714,7 @@ static struct console amba_console = { .name = "ttyAMA", .device = uart_console_device, .setup = pl011_console_setup, + .exit = pl011_console_exit, .match = pl011_console_match, .write_atomic = pl011_console_write_atomic, .write_thread = pl011_console_write_thread, From c0e8cfef754645856374e82c8effd54b7d82002b Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 24 Jul 2026 23:33:48 +0200 Subject: [PATCH 75/97] serial: amba-pl011: keep console clock enabled for atomic writes pl011_console_write_atomic() runs from nbcon atomic context, where sleeping is not allowed. It calls clk_enable(), which takes the common-clk enable_lock. Under PREEMPT_RT that is a sleeping lock: clk_enable_lock() first tries spin_trylock_irqsave(), but on contention falls back to spin_lock_irqsave(). Therefore, an atomic-context printk on an RT kernel with a clk-backed pl011 can trip: BUG: sleeping function called from invalid context at spinlock_rt.c:48 __might_resched from rt_spin_lock rt_spin_lock from clk_enable_lock clk_enable_lock from clk_enable clk_enable from pl011_console_write_atomic ... from vprintk_emit This was found and reproduced on PREEMPT_RT. Arm32 and arm64 DT SoCs are affected; arm64 SBSA/ACPI has no clk, so clk_enable(NULL) short-circuits before the lock. In addition, write_atomic() may be invoked from NMI context and is documented to avoid locking. Removing clk_enable() from the callback also avoids a potentially unsafe NMI acquisition of the common-clock enable_lock. An nbcon atomic-capable console must be printable from any context, so the clock cannot be gated between writes. Enable the clock while the console is available for output: use clk_prepare_enable() in pl011_console_setup(), release it via clk_disable_unprepare() in the console .exit() callback, and drop the per-write clk_enable()/clk_disable() pairs from write_atomic() and write_thread(). When printk suspends consoles, drop the reference after uart_suspend_port() stops console access and restore it before uart_resume_port() -- but only if suspend actually marked the port suspended (a wake-capable tty stays running and must keep its clock), and keep it when console_suspend_enabled is false so no_console_suspend works. The active power cost of keeping the clock enabled is platform-dependent: none where the UART clock is a fixed always-on oscillator, real where it is a gateable clock branch, which then cannot be gated (nor possibly can its parent clocks) while the console is available for output. When serial core actually suspends the port, the reference is released so the clock provider can gate the clock tree. Fixes: 2eb2608618ce ("serial: amba-pl011: Implement nbcon console") Suggested-by: John Ogness Link: https://lore.kernel.org/all/8733xeaxix.fsf@jogness.linutronix.de/ Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260724213348.77418-3-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/amba-pl011.c | 40 +++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 1aa43994a3cd..611d1f6a0590 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2523,7 +2523,7 @@ static int pl011_console_setup(struct console *co, char *options) /* Allow pins to be muxed in and configured */ pinctrl_pm_select_default_state(uap->port.dev); - ret = clk_prepare(uap->clk); + ret = clk_prepare_enable(uap->clk); if (ret) return ret; @@ -2556,7 +2556,7 @@ static int pl011_console_exit(struct console *co) { struct uart_amba_port *uap = amba_ports[co->index]; - clk_unprepare(uap->clk); + clk_disable_unprepare(uap->clk); return 0; } @@ -2630,8 +2630,6 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt if (!nbcon_enter_unsafe(wctxt)) return; - clk_enable(uap->clk); - if (!uap->vendor->always_enabled) { old_cr = pl011_read(uap, REG_CR); pl011_write((old_cr & ~UART011_CR_CTSEN) | (UART01x_CR_UARTEN | UART011_CR_TXE), @@ -2648,8 +2646,6 @@ pl011_console_write_atomic(struct console *co, struct nbcon_write_context *wctxt if (!uap->vendor->always_enabled) pl011_write(old_cr, uap, REG_CR); - clk_disable(uap->clk); - nbcon_exit_unsafe(wctxt); } @@ -2662,8 +2658,6 @@ pl011_console_write_thread(struct console *co, struct nbcon_write_context *wctxt if (!nbcon_enter_unsafe(wctxt)) return; - clk_enable(uap->clk); - if (!uap->vendor->always_enabled) { old_cr = pl011_read(uap, REG_CR); pl011_write((old_cr & ~UART011_CR_CTSEN) | (UART01x_CR_UARTEN | UART011_CR_TXE), @@ -2692,8 +2686,6 @@ pl011_console_write_thread(struct console *co, struct nbcon_write_context *wctxt if (!uap->vendor->always_enabled) pl011_write(old_cr, uap, REG_CR); - clk_disable(uap->clk); - nbcon_exit_unsafe(wctxt); } @@ -3080,21 +3072,45 @@ static void pl011_remove(struct amba_device *dev) static int pl011_suspend(struct device *dev) { struct uart_amba_port *uap = dev_get_drvdata(dev); + int ret; if (!uap) return -EINVAL; - return uart_suspend_port(&amba_reg, &uap->port); + ret = uart_suspend_port(&amba_reg, &uap->port); + if (ret) + return ret; + + if (console_suspend_enabled && uap->port.suspended && + uart_console_registered(&uap->port)) + clk_disable_unprepare(uap->clk); + + return 0; } static int pl011_resume(struct device *dev) { struct uart_amba_port *uap = dev_get_drvdata(dev); + bool resume_console; + int ret; if (!uap) return -EINVAL; - return uart_resume_port(&amba_reg, &uap->port); + resume_console = console_suspend_enabled && + uap->port.suspended && + uart_console_registered(&uap->port); + if (resume_console) { + ret = clk_prepare_enable(uap->clk); + if (ret) + return ret; + } + + ret = uart_resume_port(&amba_reg, &uap->port); + if (ret && resume_console) + clk_disable_unprepare(uap->clk); + + return ret; } #endif From 5a425dcd533b428426d98d544b440658bdbe78e4 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Sat, 1 Aug 2026 11:18:15 +0530 Subject: [PATCH 76/97] serial: qcom-geni: Use common GENI resource initialisation helpers The UART driver maintains local helpers for resource and power-domain initialisation that duplicate functionality already provided by the common GENI framework. Replace the driver-specific implementations with geni_se_resources_init() and geni_se_domain_attach(), and use the power-domain list stored in struct geni_se. This reduces code duplication and centralises GENI resource management without functional changes. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260801-reuse_common_geni_framework_helpers-v2-1-13753256ef71@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 81 +++------------------------ 1 file changed, 9 insertions(+), 72 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index fa4adb543562..c8c4f01d3143 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -115,8 +115,7 @@ static DEFINE_IDA(port_ida); struct qcom_geni_device_data { bool console; enum geni_se_xfer_mode mode; - struct dev_pm_domain_attach_data pd_data; - int (*resources_init)(struct uart_port *uport); + int (*resources_init)(struct geni_se *se); int (*set_rate)(struct uart_port *uport, unsigned int baud); int (*power_state)(struct uart_port *uport, bool state); }; @@ -159,7 +158,6 @@ struct qcom_geni_serial_port { struct irq_work tx_kick; struct qcom_geni_private_data private_data; const struct qcom_geni_device_data *dev_data; - struct dev_pm_domain_list *pd_list; struct notifier_block panic_nb; }; @@ -1500,7 +1498,7 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) static int geni_serial_set_level(struct uart_port *uport, unsigned int baud) { struct qcom_geni_serial_port *port = to_dev_port(uport); - struct device *perf_dev = port->pd_list->pd_devs[DOMAIN_IDX_PERF]; + struct device *perf_dev = port->se.pd_list->pd_devs[DOMAIN_IDX_PERF]; /* * The performance protocol sets UART communication @@ -1860,57 +1858,6 @@ static int geni_serial_resource_state(struct uart_port *uport, bool power_on) return power_on ? geni_serial_resources_on(uport) : geni_serial_resources_off(uport); } -static int geni_serial_pwr_init(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - int ret; - - ret = dev_pm_domain_attach_list(port->se.dev, - &port->dev_data->pd_data, &port->pd_list); - if (ret <= 0) - return -EINVAL; - - return 0; -} - -static int geni_serial_resource_init(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - int ret; - - port->se.clk = devm_clk_get(port->se.dev, "se"); - if (IS_ERR(port->se.clk)) { - ret = PTR_ERR(port->se.clk); - dev_err(port->se.dev, "Err getting SE Core clk %d\n", ret); - return ret; - } - - ret = geni_icc_get(&port->se, NULL); - if (ret) - return ret; - - port->se.icc_paths[GENI_TO_CORE].avg_bw = GENI_DEFAULT_BW; - port->se.icc_paths[CPU_TO_GENI].avg_bw = GENI_DEFAULT_BW; - - /* Set BW for register access */ - ret = geni_icc_set_bw(&port->se); - if (ret) - return ret; - - ret = devm_pm_opp_set_clkname(port->se.dev, "se"); - if (ret) - return ret; - - /* OPP table is optional */ - ret = devm_pm_opp_of_add_table(port->se.dev); - if (ret && ret != -ENODEV) { - dev_err(port->se.dev, "invalid OPP table in device tree\n"); - return ret; - } - - return 0; -} - /** * qcom_geni_rs485_config - Configure RS485 settings for the UART port * @uport: Pointer to the UART port structure @@ -2024,7 +1971,7 @@ static int qcom_geni_serial_probe(struct platform_device *pdev) port->se.dev = &pdev->dev; port->se.wrapper = dev_get_drvdata(pdev->dev.parent); - ret = port->dev_data->resources_init(uport); + ret = port->dev_data->resources_init(&port->se); if (ret) return ret; @@ -2137,7 +2084,7 @@ 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->pd_list); + dev_pm_domain_detach_list(port->se.pd_list); return ret; } @@ -2162,7 +2109,7 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) port->rx_dma_addr = 0; } - dev_pm_domain_detach_list(port->pd_list); + dev_pm_domain_detach_list(port->se.pd_list); } static int __maybe_unused qcom_geni_serial_runtime_suspend(struct device *dev) @@ -2242,7 +2189,7 @@ static int qcom_geni_serial_resume(struct device *dev) static const struct qcom_geni_device_data qcom_geni_console_data = { .console = true, .mode = GENI_SE_FIFO, - .resources_init = geni_serial_resource_init, + .resources_init = geni_se_resources_init, .set_rate = geni_serial_set_rate, .power_state = geni_serial_resource_state, }; @@ -2250,12 +2197,7 @@ static const struct qcom_geni_device_data qcom_geni_console_data = { static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = { .console = true, .mode = GENI_SE_FIFO, - .pd_data = { - .pd_flags = PD_FLAG_DEV_LINK_ON, - .pd_names = (const char*[]) { "power", "perf" }, - .num_pd_names = 2, - }, - .resources_init = geni_serial_pwr_init, + .resources_init = geni_se_domain_attach, .set_rate = geni_serial_set_level, }; #endif @@ -2263,7 +2205,7 @@ static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = { static const struct qcom_geni_device_data qcom_geni_uart_data = { .console = false, .mode = GENI_SE_DMA, - .resources_init = geni_serial_resource_init, + .resources_init = geni_se_resources_init, .set_rate = geni_serial_set_rate, .power_state = geni_serial_resource_state, }; @@ -2271,12 +2213,7 @@ static const struct qcom_geni_device_data qcom_geni_uart_data = { static const struct qcom_geni_device_data sa8255p_qcom_geni_uart_data = { .console = false, .mode = GENI_SE_DMA, - .pd_data = { - .pd_flags = PD_FLAG_DEV_LINK_ON, - .pd_names = (const char*[]) { "power", "perf" }, - .num_pd_names = 2, - }, - .resources_init = geni_serial_pwr_init, + .resources_init = geni_se_domain_attach, .set_rate = geni_serial_set_level, }; From 4b3f927b74fdbe393e2642817184a9f7a56c59ea Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Sat, 1 Aug 2026 11:18:16 +0530 Subject: [PATCH 77/97] serial: qcom-geni: Use resources helper APIs in runtime PM functions To manage GENI serial engine resources during runtime power management, drivers currently need to call functions for ICC, clock, and SE resource operations in both suspend and resume paths, resulting in code duplication across drivers. The new geni_se_resources_activate() and geni_se_resources_deactivate() helper APIs addresses this issue by providing a streamlined method to enable or disable all resources based, thereby eliminating redundancy across drivers. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260801-reuse_common_geni_framework_helpers-v2-2-13753256ef71@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 72 +++++++-------------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index c8c4f01d3143..83ba0f5433ef 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -117,7 +117,8 @@ struct qcom_geni_device_data { enum geni_se_xfer_mode mode; int (*resources_init)(struct geni_se *se); int (*set_rate)(struct uart_port *uport, unsigned int baud); - int (*power_state)(struct uart_port *uport, bool state); + int (*power_on)(struct geni_se *se); + int (*power_off)(struct geni_se *se); }; struct qcom_geni_private_data { @@ -1817,47 +1818,6 @@ static struct uart_driver qcom_geni_uart_driver = { .nr = CONFIG_SERIAL_QCOM_GENI_UART_PORTS, }; -static int geni_serial_resources_on(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - int ret; - - ret = geni_icc_enable(&port->se); - if (ret) - return ret; - - ret = geni_se_resources_on(&port->se); - if (ret) { - geni_icc_disable(&port->se); - return ret; - } - - if (port->clk_rate) - dev_pm_opp_set_rate(uport->dev, port->clk_rate); - - return 0; -} - -static int geni_serial_resources_off(struct uart_port *uport) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - int ret; - - dev_pm_opp_set_rate(uport->dev, 0); - ret = geni_se_resources_off(&port->se); - if (ret) - return ret; - - geni_icc_disable(&port->se); - - return 0; -} - -static int geni_serial_resource_state(struct uart_port *uport, bool power_on) -{ - return power_on ? geni_serial_resources_on(uport) : geni_serial_resources_off(uport); -} - /** * qcom_geni_rs485_config - Configure RS485 settings for the UART port * @uport: Pointer to the UART port structure @@ -2115,25 +2075,27 @@ static void qcom_geni_serial_remove(struct platform_device *pdev) static int __maybe_unused qcom_geni_serial_runtime_suspend(struct device *dev) { struct qcom_geni_serial_port *port = dev_get_drvdata(dev); - struct uart_port *uport = &port->uport; - int ret = 0; - if (port->dev_data->power_state) - ret = port->dev_data->power_state(uport, false); - - return ret; + return port->dev_data->power_off ? + port->dev_data->power_off(&port->se) : 0; } static int __maybe_unused qcom_geni_serial_runtime_resume(struct device *dev) { struct qcom_geni_serial_port *port = dev_get_drvdata(dev); struct uart_port *uport = &port->uport; - int ret = 0; + int ret; - if (port->dev_data->power_state) - ret = port->dev_data->power_state(uport, true); + if (port->dev_data->power_on) { + ret = port->dev_data->power_on(&port->se); + if (ret) + return ret; + } - return ret; + if (port->se.has_opp && port->clk_rate) + return dev_pm_opp_set_rate(uport->dev, port->clk_rate); + + return 0; } static int qcom_geni_serial_suspend(struct device *dev) @@ -2191,7 +2153,8 @@ static const struct qcom_geni_device_data qcom_geni_console_data = { .mode = GENI_SE_FIFO, .resources_init = geni_se_resources_init, .set_rate = geni_serial_set_rate, - .power_state = geni_serial_resource_state, + .power_on = geni_se_resources_activate, + .power_off = geni_se_resources_deactivate, }; static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = { @@ -2207,7 +2170,8 @@ static const struct qcom_geni_device_data qcom_geni_uart_data = { .mode = GENI_SE_DMA, .resources_init = geni_se_resources_init, .set_rate = geni_serial_set_rate, - .power_state = geni_serial_resource_state, + .power_on = geni_se_resources_activate, + .power_off = geni_se_resources_deactivate, }; static const struct qcom_geni_device_data sa8255p_qcom_geni_uart_data = { From f56b8eef48698fe479b623cb9a8706acfde05f04 Mon Sep 17 00:00:00 2001 From: Praveen Talari Date: Sat, 1 Aug 2026 11:18:17 +0530 Subject: [PATCH 78/97] serial: qcom-geni: Use geni_se_set_perf_level() for baud rate perf level The driver implements its own helper to select the performance level corresponding to a requested baud rate. The helper duplicates functionality already provided by geni_se_set_perf_level() in the GENI core. Replace the local implementation with the common helper and remove the associated duplicate definitions and code. This consolidates performance-level management in the GENI framework and reduces driver specific code. Signed-off-by: Praveen Talari Link: https://patch.msgid.link/20260801-reuse_common_geni_framework_helpers-v2-3-13753256ef71@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/qcom_geni_serial.c | 53 ++++----------------------- 1 file changed, 8 insertions(+), 45 deletions(-) diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c index 83ba0f5433ef..c268ebba2ee2 100644 --- a/drivers/tty/serial/qcom_geni_serial.c +++ b/drivers/tty/serial/qcom_geni_serial.c @@ -109,14 +109,12 @@ #define DMA_RX_BUF_SIZE 2048 static DEFINE_IDA(port_ida); -#define DOMAIN_IDX_POWER 0 -#define DOMAIN_IDX_PERF 1 struct qcom_geni_device_data { bool console; enum geni_se_xfer_mode mode; int (*resources_init)(struct geni_se *se); - int (*set_rate)(struct uart_port *uport, unsigned int baud); + int (*set_rate)(struct geni_se *se, unsigned long baud); int (*power_on)(struct geni_se *se); int (*power_off)(struct geni_se *se); }; @@ -1440,9 +1438,10 @@ static int qcom_geni_serial_startup(struct uart_port *uport) return 0; } -static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) +static int geni_serial_set_rate(struct geni_se *se, unsigned long baud) { - struct qcom_geni_serial_port *port = to_dev_port(uport); + struct qcom_geni_serial_port *port = dev_get_drvdata(se->dev); + struct uart_port *uport = &port->uport; unsigned long clk_rate; unsigned int avg_bw_core, clk_idx; unsigned int clk_div; @@ -1458,7 +1457,7 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) ret = geni_se_clk_freq_match(&port->se, baud * sampling_rate, &clk_idx, &clk_rate, false); if (ret) { - dev_err(port->se.dev, "Failed to find src clk for baud rate: %d ret: %d\n", + dev_err(port->se.dev, "Failed to find src clk for baud rate: %lu ret: %d\n", baud, ret); return ret; } @@ -1496,42 +1495,6 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud) return 0; } -static int geni_serial_set_level(struct uart_port *uport, unsigned int baud) -{ - struct qcom_geni_serial_port *port = to_dev_port(uport); - struct device *perf_dev = port->se.pd_list->pd_devs[DOMAIN_IDX_PERF]; - - /* - * The performance protocol sets UART communication - * speeds by selecting different performance levels - * through the OPP framework. - * - * Supported perf levels for baudrates in firmware are below - * +---------------------+--------------------+ - * | Perf level value | Baudrate values | - * +---------------------+--------------------+ - * | 300 | 300 | - * | 1200 | 1200 | - * | 2400 | 2400 | - * | 4800 | 4800 | - * | 9600 | 9600 | - * | 19200 | 19200 | - * | 38400 | 38400 | - * | 57600 | 57600 | - * | 115200 | 115200 | - * | 230400 | 230400 | - * | 460800 | 460800 | - * | 921600 | 921600 | - * | 2000000 | 2000000 | - * | 3000000 | 3000000 | - * | 3200000 | 3200000 | - * | 4000000 | 4000000 | - * +---------------------+--------------------+ - */ - - return dev_pm_opp_set_level(perf_dev, baud); -} - static void qcom_geni_serial_set_termios(struct uart_port *uport, struct ktermios *termios, const struct ktermios *old) @@ -1550,7 +1513,7 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport, /* baud rate */ baud = uart_get_baud_rate(uport, termios, old, 300, 8000000); - ret = port->dev_data->set_rate(uport, baud); + ret = port->dev_data->set_rate(&port->se, baud); if (ret) return; @@ -2161,7 +2124,7 @@ static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = { .console = true, .mode = GENI_SE_FIFO, .resources_init = geni_se_domain_attach, - .set_rate = geni_serial_set_level, + .set_rate = geni_se_set_perf_level, }; #endif @@ -2178,7 +2141,7 @@ static const struct qcom_geni_device_data sa8255p_qcom_geni_uart_data = { .console = false, .mode = GENI_SE_DMA, .resources_init = geni_se_domain_attach, - .set_rate = geni_serial_set_level, + .set_rate = geni_se_set_perf_level, }; static const struct dev_pm_ops qcom_geni_serial_pm_ops = { From 0481a041e9569dfc2448ba4c6cb30335fcb8430f Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:07 +0800 Subject: [PATCH 79/97] serial: 8250: split Moxa PCIe serial board support out of 8250_pci The Moxa PCIe multiport serial boards are currently handled as part of 8250_pci.c. In preparation for adding Moxa-specific UART features and optimizations, move the Moxa PCIe implementation into a dedicated driver. This introduces drivers/tty/serial/8250/8250_mxpcie.c and wires it up via Kconfig and Makefile, while preserving the existing probe flow and device IDs. This change was suggested during earlier reviews by Andy Shevchenko [1][2]. No functional change intended. Link: https://lore.kernel.org/all/ZmQovC6TbDpTb3c8@surfacebook.localdomain/ [1] Link: https://lore.kernel.org/all/CAHp75VeDsVt0GQYUFxLM+obfmqXBPa3hM3YMsFbc26uzWZG-SQ@mail.gmail.com/ [2] Suggested-by: Andy Shevchenko Signed-off-by: Crescent Hsieh Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260731074820.735619-2-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 288 ++++++++++++++++++++++++++ drivers/tty/serial/8250/8250_pci.c | 245 +--------------------- drivers/tty/serial/8250/Kconfig | 10 + drivers/tty/serial/8250/Makefile | 1 + 4 files changed, 302 insertions(+), 242 deletions(-) create mode 100644 drivers/tty/serial/8250/8250_mxpcie.c diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c new file mode 100644 index 000000000000..e75446923d08 --- /dev/null +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Moxa PCIe multiport serial device driver + * + * Copyright (C) 2025 Moxa Inc. (support@moxa.com) + * Author: Crescent Hsieh + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "8250.h" + +#define PCI_DEVICE_ID_MOXA_CP102E 0x1024 +#define PCI_DEVICE_ID_MOXA_CP102EL 0x1025 +#define PCI_DEVICE_ID_MOXA_CP102N 0x1027 +#define PCI_DEVICE_ID_MOXA_CP104EL_A 0x1045 +#define PCI_DEVICE_ID_MOXA_CP104N 0x1046 +#define PCI_DEVICE_ID_MOXA_CP112N 0x1121 +#define PCI_DEVICE_ID_MOXA_CP114EL 0x1144 +#define PCI_DEVICE_ID_MOXA_CP114N 0x1145 +#define PCI_DEVICE_ID_MOXA_CP116E_A_A 0x1160 +#define PCI_DEVICE_ID_MOXA_CP116E_A_B 0x1161 +#define PCI_DEVICE_ID_MOXA_CP118EL_A 0x1182 +#define PCI_DEVICE_ID_MOXA_CP118E_A_I 0x1183 +#define PCI_DEVICE_ID_MOXA_CP132EL 0x1322 +#define PCI_DEVICE_ID_MOXA_CP132N 0x1323 +#define PCI_DEVICE_ID_MOXA_CP134EL_A 0x1342 +#define PCI_DEVICE_ID_MOXA_CP134N 0x1343 +#define PCI_DEVICE_ID_MOXA_CP138E_A 0x1381 +#define PCI_DEVICE_ID_MOXA_CP168EL_A 0x1683 + +/* Bits in PCI device ID encoding board capabilities */ +#define MOXA_DEV_ID_IFACE_MASK GENMASK(11, 8) /* Supported serial interface */ +#define MOXA_DEV_ID_NPORTS_MASK GENMASK(7, 4) /* Number of UART ports */ + +/* UART */ +#define MOXA_PUART_BASE_BAUD 921600 +#define MOXA_PUART_OFFSET 0x200 + +#define MOXA_GPIO_DIRECTION 0x09 +#define MOXA_GPIO_OUTPUT 0x0A + +#define MOXA_GPIO_PIN2 BIT(2) + +#define MOXA_UIR_OFFSET 0x04 +#define MOXA_UIR_RS232 0x00 +#define MOXA_UIR_RS422 0x01 +#define MOXA_UIR_RS485_4W 0x0B +#define MOXA_UIR_RS485_2W 0x0F + +#define MOXA_EVEN_RS_MASK GENMASK(3, 0) +#define MOXA_ODD_RS_MASK GENMASK(7, 4) + +struct mxpcie8250 { + unsigned int supp_rs; + unsigned int num_ports; + void __iomem *bar1_base; /* UART registers (MMIO) */ + void __iomem *bar2_base; /* UIR / GPIO / CPLD (IO) */ + int line[] __counted_by(num_ports); +}; + +enum { + MOXA_SUPP_RS232 = BIT(0), + MOXA_SUPP_RS422 = BIT(1), + MOXA_SUPP_RS485 = BIT(2), +}; + +static bool mxpcie8250_is_mini_pcie(unsigned short device) +{ + if (device == PCI_DEVICE_ID_MOXA_CP102N || + device == PCI_DEVICE_ID_MOXA_CP104N || + device == PCI_DEVICE_ID_MOXA_CP112N || + device == PCI_DEVICE_ID_MOXA_CP114N || + device == PCI_DEVICE_ID_MOXA_CP132N || + device == PCI_DEVICE_ID_MOXA_CP134N) + return true; + + return false; +} + +static unsigned int mxpcie8250_get_supp_rs(unsigned short device) +{ + switch (device & MOXA_DEV_ID_IFACE_MASK) { + case 0x0000: + case 0x0600: + return MOXA_SUPP_RS232; + case 0x0100: + return MOXA_SUPP_RS232 | MOXA_SUPP_RS422 | MOXA_SUPP_RS485; + case 0x0300: + return MOXA_SUPP_RS422 | MOXA_SUPP_RS485; + default: + return 0; + } +} + +static unsigned short mxpcie8250_get_nports(unsigned short device) +{ + switch (device) { + case PCI_DEVICE_ID_MOXA_CP116E_A_A: + case PCI_DEVICE_ID_MOXA_CP116E_A_B: + return 8; + default: + return FIELD_GET(MOXA_DEV_ID_NPORTS_MASK, device); + } +} + +static void mxpcie8250_set_interface(struct mxpcie8250 *priv, + unsigned int port_idx, + u8 mode) +{ + void __iomem *uir_addr = priv->bar2_base + MOXA_UIR_OFFSET + port_idx / 2; + u8 cval; + + cval = ioread8(uir_addr); + + if (port_idx % 2) + FIELD_MODIFY(MOXA_ODD_RS_MASK, &cval, mode); + else + FIELD_MODIFY(MOXA_EVEN_RS_MASK, &cval, mode); + + iowrite8(cval, uir_addr); +} + +static void mxpcie8250_init_board(struct pci_dev *pdev, struct mxpcie8250 *priv) +{ + void __iomem *bar2_base = priv->bar2_base; + unsigned short device = pdev->device; + u8 cval; + + /* Initial terminator */ + if (device == PCI_DEVICE_ID_MOXA_CP114EL || + device == PCI_DEVICE_ID_MOXA_CP118EL_A) { + iowrite8(0xff, bar2_base + MOXA_GPIO_DIRECTION); + iowrite8(0x00, bar2_base + MOXA_GPIO_OUTPUT); + } + /* + * Enable hardware buffer to prevent break signal output when system boots up. + * This hardware buffer is only supported on Mini PCIe series. + */ + if (mxpcie8250_is_mini_pcie(device)) { + /* Set GPIO direction */ + cval = ioread8(bar2_base + MOXA_GPIO_DIRECTION); + cval |= MOXA_GPIO_PIN2; + iowrite8(cval, bar2_base + MOXA_GPIO_DIRECTION); + /* Enable low GPIO */ + cval = ioread8(bar2_base + MOXA_GPIO_OUTPUT); + cval &= ~MOXA_GPIO_PIN2; + iowrite8(cval, bar2_base + MOXA_GPIO_OUTPUT); + } +} + +static void mxpcie8250_setup_port(struct pci_dev *pdev, + struct mxpcie8250 *priv, + struct uart_8250_port *up, + int idx) +{ + unsigned short device = pdev->device; + int offset = idx * MOXA_PUART_OFFSET; + u8 init_mode = MOXA_UIR_RS232; + + if (!(priv->supp_rs & MOXA_SUPP_RS232)) + init_mode = MOXA_UIR_RS422; + + mxpcie8250_set_interface(priv, idx, init_mode); + + if (idx == 3 && + (device == PCI_DEVICE_ID_MOXA_CP104EL_A || + device == PCI_DEVICE_ID_MOXA_CP114EL || + device == PCI_DEVICE_ID_MOXA_CP134EL_A)) + offset = 7 * MOXA_PUART_OFFSET; + + up->port.mapbase = pci_resource_start(pdev, FL_BASE1) + offset; + up->port.membase = pcim_iomap_table(pdev)[FL_BASE1] + offset; +} + +static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct uart_8250_port up = {}; + struct mxpcie8250 *priv; + unsigned short device = pdev->device; + unsigned int num_ports; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + num_ports = mxpcie8250_get_nports(device); + + priv = devm_kzalloc(dev, struct_size(priv, line, num_ports), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->num_ports = num_ports; + priv->supp_rs = mxpcie8250_get_supp_rs(device); + + priv->bar1_base = pcim_iomap(pdev, FL_BASE1, 0); + if (!priv->bar1_base) + return -ENOMEM; + + priv->bar2_base = pcim_iomap(pdev, FL_BASE2, 0); + if (!priv->bar2_base) + return -ENOMEM; + + mxpcie8250_init_board(pdev, priv); + + up.port.dev = dev; + up.port.irq = pdev->irq; + up.port.uartclk = MOXA_PUART_BASE_BAUD * 16; + up.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ; + + up.port.iotype = UPIO_MEM; + up.port.iobase = 0; + up.port.regshift = 0; + + for (unsigned int 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", + up.port.iobase, up.port.irq, up.port.iotype); + + priv->line[i] = serial8250_register_8250_port(&up); + if (priv->line[i] < 0) { + dev_err(dev, + "Couldn't register serial port %lx, irq %d, type %d, error %d\n", + up.port.iobase, up.port.irq, + up.port.iotype, priv->line[i]); + break; + } + } + pci_set_drvdata(pdev, priv); + + return 0; +} + +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++) + serial8250_unregister_port(priv->line[i]); +} + +static const struct pci_device_id mxpcie8250_pci_ids[] = { + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102E) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102EL) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102N) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104EL_A) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104N) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP112N) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP114EL) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP114N) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP116E_A_A) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP116E_A_B) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP118EL_A) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP118E_A_I) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP132EL) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP132N) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP134EL_A) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP134N) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP138E_A) }, + { PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP168EL_A) }, + { } +}; +MODULE_DEVICE_TABLE(pci, mxpcie8250_pci_ids); + +static struct pci_driver mxpcie8250_pci_driver = { + .name = "8250_mxpcie", + .id_table = mxpcie8250_pci_ids, + .probe = mxpcie8250_probe, + .remove = mxpcie8250_remove, +}; +module_pci_driver(mxpcie8250_pci_driver); + +MODULE_AUTHOR("Moxa Inc."); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Moxa PCIe Multiport Serial Device Driver"); diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index 58b4e525bdb6..c24a59896982 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -76,25 +76,6 @@ #define PCI_DEVICE_ID_WCHIC_CH384_4S 0x3470 #define PCI_DEVICE_ID_WCHIC_CH384_8S 0x3853 -#define PCI_DEVICE_ID_MOXA_CP102E 0x1024 -#define PCI_DEVICE_ID_MOXA_CP102EL 0x1025 -#define PCI_DEVICE_ID_MOXA_CP102N 0x1027 -#define PCI_DEVICE_ID_MOXA_CP104EL_A 0x1045 -#define PCI_DEVICE_ID_MOXA_CP104N 0x1046 -#define PCI_DEVICE_ID_MOXA_CP112N 0x1121 -#define PCI_DEVICE_ID_MOXA_CP114EL 0x1144 -#define PCI_DEVICE_ID_MOXA_CP114N 0x1145 -#define PCI_DEVICE_ID_MOXA_CP116E_A_A 0x1160 -#define PCI_DEVICE_ID_MOXA_CP116E_A_B 0x1161 -#define PCI_DEVICE_ID_MOXA_CP118EL_A 0x1182 -#define PCI_DEVICE_ID_MOXA_CP118E_A_I 0x1183 -#define PCI_DEVICE_ID_MOXA_CP132EL 0x1322 -#define PCI_DEVICE_ID_MOXA_CP132N 0x1323 -#define PCI_DEVICE_ID_MOXA_CP134EL_A 0x1342 -#define PCI_DEVICE_ID_MOXA_CP134N 0x1343 -#define PCI_DEVICE_ID_MOXA_CP138E_A 0x1381 -#define PCI_DEVICE_ID_MOXA_CP168EL_A 0x1683 - #define PCI_DEVICE_ID_ADDIDATA_CPCI7500 0x7003 #define PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG 0x7024 #define PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG 0x7025 @@ -2002,138 +1983,6 @@ pci_sunix_setup(struct serial_private *priv, return setup_port(priv, port, bar, offset, 0); } -#define MOXA_PUART_GPIO_EN 0x09 -#define MOXA_PUART_GPIO_OUT 0x0A - -#define MOXA_GPIO_PIN2 BIT(2) - -#define MOXA_RS232 0x00 -#define MOXA_RS422 0x01 -#define MOXA_RS485_4W 0x0B -#define MOXA_RS485_2W 0x0F -#define MOXA_UIR_OFFSET 0x04 -#define MOXA_EVEN_RS_MASK GENMASK(3, 0) -#define MOXA_ODD_RS_MASK GENMASK(7, 4) - -enum { - MOXA_SUPP_RS232 = BIT(0), - MOXA_SUPP_RS422 = BIT(1), - MOXA_SUPP_RS485 = BIT(2), -}; - -static unsigned short moxa_get_nports(unsigned short device) -{ - switch (device) { - case PCI_DEVICE_ID_MOXA_CP116E_A_A: - case PCI_DEVICE_ID_MOXA_CP116E_A_B: - return 8; - } - - return FIELD_GET(0x00F0, device); -} - -static bool pci_moxa_is_mini_pcie(unsigned short device) -{ - if (device == PCI_DEVICE_ID_MOXA_CP102N || - device == PCI_DEVICE_ID_MOXA_CP104N || - device == PCI_DEVICE_ID_MOXA_CP112N || - device == PCI_DEVICE_ID_MOXA_CP114N || - device == PCI_DEVICE_ID_MOXA_CP132N || - device == PCI_DEVICE_ID_MOXA_CP134N) - return true; - - return false; -} - -static unsigned int pci_moxa_supported_rs(struct pci_dev *dev) -{ - switch (dev->device & 0x0F00) { - case 0x0000: - case 0x0600: - return MOXA_SUPP_RS232; - case 0x0100: - return MOXA_SUPP_RS232 | MOXA_SUPP_RS422 | MOXA_SUPP_RS485; - case 0x0300: - return MOXA_SUPP_RS422 | MOXA_SUPP_RS485; - } - return 0; -} - -static int pci_moxa_set_interface(const struct pci_dev *dev, - unsigned int port_idx, - u8 mode) -{ - resource_size_t iobar_addr = pci_resource_start(dev, 2); - resource_size_t UIR_addr = iobar_addr + MOXA_UIR_OFFSET + port_idx / 2; - u8 val; - - val = inb(UIR_addr); - - if (port_idx % 2) { - val &= ~MOXA_ODD_RS_MASK; - val |= FIELD_PREP(MOXA_ODD_RS_MASK, mode); - } else { - val &= ~MOXA_EVEN_RS_MASK; - val |= FIELD_PREP(MOXA_EVEN_RS_MASK, mode); - } - outb(val, UIR_addr); - - return 0; -} - -static int pci_moxa_init(struct pci_dev *dev) -{ - unsigned short device = dev->device; - resource_size_t iobar_addr = pci_resource_start(dev, 2); - unsigned int i, num_ports = moxa_get_nports(device); - u8 val, init_mode = MOXA_RS232; - - if (!IS_ENABLED(CONFIG_HAS_IOPORT)) - return serial_8250_warn_need_ioport(dev); - - if (!(pci_moxa_supported_rs(dev) & MOXA_SUPP_RS232)) { - init_mode = MOXA_RS422; - } - for (i = 0; i < num_ports; ++i) - pci_moxa_set_interface(dev, i, init_mode); - - /* - * Enable hardware buffer to prevent break signal output when system boots up. - * This hardware buffer is only supported on Mini PCIe series. - */ - if (pci_moxa_is_mini_pcie(device)) { - /* Set GPIO direction */ - val = inb(iobar_addr + MOXA_PUART_GPIO_EN); - val |= MOXA_GPIO_PIN2; - outb(val, iobar_addr + MOXA_PUART_GPIO_EN); - /* Enable low GPIO */ - val = inb(iobar_addr + MOXA_PUART_GPIO_OUT); - val &= ~MOXA_GPIO_PIN2; - outb(val, iobar_addr + MOXA_PUART_GPIO_OUT); - } - - return num_ports; -} - -static int -pci_moxa_setup(struct serial_private *priv, - const struct pciserial_board *board, - struct uart_8250_port *port, int idx) -{ - unsigned int bar = FL_GET_BASE(board->flags); - int offset; - - if (!IS_ENABLED(CONFIG_HAS_IOPORT)) - return serial_8250_warn_need_ioport(priv->dev); - - if (board->num_ports == 4 && idx == 3) - offset = 7 * board->uart_offset; - else - offset = idx * board->uart_offset; - - return setup_port(priv, port, bar, offset, 0); -} - #define SB_OPTR_IMR0 0x0c /* Interrupt mask register, p0 to p7 */ static int pci_systembase_init(struct pci_dev *dev) { @@ -2996,17 +2845,6 @@ static struct pci_serial_quirk pci_serial_quirks[] = { .setup = pci_fintek_setup, .init = pci_fintek_init, }, - /* - * MOXA - */ - { - .vendor = PCI_VENDOR_ID_MOXA, - .device = PCI_ANY_ID, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - .init = pci_moxa_init, - .setup = pci_moxa_setup, - }, { .vendor = 0x1c29, .device = 0x1204, @@ -3225,9 +3063,6 @@ enum pci_board_num_t { pbn_titan_2_4000000, pbn_titan_4_4000000, pbn_titan_8_4000000, - pbn_moxa_2, - pbn_moxa_4, - pbn_moxa_8, }; /* @@ -4005,24 +3840,6 @@ static struct pciserial_board pci_boards[] = { .uart_offset = 0x200, .first_offset = 0x1000, }, - [pbn_moxa_2] = { - .flags = FL_BASE1, - .num_ports = 2, - .base_baud = 921600, - .uart_offset = 0x200, - }, - [pbn_moxa_4] = { - .flags = FL_BASE1, - .num_ports = 4, - .base_baud = 921600, - .uart_offset = 0x200, - }, - [pbn_moxa_8] = { - .flags = FL_BASE1, - .num_ports = 8, - .base_baud = 921600, - .uart_offset = 0x200, - }, }; #define REPORT_CONFIG(option) \ @@ -4076,6 +3893,9 @@ static const struct pci_device_id blacklist[] = { { PCI_VDEVICE(PERICOM, PCI_ANY_ID), .driver_data = REPORT_8250_CONFIG(PERICOM), }, { PCI_VDEVICE(ACCESSIO, PCI_ANY_ID), .driver_data = REPORT_8250_CONFIG(PERICOM), }, + /* Moxa devices */ + { PCI_VDEVICE(MOXA, PCI_ANY_ID), REPORT_8250_CONFIG(MOXA), }, + /* End of the black list */ { } }; @@ -5950,65 +5770,6 @@ static const struct pci_device_id serial_pci_tbl[] = { .driver_data = pbn_ni8430_4, }, - /* - * MOXA - */ - { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102E), - .driver_data = pbn_moxa_2, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102EL), - .driver_data = pbn_moxa_2, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP102N), - .driver_data = pbn_moxa_2, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104EL_A), - .driver_data = pbn_moxa_4, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP104N), - .driver_data = pbn_moxa_4, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP112N), - .driver_data = pbn_moxa_2, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP114EL), - .driver_data = pbn_moxa_4, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP114N), - .driver_data = pbn_moxa_4, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP116E_A_A), - .driver_data = pbn_moxa_8, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP116E_A_B), - .driver_data = pbn_moxa_8, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP118EL_A), - .driver_data = pbn_moxa_8, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP118E_A_I), - .driver_data = pbn_moxa_8, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP132EL), - .driver_data = pbn_moxa_2, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP132N), - .driver_data = pbn_moxa_2, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP134EL_A), - .driver_data = pbn_moxa_4, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP134N), - .driver_data = pbn_moxa_4, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP138E_A), - .driver_data = pbn_moxa_8, - }, { - PCI_VDEVICE(MOXA, PCI_DEVICE_ID_MOXA_CP168EL_A), - .driver_data = pbn_moxa_8, - }, - /* * ADDI-DATA GmbH communication cards */ diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index fc3e58d62233..fa6f33303cb0 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -141,6 +141,16 @@ config SERIAL_8250_EXAR 422x PCIe serial cards that are not covered by the more generic SERIAL_8250_PCI option. +config SERIAL_8250_MOXA_PCIE + tristate "8250/16550 Moxa PCIe device support" + depends on SERIAL_8250 && PCI + default SERIAL_8250 + help + Say Y here if you have a Moxa PCIe serial card. + + To compile this driver as a module, choose M here: the + module will be called 8250_mxpcie. + config SERIAL_8250_HP300 tristate depends on SERIAL_8250 && HP300 diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index 6d21402b4435..46adadcfe072 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_SERIAL_8250_LPC18XX) += 8250_lpc18xx.o obj-$(CONFIG_SERIAL_8250_LPSS) += 8250_lpss.o obj-$(CONFIG_SERIAL_8250_MEN_MCB) += 8250_men_mcb.o obj-$(CONFIG_SERIAL_8250_MID) += 8250_mid.o +obj-$(CONFIG_SERIAL_8250_MOXA_PCIE) += 8250_mxpcie.o obj-$(CONFIG_SERIAL_8250_MT6577) += 8250_mtk.o obj-$(CONFIG_SERIAL_8250_NI) += 8250_ni.o obj-$(CONFIG_SERIAL_OF_PLATFORM) += 8250_of.o From b5c53df53414635385a1711d0e6262b78b7f4123 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:08 +0800 Subject: [PATCH 80/97] serial: 8250: add Moxa MUEx50 UART port type Add a new 8250 port type for the Moxa MUEx50 UART and describe its basic FIFO size and trigger characteristics in the 8250 port configuration table. The 8250_mxpcie driver sets UPF_FIXED_TYPE and uses PORT_MUEX50 so that the generic 8250 core applies the correct defaults. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-3-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 3 ++- drivers/tty/serial/8250/8250_port.c | 8 ++++++++ include/uapi/linux/serial_core.h | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index e75446923d08..9f4374d75fcc 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -218,7 +218,8 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.dev = dev; up.port.irq = pdev->irq; up.port.uartclk = MOXA_PUART_BASE_BAUD * 16; - up.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ; + up.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ | UPF_FIXED_TYPE; + up.port.type = PORT_MUEX50; up.port.iotype = UPIO_MEM; up.port.iobase = 0; diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 6c10bff10970..12902036e511 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -311,6 +311,14 @@ static const struct serial8250_config uart_config[] = { .rxtrig_bytes = {1, 8, 16, 30}, .flags = UART_CAP_FIFO | UART_CAP_AFE, }, + [PORT_MUEX50] = { + .name = "Moxa MUEx50 UART", + .fifo_size = 128, + .tx_loadsz = 128, + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, + .rxtrig_bytes = {15, 31, 63, 111}, + .flags = UART_CAP_FIFO, + }, }; /* Uart divisor latch read */ diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 9c007a106330..377884e3856a 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -100,6 +100,9 @@ /* TXX9 type number */ #define PORT_TXX9 64 +/* Moxa MUEx50 UART */ +#define PORT_MUEX50 65 + /*Digi jsm */ #define PORT_JSM 69 From 0a53c72969e72041476a5ae3fe8381fc7e2c13dd Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:09 +0800 Subject: [PATCH 81/97] serial: 8250_mxpcie: enable enhanced mode and program FIFO trigger levels The MUEx50 UART provides an enhanced register set and programmable FIFO trigger levels for RX, TX, and flow control. Enable enhanced mode during port startup and program the MUEx50 FIFO trigger registers according to the configured port settings. Clear the programmed state again during shutdown to restore the default UART configuration. The TX FIFO write pointer and read pointer are driven by different clocks. Clear the FIFOs repeatedly during startup so both pointers are reset before programming the trigger levels. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-4-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 9f4374d75fcc..e1d607b607e8 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -46,6 +46,23 @@ /* UART */ #define MOXA_PUART_BASE_BAUD 921600 #define MOXA_PUART_OFFSET 0x200 +#define MOXA_PUART_TX_TRIG_DEFAULT 0 +#define MOXA_PUART_RX_TRIG_DEFAULT 96 +#define MOXA_PUART_RX_FLOW_LOW_DEFAULT 16 +#define MOXA_PUART_RX_FLOW_HIGH_DEFAULT 110 + +/* Special Function Register (SFR) */ +#define MOXA_PUART_SFR 0x07 +#define MOXA_PUART_SFR_950 BIT(5) + +/* Enhanced Function Register (EFR) */ +#define MOXA_PUART_EFR 0x0A +#define MOXA_PUART_EFR_ENHANCED BIT(4) + +#define MOXA_PUART_TTL 0x10 /* Tx Interrupt Trigger Level */ +#define MOXA_PUART_RTL 0x11 /* Rx Interrupt Trigger Level */ +#define MOXA_PUART_FCL 0x12 /* Flow Control Low Trigger Level */ +#define MOXA_PUART_FCH 0x13 /* Flow Control High Trigger Level */ #define MOXA_GPIO_DIRECTION 0x09 #define MOXA_GPIO_OUTPUT 0x0A @@ -131,6 +148,49 @@ static void mxpcie8250_set_interface(struct mxpcie8250 *priv, iowrite8(cval, uir_addr); } +static int mxpcie8250_startup(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + int ret; + + ret = serial8250_do_startup(port); + if (ret) + return ret; + + /* + * The TX FIFO write pointer (w_ptr) and read pointer (r_ptr) + * are driven by different clocks: w_ptr uses the PCIe clock + * and r_ptr uses the UART clock. When TX FIFO flush is requested, + * w_ptr may be cleared before r_ptr, so the UART can still observe + * pending TX data. + * + * It is recommended to clear the FIFOs at least 5 times to ensure + * both pointers are reset. + */ + for (unsigned int i = 0; i < 5; ++i) + serial_out(up, UART_FCR, UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + + serial_out(up, MOXA_PUART_EFR, MOXA_PUART_EFR_ENHANCED); + serial_out(up, MOXA_PUART_SFR, MOXA_PUART_SFR_950); + + serial_out(up, MOXA_PUART_TTL, MOXA_PUART_TX_TRIG_DEFAULT); + serial_out(up, MOXA_PUART_RTL, MOXA_PUART_RX_TRIG_DEFAULT); + serial_out(up, MOXA_PUART_FCL, MOXA_PUART_RX_FLOW_LOW_DEFAULT); + serial_out(up, MOXA_PUART_FCH, MOXA_PUART_RX_FLOW_HIGH_DEFAULT); + + return 0; +} + +static void mxpcie8250_shutdown(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + + serial_out(up, MOXA_PUART_EFR, 0); + serial_out(up, MOXA_PUART_SFR, 0); + + serial8250_do_shutdown(port); +} + static void mxpcie8250_init_board(struct pci_dev *pdev, struct mxpcie8250 *priv) { void __iomem *bar2_base = priv->bar2_base; @@ -225,6 +285,9 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.iobase = 0; up.port.regshift = 0; + up.port.startup = mxpcie8250_startup; + up.port.shutdown = mxpcie8250_shutdown; + for (unsigned int i = 0; i < num_ports; i++) { mxpcie8250_setup_port(pdev, priv, &up, i); From 55edf8511f4728a79757ab64ae9126e2ea59e310 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:10 +0800 Subject: [PATCH 82/97] serial: 8250_mxpcie: enable automatic RTS/CTS flow control The MUEx50 UART supports automatic RTS/CTS flow control via the enhanced feature register. Implement a mxpcie-specific set_termios() callback that enables MUEx50 auto-RTS/auto-CTS when CRTSCTS is requested and disables it otherwise. Keep the 8250 port status flags in sync with the hardware configuration. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-5-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index e1d607b607e8..25ca43225e7a 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -58,6 +58,10 @@ /* Enhanced Function Register (EFR) */ #define MOXA_PUART_EFR 0x0A #define MOXA_PUART_EFR_ENHANCED BIT(4) +#define MOXA_PUART_EFR_AUTO_RTS BIT(6) +#define MOXA_PUART_EFR_AUTO_CTS BIT(7) +#define MOXA_PUART_EFR_RX_FLOW_MASK GENMASK(1, 0) +#define MOXA_PUART_EFR_TX_FLOW_MASK GENMASK(3, 2) #define MOXA_PUART_TTL 0x10 /* Tx Interrupt Trigger Level */ #define MOXA_PUART_RTL 0x11 /* Rx Interrupt Trigger Level */ @@ -148,6 +152,29 @@ static void mxpcie8250_set_interface(struct mxpcie8250 *priv, iowrite8(cval, uir_addr); } +static void mxpcie8250_set_termios(struct uart_port *port, + struct ktermios *new, + 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; + + serial8250_do_set_termios(port, new, old); + + up->port.status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS); + + efr = serial_in(up, MOXA_PUART_EFR); + efr &= ~(MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS); + + if (cflag & CRTSCTS) { + efr |= (MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS); + up->port.status |= (UPSTAT_AUTORTS | UPSTAT_AUTOCTS); + } + serial_out(up, MOXA_PUART_EFR, efr); +} + static int mxpcie8250_startup(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); @@ -285,6 +312,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.iobase = 0; up.port.regshift = 0; + up.port.set_termios = mxpcie8250_set_termios; up.port.startup = mxpcie8250_startup; up.port.shutdown = mxpcie8250_shutdown; From d98cf22e68ec42b2f9eb7746c6bdae00caccde02 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:11 +0800 Subject: [PATCH 83/97] serial: 8250_mxpcie: offload XON/XOFF flow control to MUEx50 hardware The MUEx50 UART can handle in-band software flow control (XON/XOFF) directly in hardware. Program the on-chip XON/XOFF characters from termios settings and enable the corresponding MUEx50 flow control modes when IXON or IXOFF is requested. Provide throttle and unthrottle callbacks so RX can be stopped and resumed cleanly. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-6-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 51 +++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 25ca43225e7a..39eee8a9b82d 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -61,8 +61,20 @@ #define MOXA_PUART_EFR_AUTO_RTS BIT(6) #define MOXA_PUART_EFR_AUTO_CTS BIT(7) #define MOXA_PUART_EFR_RX_FLOW_MASK GENMASK(1, 0) +#define MOXA_PUART_EFR_RX_FLOW_DISABLED 0x0 +#define MOXA_PUART_EFR_RX_FLOW_XON2_XOFF2 0x1 +#define MOXA_PUART_EFR_RX_FLOW_XON1_XOFF1 0x2 +#define MOXA_PUART_EFR_RX_FLOW_COPY_TX 0x3 #define MOXA_PUART_EFR_TX_FLOW_MASK GENMASK(3, 2) +#define MOXA_PUART_EFR_TX_FLOW_DISABLED 0x0 +#define MOXA_PUART_EFR_TX_FLOW_XON2_XOFF2 0x1 +#define MOXA_PUART_EFR_TX_FLOW_XON1_XOFF1 0x2 +#define MOXA_PUART_EFR_TX_FLOW_RESERVED 0x3 +#define MOXA_PUART_XON1 0x0B +#define MOXA_PUART_XON2 0x0C +#define MOXA_PUART_XOFF1 0x0D +#define MOXA_PUART_XOFF2 0x0E #define MOXA_PUART_TTL 0x10 /* Tx Interrupt Trigger Level */ #define MOXA_PUART_RTL 0x11 /* Rx Interrupt Trigger Level */ #define MOXA_PUART_FCL 0x12 /* Flow Control Low Trigger Level */ @@ -159,11 +171,11 @@ static void mxpcie8250_set_termios(struct uart_port *port, 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; + u8 efr, val; serial8250_do_set_termios(port, new, old); - up->port.status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS); + up->port.status &= ~(UPSTAT_AUTORTS | UPSTAT_AUTOCTS | UPSTAT_AUTOXOFF); efr = serial_in(up, MOXA_PUART_EFR); efr &= ~(MOXA_PUART_EFR_AUTO_RTS | MOXA_PUART_EFR_AUTO_CTS); @@ -172,6 +184,21 @@ static void mxpcie8250_set_termios(struct uart_port *port, 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)); + + val = I_IXON(tty) ? 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; + FIELD_MODIFY(MOXA_PUART_EFR_TX_FLOW_MASK, &efr, val); + + if (I_IXOFF(tty)) + up->port.status |= UPSTAT_AUTOXOFF; + serial_out(up, MOXA_PUART_EFR, efr); } @@ -218,6 +245,24 @@ static void mxpcie8250_shutdown(struct uart_port *port) serial8250_do_shutdown(port); } +static void mxpcie8250_throttle(struct uart_port *port) +{ + guard(uart_port_lock_irqsave)(port); + + port->ops->stop_rx(port); +} + +static void mxpcie8250_unthrottle(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + + guard(uart_port_lock_irqsave)(port); + + up->ier |= UART_IER_RLSI | UART_IER_RDI; + port->read_status_mask |= UART_LSR_DR; + serial_out(up, UART_IER, up->ier); +} + static void mxpcie8250_init_board(struct pci_dev *pdev, struct mxpcie8250 *priv) { void __iomem *bar2_base = priv->bar2_base; @@ -315,6 +360,8 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.set_termios = mxpcie8250_set_termios; up.port.startup = mxpcie8250_startup; up.port.shutdown = mxpcie8250_shutdown; + up.port.throttle = mxpcie8250_throttle; + up.port.unthrottle = mxpcie8250_unthrottle; for (unsigned int i = 0; i < num_ports; i++) { mxpcie8250_setup_port(pdev, priv, &up, i); From e8224757c6e7cd2b521b18cbccc70ee4ae1a4977 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:12 +0800 Subject: [PATCH 84/97] serial: 8250_mxpcie: add custom handle_irq callback Add a mxpcie-specific handle_irq() implementation for Moxa PCIe serial ports. This keeps the interrupt handling self-contained in the driver and provides a hook point for MUEx50-specific RX/TX paths added in subsequent patches. The handler processes RX, updates modem status, and handles TX when THRE is asserted. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-7-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 39eee8a9b82d..9190825ff3d3 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -263,6 +263,47 @@ static void mxpcie8250_unthrottle(struct uart_port *port) serial_out(up, UART_IER, up->ier); } +static bool mxpcie8250_should_rx(struct uart_8250_port *up, u16 lsr) +{ + struct uart_port *port = &up->port; + + if (!(lsr & (UART_LSR_DR | UART_LSR_BI))) + return false; + + if (!(port->status & (UPSTAT_AUTOCTS | UPSTAT_AUTORTS))) + return true; + if (lsr & (UART_LSR_FIFOE | UART_LSR_BRK_ERROR_BITS)) + return true; + if (port->read_status_mask & UART_LSR_DR) + return true; + + return false; +} + +static int mxpcie8250_handle_irq(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + u16 lsr; + u8 iir; + + iir = serial_in(up, UART_IIR); + if (iir & UART_IIR_NO_INT) + return 0; + + guard(uart_port_lock_check_sysrq_irqsave)(port); + + lsr = serial_lsr_in(up); + if (mxpcie8250_should_rx(up, lsr)) + lsr = serial8250_rx_chars(up, lsr); + + serial8250_modem_status(up); + + if ((lsr & UART_LSR_THRE) && (up->ier & UART_IER_THRI)) + serial8250_tx_chars(up); + + return 1; +} + static void mxpcie8250_init_board(struct pci_dev *pdev, struct mxpcie8250 *priv) { void __iomem *bar2_base = priv->bar2_base; @@ -362,6 +403,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.shutdown = mxpcie8250_shutdown; up.port.throttle = mxpcie8250_throttle; up.port.unthrottle = mxpcie8250_unthrottle; + up.port.handle_irq = mxpcie8250_handle_irq; for (unsigned int i = 0; i < num_ports; i++) { mxpcie8250_setup_port(pdev, priv, &up, i); From c016f2405cec3809c4be960ab6c3a9c3ee82ef71 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:13 +0800 Subject: [PATCH 85/97] serial: 8250_mxpcie: speed up RX using memory-mapped FIFO window The MUEx50 UART provides a memory-mapped RX FIFO data window along with an RX FIFO byte counter. When no break or error conditions are present, read received data in bulk via the MMIO FIFO window and push it to the tty layer in one operation. Fall back to the generic 8250 RX path for break and error handling. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-8-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 32 +++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 9190825ff3d3..415468a02d0d 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,9 @@ #define MOXA_PUART_RTL 0x11 /* Rx Interrupt Trigger Level */ #define MOXA_PUART_FCL 0x12 /* Flow Control Low Trigger Level */ #define MOXA_PUART_FCH 0x13 /* Flow Control High Trigger Level */ +#define MOXA_PUART_RX_FIFO_CNT 0x15 /* Rx FIFO Data Counter */ + +#define MOXA_PUART_RX_FIFO_MEM 0x100 /* Memory Space to Rx FIFO Data Register */ #define MOXA_GPIO_DIRECTION 0x09 #define MOXA_GPIO_OUTPUT 0x0A @@ -263,6 +267,26 @@ static void mxpcie8250_unthrottle(struct uart_port *port) serial_out(up, UART_IER, up->ier); } +static void mxpcie8250_rx_chars(struct uart_8250_port *up) +{ + struct uart_port *port = &up->port; + struct tty_port *tport = &port->state->port; + unsigned int count; + u8 *buf; + + count = serial_in(up, MOXA_PUART_RX_FIFO_CNT); + count = min(count, port->fifosize); + count = tty_prepare_flip_string(tport, &buf, count); + if (!count) + return; + + for (unsigned int i = 0; i < count; ++i) + buf[i] = serial_in(up, MOXA_PUART_RX_FIFO_MEM + i); + + port->icount.rx += count; + tty_flip_buffer_push(tport); +} + static bool mxpcie8250_should_rx(struct uart_8250_port *up, u16 lsr) { struct uart_port *port = &up->port; @@ -293,8 +317,12 @@ static int mxpcie8250_handle_irq(struct uart_port *port) guard(uart_port_lock_check_sysrq_irqsave)(port); lsr = serial_lsr_in(up); - if (mxpcie8250_should_rx(up, lsr)) - lsr = serial8250_rx_chars(up, lsr); + if (mxpcie8250_should_rx(up, lsr)) { + if (!(lsr & UART_LSR_BRK_ERROR_BITS)) + mxpcie8250_rx_chars(up); + else + lsr = serial8250_rx_chars(up, lsr); + } serial8250_modem_status(up); From 7df53c4d3364de240c94591aadb20737ddc970ea Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:14 +0800 Subject: [PATCH 86/97] serial: 8250_mxpcie: speed up TX using memory-mapped FIFO window The MUEx50 UART provides a memory-mapped TX FIFO data window along with a TX FIFO level counter. Fill the TX FIFO in bulk via the MMIO FIFO window based on available FIFO space, using uart_port_tx_limited() for the common serial-core TX handling. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-9-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 415468a02d0d..e864fb0624e5 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -81,8 +81,10 @@ #define MOXA_PUART_FCL 0x12 /* Flow Control Low Trigger Level */ #define MOXA_PUART_FCH 0x13 /* Flow Control High Trigger Level */ #define MOXA_PUART_RX_FIFO_CNT 0x15 /* Rx FIFO Data Counter */ +#define MOXA_PUART_TX_FIFO_CNT 0x16 /* Tx FIFO Data Counter */ #define MOXA_PUART_RX_FIFO_MEM 0x100 /* Memory Space to Rx FIFO Data Register */ +#define MOXA_PUART_TX_FIFO_MEM 0x100 /* Memory Space to Tx FIFO Data Register */ #define MOXA_GPIO_DIRECTION 0x09 #define MOXA_GPIO_OUTPUT 0x0A @@ -304,6 +306,18 @@ static bool mxpcie8250_should_rx(struct uart_8250_port *up, u16 lsr) return false; } +static void mxpcie8250_tx_chars(struct uart_8250_port *up) +{ + struct uart_port *port = &up->port; + unsigned int offset = 0; + unsigned char c; + + uart_port_tx_limited(port, c, port->fifosize - serial_in(up, MOXA_PUART_TX_FIFO_CNT), + true, + serial_out(up, MOXA_PUART_TX_FIFO_MEM + offset++, c), + ({})); +} + static int mxpcie8250_handle_irq(struct uart_port *port) { struct uart_8250_port *up = up_to_u8250p(port); @@ -327,7 +341,7 @@ static int mxpcie8250_handle_irq(struct uart_port *port) serial8250_modem_status(up); if ((lsr & UART_LSR_THRE) && (up->ier & UART_IER_THRI)) - serial8250_tx_chars(up); + mxpcie8250_tx_chars(up); return 1; } From d21a1509c62386200c09ed102983b8d79a1b0485 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:15 +0800 Subject: [PATCH 87/97] serial: 8250_mxpcie: support serial interface mode switching Moxa PCIe multiport serial boards support switching the serial interface mode between RS232, RS422, RS485-2W, and RS485-4W via on-board control registers. Implement an rs485_config() callback and map TIOCSRS485 requests to the corresponding hardware modes using serial_rs485 flags: - RS232 = (no flags set) - RS422 = SER_RS485_ENABLED | SER_RS485_MODE_RS422 - RS485_2W (half-duplex) = SER_RS485_ENABLED - RS485_4W (full-duplex) = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX This allows users to reconfigure the serial mode at runtime via ioctl(). Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-10-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 45 +++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index e864fb0624e5..93f0af958d11 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -114,6 +114,10 @@ enum { MOXA_SUPP_RS485 = BIT(2), }; +static const struct serial_rs485 mxpcie8250_rs485_supported = { + .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX | SER_RS485_MODE_RS422, +}; + static bool mxpcie8250_is_mini_pcie(unsigned short device) { if (device == PCI_DEVICE_ID_MOXA_CP102N || @@ -170,6 +174,38 @@ static void mxpcie8250_set_interface(struct mxpcie8250 *priv, iowrite8(cval, uir_addr); } +/* + * Moxa PCIe multiport serial boards support switching serial interfaces + * via the ioctl() command "TIOCSRS485". Supported modes and corresponding + * flags in "serial_rs485": + * + * RS232 = (no flags set) + * RS422 = SER_RS485_ENABLED | SER_RS485_MODE_RS422 + * RS485_2W (half-duplex) = SER_RS485_ENABLED + * RS485_4W (full-duplex) = SER_RS485_ENABLED | SER_RS485_RX_DURING_TX + */ +static int mxpcie8250_rs485_config(struct uart_port *port, + struct ktermios *termios, + struct serial_rs485 *rs485) +{ + struct mxpcie8250 *priv = dev_get_drvdata(port->dev); + u8 mode = MOXA_UIR_RS232; + + if (rs485->flags & SER_RS485_ENABLED) { + if (rs485->flags & SER_RS485_MODE_RS422) + mode = MOXA_UIR_RS422; + else if (rs485->flags & SER_RS485_RX_DURING_TX) + mode = MOXA_UIR_RS485_4W; + else + mode = MOXA_UIR_RS485_2W; + } else if (!(priv->supp_rs & MOXA_SUPP_RS232)) { + return -ENODEV; + } + mxpcie8250_set_interface(priv, port->port_id, mode); + + return 0; +} + static void mxpcie8250_set_termios(struct uart_port *port, struct ktermios *new, const struct ktermios *old) @@ -383,9 +419,14 @@ static void mxpcie8250_setup_port(struct pci_dev *pdev, int offset = idx * MOXA_PUART_OFFSET; u8 init_mode = MOXA_UIR_RS232; - if (!(priv->supp_rs & MOXA_SUPP_RS232)) + if (priv->supp_rs & MOXA_SUPP_RS485) { + up->port.rs485_config = mxpcie8250_rs485_config; + up->port.rs485_supported = mxpcie8250_rs485_supported; + } + if (!(priv->supp_rs & MOXA_SUPP_RS232)) { init_mode = MOXA_UIR_RS422; - + up->port.rs485.flags = SER_RS485_ENABLED | SER_RS485_MODE_RS422; + } mxpcie8250_set_interface(priv, idx, init_mode); if (idx == 3 && From af1ba61b62be975902c5a4babcc7952f6dfdbd29 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:16 +0800 Subject: [PATCH 88/97] serial: 8250: allow low-level drivers to override break control Some UARTs require driver-specific handling for break signaling, which cannot be expressed by the generic 8250 break implementation alone. Add an optional uart_port break_ctl callback and route serial8250_break_ctl() through it when provided. Rename the existing 8250 implementation to serial8250_do_break_ctl() and export it so low-level drivers can reuse the default 8250 behavior when appropriate. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-11-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 2 ++ drivers/tty/serial/8250/8250_port.c | 11 ++++++++++- include/linux/serial_8250.h | 1 + include/linux/serial_core.h | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index 2f569c370856..adb5d8a55a14 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -849,6 +849,8 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) uart->port.startup = up->port.startup; if (up->port.shutdown) uart->port.shutdown = up->port.shutdown; + if (up->port.break_ctl) + uart->port.break_ctl = up->port.break_ctl; if (up->port.pm) uart->port.pm = up->port.pm; if (up->port.handle_break) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 12902036e511..926d46821c70 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -1972,7 +1972,7 @@ static void serial8250_set_mctrl(struct uart_port *port, unsigned int mctrl) serial8250_do_set_mctrl(port, mctrl); } -static void serial8250_break_ctl(struct uart_port *port, int break_state) +void serial8250_do_break_ctl(struct uart_port *port, int break_state) { struct uart_8250_port *up = up_to_u8250p(port); @@ -1985,6 +1985,15 @@ static void serial8250_break_ctl(struct uart_port *port, int break_state) up->lcr &= ~UART_LCR_SBC; serial_port_out(port, UART_LCR, up->lcr); } +EXPORT_SYMBOL_GPL(serial8250_do_break_ctl); + +static void serial8250_break_ctl(struct uart_port *port, int break_state) +{ + if (port->break_ctl) + port->break_ctl(port, break_state); + else + serial8250_do_break_ctl(port, break_state); +} /* Returns true if @bits were set, false on timeout */ static bool wait_for_lsr(struct uart_8250_port *up, int bits) diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index eba36710bc47..eeb868e93e15 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h @@ -204,6 +204,7 @@ void serial8250_do_shutdown(struct uart_port *port); void serial8250_do_pm(struct uart_port *port, unsigned int state, unsigned int oldstate); void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl); +void serial8250_do_break_ctl(struct uart_port *port, int break_state); void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud, unsigned int quot); int fsl8250_handle_irq(struct uart_port *port); diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index c4cc4f66af4b..a7fd24f9e908 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -464,6 +464,7 @@ struct uart_port { void (*shutdown)(struct uart_port *port); void (*throttle)(struct uart_port *port); void (*unthrottle)(struct uart_port *port); + void (*break_ctl)(struct uart_port *port, int break_state); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, unsigned int old); From ade78d3d253c4b481b280c08ba9c1701733c7412 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:17 +0800 Subject: [PATCH 89/97] serial: 8250_mxpcie: add break support for RS485 using MUEx50 features On MUEx50, break signaling under RS485 requires a driver-specific sequence and cannot be handled correctly by the generic 8250 break implementation alone. Implement a mxpcie break_ctl callback that performs MUEx50-specific break handling when RS485 is enabled and fall back to the default 8250 break handling for other modes. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-12-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 93f0af958d11..6f44a557a6d6 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -54,6 +54,7 @@ /* Special Function Register (SFR) */ #define MOXA_PUART_SFR 0x07 +#define MOXA_PUART_SFR_FORCE_TX BIT(0) #define MOXA_PUART_SFR_950 BIT(5) /* Enhanced Function Register (EFR) */ @@ -382,6 +383,52 @@ static int mxpcie8250_handle_irq(struct uart_port *port) return 1; } +static void mxpcie8250_software_break_ctl(struct uart_port *port, int break_state) +{ + struct uart_8250_port *up = up_to_u8250p(port); + struct tty_struct *tty = port->state->port.tty; + unsigned int baud, quot; + u8 sfr, tx_byte = 0x01; + + guard(uart_port_lock_irqsave)(port); + + if (break_state == -1) { + serial_out(up, UART_LCR, up->lcr | UART_LCR_DLAB); + serial_dl_write(up, 0); + serial_out(up, UART_LCR, up->lcr); + + serial_out(up, MOXA_PUART_TX_FIFO_MEM, tx_byte); + + sfr = serial_in(up, MOXA_PUART_SFR); + serial_out(up, MOXA_PUART_SFR, sfr | MOXA_PUART_SFR_FORCE_TX); + + up->lcr |= UART_LCR_SBC; + serial_out(up, UART_LCR, up->lcr); + } else { + up->lcr &= ~UART_LCR_SBC; + serial_out(up, UART_LCR, up->lcr); + + sfr = serial_in(up, MOXA_PUART_SFR); + serial_out(up, MOXA_PUART_SFR, sfr & ~MOXA_PUART_SFR_FORCE_TX); + + serial_out(up, UART_FCR, UART_FCR_CLEAR_XMIT); + + baud = tty_get_baud_rate(tty); + quot = uart_get_divisor(port, baud); + serial8250_do_set_divisor(port, baud, quot); + serial_out(up, UART_LCR, up->lcr); + } +} + +static void mxpcie8250_break_ctl(struct uart_port *port, int break_state) +{ + if (port->rs485.flags & SER_RS485_ENABLED && + !(port->rs485.flags & SER_RS485_MODE_RS422)) + mxpcie8250_software_break_ctl(port, break_state); + else + serial8250_do_break_ctl(port, break_state); +} + static void mxpcie8250_init_board(struct pci_dev *pdev, struct mxpcie8250 *priv) { void __iomem *bar2_base = priv->bar2_base; @@ -487,6 +534,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.throttle = mxpcie8250_throttle; up.port.unthrottle = mxpcie8250_unthrottle; up.port.handle_irq = mxpcie8250_handle_irq; + up.port.break_ctl = mxpcie8250_break_ctl; for (unsigned int i = 0; i < num_ports; i++) { mxpcie8250_setup_port(pdev, priv, &up, i); From 8a6c543cf8b3630a3db714e1be99931ab5f98513 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:18 +0800 Subject: [PATCH 90/97] serial: 8250: allow UART drivers to override rx_trig_bytes handling The rx_trig_bytes sysfs attribute currently relies on 8250-internal helper functions and assumes a fixed mapping between trigger levels and FIFO behavior. Some UARTs provide hardware-specific RX trigger mechanisms that do not fit this model. Add optional uart_port callbacks for setting and getting the RX trigger level, and use them when provided, while preserving the existing 8250 helpers as the default fallback. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-13-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_core.c | 4 ++++ drivers/tty/serial/8250/8250_port.c | 14 ++++++++++++-- include/linux/serial_core.h | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c index adb5d8a55a14..b875d394796f 100644 --- a/drivers/tty/serial/8250/8250_core.c +++ b/drivers/tty/serial/8250/8250_core.c @@ -845,6 +845,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up) uart->port.get_divisor = up->port.get_divisor; if (up->port.set_divisor) uart->port.set_divisor = up->port.set_divisor; + if (up->port.get_rxtrig) + uart->port.get_rxtrig = up->port.get_rxtrig; + if (up->port.set_rxtrig) + uart->port.set_rxtrig = up->port.set_rxtrig; if (up->port.startup) uart->port.startup = up->port.startup; if (up->port.shutdown) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 926d46821c70..38fa45e74a37 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -3035,9 +3035,14 @@ static ssize_t rx_trig_bytes_show(struct device *dev, struct device_attribute *attr, char *buf) { struct tty_port *port = dev_get_drvdata(dev); + struct uart_state *state = container_of(port, struct uart_state, port); + struct uart_port *uport = state->uart_port; int rxtrig_bytes; - rxtrig_bytes = do_serial8250_get_rxtrig(port); + if (uport->get_rxtrig) + rxtrig_bytes = uport->get_rxtrig(uport); + else + rxtrig_bytes = do_serial8250_get_rxtrig(port); if (rxtrig_bytes < 0) return rxtrig_bytes; @@ -3080,6 +3085,8 @@ static ssize_t rx_trig_bytes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct tty_port *port = dev_get_drvdata(dev); + struct uart_state *state = container_of(port, struct uart_state, port); + struct uart_port *uport = state->uart_port; unsigned char bytes; int ret; @@ -3090,7 +3097,10 @@ static ssize_t rx_trig_bytes_store(struct device *dev, if (ret < 0) return ret; - ret = do_serial8250_set_rxtrig(port, bytes); + if (uport->set_rxtrig) + ret = uport->set_rxtrig(uport, bytes); + else + ret = do_serial8250_set_rxtrig(port, bytes); if (ret < 0) return ret; diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index a7fd24f9e908..904760876ca8 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -460,6 +460,8 @@ struct uart_port { unsigned int baud, unsigned int quot, unsigned int quot_frac); + int (*get_rxtrig)(struct uart_port *port); + int (*set_rxtrig)(struct uart_port *port, unsigned char bytes); int (*startup)(struct uart_port *port); void (*shutdown)(struct uart_port *port); void (*throttle)(struct uart_port *port); From 8e929ae7c96c8d2a397bee2c98186bc38581fb78 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:19 +0800 Subject: [PATCH 91/97] serial: 8250_mxpcie: introduce per-port private data structure Introduce a private per-port data structure for the mxpcie driver and replace the shared flexible array of registered lines with an array of per-port objects. This prepares the driver for storing per-port state needed by subsequent features. No functional change intended. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-14-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index 6f44a557a6d6..b37e014af553 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -101,12 +101,16 @@ #define MOXA_EVEN_RS_MASK GENMASK(3, 0) #define MOXA_ODD_RS_MASK GENMASK(7, 4) +struct mxpcie8250_port { + int line; +}; + struct mxpcie8250 { unsigned int supp_rs; unsigned int num_ports; void __iomem *bar1_base; /* UART registers (MMIO) */ void __iomem *bar2_base; /* UIR / GPIO / CPLD (IO) */ - int line[] __counted_by(num_ports); + struct mxpcie8250_port port[] __counted_by(num_ports); }; enum { @@ -501,7 +505,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id num_ports = mxpcie8250_get_nports(device); - priv = devm_kzalloc(dev, struct_size(priv, line, num_ports), GFP_KERNEL); + priv = devm_kzalloc(dev, struct_size(priv, port, num_ports), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -542,12 +546,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->line[i] = serial8250_register_8250_port(&up); - if (priv->line[i] < 0) { + priv->port[i].line = serial8250_register_8250_port(&up); + if (priv->port[i].line < 0) { dev_err(dev, "Couldn't register serial port %lx, irq %d, type %d, error %d\n", up.port.iobase, up.port.irq, - up.port.iotype, priv->line[i]); + up.port.iotype, priv->port[i].line); break; } } @@ -561,7 +565,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++) - serial8250_unregister_port(priv->line[i]); + serial8250_unregister_port(priv->port[i].line); } static const struct pci_device_id mxpcie8250_pci_ids[] = { From 4412f9d0df9dbab1f1bd4b61e2541cbb6d99f598 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:20 +0800 Subject: [PATCH 92/97] serial: 8250_mxpcie: implement rx_trig_bytes callbacks via MUEx50 RTL The MUEx50 UART exposes a programmable RX trigger level via the RTL register. Implement uart_port RX trigger set/get callbacks for the mxpcie driver and wire them up to the generic rx_trig_bytes sysfs interface. Store the configured trigger level in the per-port private data. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-15-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index b37e014af553..ce873fbd62e9 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -103,6 +103,7 @@ struct mxpcie8250_port { int line; + u8 rx_trig_level; }; struct mxpcie8250 { @@ -249,8 +250,27 @@ static void mxpcie8250_set_termios(struct uart_port *port, serial_out(up, MOXA_PUART_EFR, efr); } +static int mxpcie8250_get_rxtrig(struct uart_port *port) +{ + return serial_port_in(port, MOXA_PUART_RTL); +} + +static int mxpcie8250_set_rxtrig(struct uart_port *port, unsigned char bytes) +{ + struct mxpcie8250 *priv = dev_get_drvdata(port->dev); + + if (bytes > port->fifosize) + return -EINVAL; + + serial_port_out(port, MOXA_PUART_RTL, bytes); + priv->port[port->port_id].rx_trig_level = bytes; + + return 0; +} + static int mxpcie8250_startup(struct uart_port *port) { + struct mxpcie8250 *priv = dev_get_drvdata(port->dev); struct uart_8250_port *up = up_to_u8250p(port); int ret; @@ -275,7 +295,7 @@ static int mxpcie8250_startup(struct uart_port *port) serial_out(up, MOXA_PUART_SFR, MOXA_PUART_SFR_950); serial_out(up, MOXA_PUART_TTL, MOXA_PUART_TX_TRIG_DEFAULT); - serial_out(up, MOXA_PUART_RTL, MOXA_PUART_RX_TRIG_DEFAULT); + serial_out(up, MOXA_PUART_RTL, priv->port[port->port_id].rx_trig_level); serial_out(up, MOXA_PUART_FCL, MOXA_PUART_RX_FLOW_LOW_DEFAULT); serial_out(up, MOXA_PUART_FCH, MOXA_PUART_RX_FLOW_HIGH_DEFAULT); @@ -533,6 +553,8 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.regshift = 0; up.port.set_termios = mxpcie8250_set_termios; + up.port.get_rxtrig = mxpcie8250_get_rxtrig; + up.port.set_rxtrig = mxpcie8250_set_rxtrig; up.port.startup = mxpcie8250_startup; up.port.shutdown = mxpcie8250_shutdown; up.port.throttle = mxpcie8250_throttle; @@ -554,6 +576,7 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.iotype, priv->port[i].line); break; } + priv->port[i].rx_trig_level = MOXA_PUART_RX_TRIG_DEFAULT; } pci_set_drvdata(pdev, priv); From 1a0e4fbce5d9c1bc179a35a2fd9ed142664299e3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 31 Jul 2026 20:18:40 +0200 Subject: [PATCH 93/97] serial: core: do fallible allocations before the console can be registered serial_core_add_one_port() allocates uport->tty_groups after uart_configure_port(), which may register the console. If the allocation fails, the driver unwinds the port while its console remains registered. The earlier uport->name allocation has a related failure path that leaves state->uart_port linked to a port being freed. Failslab reproduced a NULL dereference in PL011 console output and a KASAN use-after-free in i.MX console output after failed binds. Allocate the name and tty_groups before linking the port and configuring it. Reserve space for the optional driver attribute group because config_port() may populate uport->attr_group during configuration. Fixes: 266dcff03eed ("Serial: allow port drivers to have a default attribute group") Fixes: f7048b15900f ("tty: serial_core: Add name field to uart_port struct") Reported-by: Sashiko Closes: https://lore.kernel.org/all/20260719070454.D6FA21F000E9@smtp.kernel.org/ Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260731181844.11330-2-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index edd1e7be2a5c..4fc99e4bbdbc 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -3090,7 +3090,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u struct uart_state *state; struct tty_port *port; struct device *tty_dev; - int num_groups; if (uport->line >= drv->nr) return -EINVAL; @@ -3102,6 +3101,22 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u if (state->uart_port) return -EINVAL; + uport->name = kasprintf(GFP_KERNEL, "%s%u", drv->dev_name, + drv->tty_driver->name_base + uport->line); + if (!uport->name) + return -ENOMEM; + + /* + * uart_configure_port() may set uport->attr_group and register the + * console. Allocate room for both groups and a NULL terminator first. + */ + uport->tty_groups = kzalloc_objs(*uport->tty_groups, 3); + if (!uport->tty_groups) { + kfree(uport->name); + return -ENOMEM; + } + uport->tty_groups[0] = &tty_dev_attr_group; + /* Link the port to the driver state table and vice versa */ atomic_set(&state->refcount, 1); init_waitqueue_head(&state->remove_wait); @@ -3118,10 +3133,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u state->pm_state = UART_PM_STATE_UNDEFINED; uart_port_set_cons(uport, drv->cons); uport->minor = drv->tty_driver->minor_start + uport->line; - uport->name = kasprintf(GFP_KERNEL, "%s%u", drv->dev_name, - drv->tty_driver->name_base + uport->line); - if (!uport->name) - return -ENOMEM; if (uport->cons && uport->dev) of_console_check(uport->dev->of_node, uport->cons->name, uport->line); @@ -3136,15 +3147,6 @@ static int serial_core_add_one_port(struct uart_driver *drv, struct uart_port *u port->console = uart_console(uport); - num_groups = 2; - if (uport->attr_group) - num_groups++; - - uport->tty_groups = kzalloc_objs(*uport->tty_groups, num_groups); - if (!uport->tty_groups) - return -ENOMEM; - - uport->tty_groups[0] = &tty_dev_attr_group; if (uport->attr_group) uport->tty_groups[1] = uport->attr_group; From 61a2fb25551be0375bc16ef2a70c987dfca26183 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 31 Jul 2026 20:18:41 +0200 Subject: [PATCH 94/97] serial: core: clear freed pointers on uart_register_driver() failure uart_register_driver() leaves drv->state pointing to freed memory when tty_alloc_driver() fails. If tty_register_driver() fails, drv->tty_driver also retains a pointer after its reference is dropped. Drivers that use drv->state as an "already registered" flag can then skip registration on the next probe and pass the freed state to uart_add_one_port(). This issue was found with failslab on QEMU's raspi1ap board by failing registration and binding the PL011 port again. Clear both pointers on their failure paths, as uart_unregister_driver() already does. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Fixes: 9e845abfc8a8 ("serial: fix NULL pointer dereference") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260731181844.11330-3-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 4fc99e4bbdbc..95774b0f1484 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -2811,8 +2811,10 @@ int uart_register_driver(struct uart_driver *drv) for (i = 0; i < drv->nr; i++) tty_port_destroy(&drv->state[i].port); tty_driver_kref_put(normal); + drv->tty_driver = NULL; out_kfree: kfree(drv->state); + drv->state = NULL; out: return retval; } From c3b5623fd97648f2747444aa8932ae604662df93 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 31 Jul 2026 20:18:42 +0200 Subject: [PATCH 95/97] tty: skip cdev_del() when no cdev is registered TTY device registration can fail before a cdev is allocated. Serial core keeps the port so setserial can still use it, and later removal passes the NULL cdev slot to cdev_del(), causing a NULL-pointer dereference. Only delete the cdev when the slot is not NULL. Fixes: a3a10ce3429e ("Avoid usb reset crashes by making tty_io cdevs truly dynamic") Fixes: da4c279942b0 ("serial: enable serdev support") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260731181844.11330-4-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 6b283fd03ff8..e742bf9d8631 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3305,7 +3305,7 @@ EXPORT_SYMBOL_GPL(tty_register_device_attr); void tty_unregister_device(struct tty_driver *driver, unsigned index) { device_destroy(&tty_class, MKDEV(driver->major, driver->minor_start) + index); - if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { + if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) && driver->cdevs[index]) { cdev_del(driver->cdevs[index]); driver->cdevs[index] = NULL; } From 6645856f0df3aeecd45519cb611415b4b89c2223 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 31 Jul 2026 20:18:43 +0200 Subject: [PATCH 96/97] tty: clear cdev pointer after cdev_add() failure tty_cdev_add() drops the cdev reference when cdev_add() fails, but leaves driver->cdevs[index] pointing to freed memory. tty_unregister_device() later passes that stale pointer to cdev_del(), causing a use-after-free. Clear the slot after dropping the reference. Fixes: c1a752ba2d6b ("tty: don't leak cdev in tty_cdev_add()") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260731181844.11330-5-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index e742bf9d8631..4889076b975f 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3167,8 +3167,10 @@ static int tty_cdev_add(struct tty_driver *driver, dev_t dev, driver->cdevs[index]->ops = &tty_fops; driver->cdevs[index]->owner = driver->owner; err = cdev_add(driver->cdevs[index], dev, count); - if (err) + if (err) { kobject_put(&driver->cdevs[index]->kobj); + driver->cdevs[index] = NULL; + } return err; } From 8b0b29fdcb47907ae0296b8fe829e918e05e300f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 31 Jul 2026 20:18:44 +0200 Subject: [PATCH 97/97] serial: imx: serialize imx_uart_ports[] lifetime imx_uart_probe() publishes its devm-allocated port in imx_uart_ports[] before uart_add_one_port() because console setup uses the table. The entry is not cleared when adding the port fails or after removal, leaving a dangling pointer. A sibling probe can register the shared console through that stale entry. This was reproduced under KASAN on QEMU mcimx6ul-evk by unbinding a sibling UART, unbinding the console UART and rebinding the sibling. Keep the entry valid through uart_remove_one_port(), then clear it. Protect port addition and removal together with their table updates so sibling operations cannot interleave. Reject an occupied slot rather than clobbering an active port during a duplicate-line probe. Fixes: dbff4e9ea2e8 ("IMX UART: remove statically initialized tables") Fixes: 9f322ad064f9 ("imx: serial: handle initialisation failure correctly") Reported-by: Sashiko Link: https://lore.kernel.org/all/20260719162850.043B41F000E9@smtp.kernel.org Link: https://lore.kernel.org/all/20260719222501.CB4CB1F000E9@smtp.kernel.org Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260731181844.11330-6-kmehltretter@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/imx.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 95fdb60e06a7..513dbe95f7e9 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -2080,6 +2081,9 @@ static const struct uart_ops imx_uart_pops = { static struct imx_port *imx_uart_ports[UART_NR]; +/* Held across uart_add/remove_one_port(); console callbacks must not take it. */ +static DEFINE_MUTEX(imx_uart_ports_lock); + #if IS_ENABLED(CONFIG_SERIAL_IMX_CONSOLE) static void imx_uart_console_putchar(struct uart_port *port, unsigned char ch) { @@ -2621,11 +2625,19 @@ static int imx_uart_probe(struct platform_device *pdev) goto err_clk; } - imx_uart_ports[sport->port.line] = sport; - platform_set_drvdata(pdev, sport); - ret = uart_add_one_port(&imx_uart_uart_driver, &sport->port); + scoped_guard(mutex, &imx_uart_ports_lock) { + if (imx_uart_ports[sport->port.line]) { + ret = -EBUSY; + } else { + imx_uart_ports[sport->port.line] = sport; + ret = uart_add_one_port(&imx_uart_uart_driver, + &sport->port); + if (ret) + imx_uart_ports[sport->port.line] = NULL; + } + } err_clk: clk_disable_unprepare(sport->clk_ipg); @@ -2637,7 +2649,9 @@ static void imx_uart_remove(struct platform_device *pdev) { struct imx_port *sport = platform_get_drvdata(pdev); + guard(mutex)(&imx_uart_ports_lock); uart_remove_one_port(&imx_uart_uart_driver, &sport->port); + imx_uart_ports[sport->port.line] = NULL; } static void imx_uart_restore_context(struct imx_port *sport)