Linux Serial subsystem development
 help / color / mirror / Atom feed
* [PATCH v9] tty: tty_port: add workqueue to flip TTY buffer
From: Xin Zhao @ 2026-02-05 10:19 UTC (permalink / raw)
  To: gregkh, jirislaby, m.szyprowski, tommaso.merciai.xr, geert
  Cc: hch, tj, jackzxcui1989, linux-kernel, linux-serial

On the embedded platform, certain critical data, such as IMU data, is
transmitted through UART. The tty_flip_buffer_push() interface in the TTY
layer uses system_dfl_wq to handle the flipping of the TTY buffer.
Although the unbound workqueue can create new threads on demand and wake
up the kworker thread on an idle CPU, it may be preempted by real-time
tasks or other high-prio tasks.

flush_to_ldisc() needs to wake up the relevant data handle thread. When
executing __wake_up_common_lock(), it calls spin_lock_irqsave(), which
does not disable preemption but disables migration in RT-Linux. This
prevents the kworker thread from being migrated to other cores by CPU's
balancing logic, resulting in long delays. The call trace is as follows:
    __wake_up_common_lock
    __wake_up
    ep_poll_callback
    __wake_up_common
    __wake_up_common_lock
    __wake_up
    n_tty_receive_buf_common
    n_tty_receive_buf2
    tty_ldisc_receive_buf
    tty_port_default_receive_buf
    flush_to_ldisc

In our system, the processing interval for each frame of IMU data
transmitted via UART can experience significant jitter due to this issue.
Instead of the expected 10 to 15 ms frame processing interval, we see
spikes up to 30 to 35 ms. Moreover, in just one or two hours, there can
be 2 to 3 occurrences of such high jitter, which is quite frequent. This
jitter exceeds the software's tolerable limit of 20 ms.

Introduce flip_wq in tty_port which can be set by tty_port_link_wq() or as
default linked to default workqueue allocated when tty_register_driver().
The default workqueue is allocated with flag WQ_SYSFS, so that cpumask and
nice can be set dynamically. The execution timing of tty_port_link_wq() is
not clearly restricted. The newly added function tty_port_link_driver_wq()
checks whether the flip_wq of the tty_port has already been assigned when
linking the default tty_driver's workqueue to the port. After the user has
set a custom workqueue for a certain tty_port using tty_port_link_wq(), the
system will only use this custom workqueue, even if tty_driver does not
have %TTY_DRIVER_NO_WORKQUEUE flag. When tty_port register device, flip_wq
link operation is done by tty_port_link_driver_wq(), but for in-memory
devices the link operation cannot cover all the cases. Although
tty_port_install() is dedicated for in-memory devices lik PTY to link port
allocated on demand, the logic of tty_port_install() is so simple that
people may not call it, vc_cons[0].d->port is one such case. We check the
buf.flip_wq when flip TTY buffer, if buf.flip_wq of TTY port is NULL, use
system_dfl_wq as a backup.

Introduce %TTY_DRIVER_NO_WORKQUEUE flag meaning not to create the
default single tty_driver workqueue. Two reasons why need to introduce the
%TTY_DRIVER_NO_WORKQUEUE flag:
1. If the WQ_SYSFS parameter is enabled, workqueue_sysfs_register() will
fail when trying to create a workqueue with the same name. The pty is an
example of this; if both CONFIG_LEGACY_PTYS and CONFIG_UNIX98_PTYS are
enabled, the call to tty_register_driver() in unix98_pty_init() will fail.
2. Different TTY ports may be used for different tasks, which may require
separate core binding control via workqueues. In this case, the workqueue
created by default in the TTY driver is unnecessary. Enabling this flag
prevents the creation of this redundant workqueue.

After applying this patch, we can set the related UART TTY flip buffer
workqueue by sysfs. We set the cpumask to CPU cores associated with the
IMU tasks, and set the nice to -20. Testing has shown significant
improvement in the previously described issue, with almost no stuttering
occurring anymore.

Signed-off-by: Xin Zhao <jackzxcui1989@163.com>
---

Change in v9:
- Fix 'cannot create duplicate filename' problem, when create flip_wq
  workqueue, use '"%s-%s", ... driver->name, driver->driver_name' as
  flip_wq workqueue name, as suggested by Marek Szyprowski.
  driver_name maybe NULL, do not create driver flip_wq workqueue  when
  driver_name is NULL. Drivers that do not define driver_name are
  potentially in-memory devices like vty, which generally do not require
  special workqueue settings.
- Fix 'NULL pointer panic' problem, tty_flip_buffer_push() now check
  whether buf.flip_wq is NULL, use system_dfl_wq instead if buf.flip_wq
  is NULL. Therefore, no longer need to call tty_port_link_wq() to link
  system_dfl_wq to pty ports.
- Set buf.flip_wq to NULL in tty_port_destroy() and
  tty_port_unregister_device().

Change in v8:
- Rebase code, use system_dfl_wq instead of system_unbound_wq.
- Link to v8: https://lore.kernel.org/linux-serial/20260129103129.2928955-1-jackzxcui1989@163.com/T/#m8697be62ae18c8c7bcb677cbd96599c23b3dab4d

Change in v7:
- Pty simply link to system_unbound_wq instead of allocating a custom one,
  as suggested by Jiri Slaby.
- Modify some inappropriate expressions in the code comments,
  as suggested by Jiri Slaby.
- Link to v7: https://lore.kernel.org/all/20251210125028.4174917-1-jackzxcui1989@163.com/T/#u

Change in v6:
- Modify many inappropriate expressions in the commit log and code comments,
  as suggested by Jiri Slaby.
- Add reasons why need to introduce the %TTY_DRIVER_CUSTOM_WORKQUEUE in
  commit log.
- Modify the error handling related to the allocation failure of workqueue in
  tty_register_driver(), as suggested by Jiri Slaby.
- Add description of tty_port_link_driver_wq() in the commit log,
  as suggested by Jiri Slaby.
- Link to v6: https://lore.kernel.org/all/20251210031827.3771327-1-jackzxcui1989@163.com/

Change in v5:
- Do not allocate workqueue twice when CONFIG_UNIX98_PTYS and
  CONFIG_LEGACY_PTYS are all enabled.
- Link to v5: https://lore.kernel.org/all/20251205030829.1829987-1-jackzxcui1989@163.com/

Change in v4:
- Simplify the logic for creating and releasing the workqueue,
  as suggested by Tejun Heo.
- Allocate single workqueue of one tty_driver as default, link it to
  port when tty_port register device or tty_driver.
- Introduce tty_port_link_wq() to link specific workqueue to port.
- Add driver flag %TTY_DRIVER_CUSTOM_WORKQUEUE meaning not to create the
  default single tty_driver workqueue.
- Link to v4: https://lore.kernel.org/all/202512041303.7192024b-lkp@intel.com/T/#t

Change in v3:
- Add tty flip workqueue for all tty ports, as suggested by Greg KH.
  Every tty port use an individual flip workqueue, while all pty ports
  share the same workqueue created in pty_flip_wq_init().
- Modify the commit log to describe the reason for latency spikes in
  RT-Linux.
- Link to v3: https://lore.kernel.org/all/20251027060929.394053-1-jackzxcui1989@163.com/

Change in v2:
- Do not add new module parameters
  as suggested by Greg KH
- Set WQ_SYSFS to allow properties changes from userspace
  as suggested by Tejun Heo
- Link to v2: https://lore.kernel.org/all/20251024155534.2302590-1-jackzxcui1989@163.com
---
 drivers/tty/pty.c          | 12 ++++++++----
 drivers/tty/tty_buffer.c   | 15 +++++++++++----
 drivers/tty/tty_io.c       | 25 ++++++++++++++++++++++++-
 drivers/tty/tty_port.c     | 22 ++++++++++++++++++++++
 include/linux/tty_buffer.h |  1 +
 include/linux/tty_driver.h |  7 +++++++
 include/linux/tty_port.h   | 13 +++++++++++++
 7 files changed, 86 insertions(+), 9 deletions(-)

diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index 6120d827a..6c406c741 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -532,14 +532,16 @@ static void __init legacy_pty_init(void)
 	pty_driver = tty_alloc_driver(legacy_count,
 			TTY_DRIVER_RESET_TERMIOS |
 			TTY_DRIVER_REAL_RAW |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(pty_driver))
 		panic("Couldn't allocate pty driver");
 
 	pty_slave_driver = tty_alloc_driver(legacy_count,
 			TTY_DRIVER_RESET_TERMIOS |
 			TTY_DRIVER_REAL_RAW |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(pty_slave_driver))
 		panic("Couldn't allocate pty slave driver");
 
@@ -849,7 +851,8 @@ static void __init unix98_pty_init(void)
 			TTY_DRIVER_REAL_RAW |
 			TTY_DRIVER_DYNAMIC_DEV |
 			TTY_DRIVER_DEVPTS_MEM |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(ptm_driver))
 		panic("Couldn't allocate Unix98 ptm driver");
 	pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX,
@@ -857,7 +860,8 @@ static void __init unix98_pty_init(void)
 			TTY_DRIVER_REAL_RAW |
 			TTY_DRIVER_DYNAMIC_DEV |
 			TTY_DRIVER_DEVPTS_MEM |
-			TTY_DRIVER_DYNAMIC_ALLOC);
+			TTY_DRIVER_DYNAMIC_ALLOC |
+			TTY_DRIVER_NO_WORKQUEUE);
 	if (IS_ERR(pts_driver))
 		panic("Couldn't allocate Unix98 pts driver");
 
diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c
index 1a5673acd..1a2b29135 100644
--- a/drivers/tty/tty_buffer.c
+++ b/drivers/tty/tty_buffer.c
@@ -59,6 +59,13 @@ void tty_buffer_lock_exclusive(struct tty_port *port)
 }
 EXPORT_SYMBOL_GPL(tty_buffer_lock_exclusive);
 
+static bool tty_buffer_queue_work(struct tty_bufhead *buf)
+{
+	struct workqueue_struct *flip_wq = READ_ONCE(buf->flip_wq);
+
+	return queue_work(flip_wq ?: system_dfl_wq, &buf->work);
+}
+
 /**
  * tty_buffer_unlock_exclusive	-	release exclusive access
  * @port: tty port owning the flip buffer
@@ -76,7 +83,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port)
 	mutex_unlock(&buf->lock);
 
 	if (restart)
-		queue_work(system_dfl_wq, &buf->work);
+		tty_buffer_queue_work(buf);
 }
 EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive);
 
@@ -530,7 +537,7 @@ void tty_flip_buffer_push(struct tty_port *port)
 	struct tty_bufhead *buf = &port->buf;
 
 	tty_flip_buffer_commit(buf->tail);
-	queue_work(system_dfl_wq, &buf->work);
+	tty_buffer_queue_work(buf);
 }
 EXPORT_SYMBOL(tty_flip_buffer_push);
 
@@ -560,7 +567,7 @@ int tty_insert_flip_string_and_push_buffer(struct tty_port *port,
 		tty_flip_buffer_commit(buf->tail);
 	spin_unlock_irqrestore(&port->lock, flags);
 
-	queue_work(system_dfl_wq, &buf->work);
+	tty_buffer_queue_work(buf);
 
 	return size;
 }
@@ -613,7 +620,7 @@ void tty_buffer_set_lock_subclass(struct tty_port *port)
 
 bool tty_buffer_restart_work(struct tty_port *port)
 {
-	return queue_work(system_dfl_wq, &port->buf.work);
+	return tty_buffer_queue_work(&port->buf);
 }
 
 bool tty_buffer_cancel_work(struct tty_port *port)
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
index e2d92cf70..a8674a20a 100644
--- a/drivers/tty/tty_io.c
+++ b/drivers/tty/tty_io.c
@@ -3446,10 +3446,27 @@ int tty_register_driver(struct tty_driver *driver)
 	if (error < 0)
 		goto err;
 
+	/*
+	 * Drivers that do not define driver_name are potentially in-memory devices
+	 * like vty, which generally do not require special workqueue settings.
+	 */
+	if (!(driver->flags & TTY_DRIVER_NO_WORKQUEUE) && driver->driver_name) {
+		driver->flip_wq = alloc_workqueue("%s-%s", WQ_UNBOUND | WQ_SYSFS,
+						  0, driver->name, driver->driver_name);
+		if (!driver->flip_wq) {
+			error = -ENOMEM;
+			goto err_unreg_char;
+		}
+		for (i = 0; i < driver->num; i++) {
+			if (driver->ports[i])
+				tty_port_link_driver_wq(driver->ports[i], driver);
+		}
+	}
+
 	if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
 		error = tty_cdev_add(driver, dev, 0, driver->num);
 		if (error)
-			goto err_unreg_char;
+			goto err_destroy_wq;
 	}
 
 	scoped_guard(mutex, &tty_mutex)
@@ -3475,6 +3492,10 @@ int tty_register_driver(struct tty_driver *driver)
 	scoped_guard(mutex, &tty_mutex)
 		list_del(&driver->tty_drivers);
 
+err_destroy_wq:
+	if (driver->flip_wq)
+		destroy_workqueue(driver->flip_wq);
+
 err_unreg_char:
 	unregister_chrdev_region(dev, driver->num);
 err:
@@ -3494,6 +3515,8 @@ void tty_unregister_driver(struct tty_driver *driver)
 				driver->num);
 	scoped_guard(mutex, &tty_mutex)
 		list_del(&driver->tty_drivers);
+	if (driver->flip_wq)
+		destroy_workqueue(driver->flip_wq);
 }
 EXPORT_SYMBOL(tty_unregister_driver);
 
diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c
index fe67c5cb0..54359310e 100644
--- a/drivers/tty/tty_port.c
+++ b/drivers/tty/tty_port.c
@@ -99,6 +99,23 @@ void tty_port_init(struct tty_port *port)
 }
 EXPORT_SYMBOL(tty_port_init);
 
+/**
+ * tty_port_link_wq - link tty_port and flip workqueue
+ * @port: tty_port of the device
+ * @flip_wq: workqueue to queue flip buffer work on
+ *
+ * Whenever %TTY_DRIVER_NO_WORKQUEUE is used, every tty_port can be linked to
+ * a workqueue manually by this function.
+ * tty_port will use system_dfl_wq when buf.flip_wq is NULL.
+ *
+ * Note that tty_port API will NOT destroy the workqueue.
+ */
+void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq)
+{
+	port->buf.flip_wq = flip_wq;
+}
+EXPORT_SYMBOL_GPL(tty_port_link_wq);
+
 /**
  * tty_port_link_device - link tty and tty_port
  * @port: tty_port of the device
@@ -157,6 +174,7 @@ struct device *tty_port_register_device_attr(struct tty_port *port,
 		const struct attribute_group **attr_grp)
 {
 	tty_port_link_device(port, driver, index);
+	tty_port_link_driver_wq(port, driver);
 	return tty_register_device_attr(driver, index, device, drvdata,
 			attr_grp);
 }
@@ -183,6 +201,7 @@ struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
 	struct device *dev;
 
 	tty_port_link_device(port, driver, index);
+	tty_port_link_driver_wq(port, driver);
 
 	dev = serdev_tty_port_register(port, host, parent, driver, index);
 	if (PTR_ERR(dev) != -ENODEV) {
@@ -210,6 +229,7 @@ void tty_port_unregister_device(struct tty_port *port,
 {
 	int ret;
 
+	WRITE_ONCE(port->buf.flip_wq, NULL);
 	ret = serdev_tty_port_unregister(port);
 	if (ret == 0)
 		return;
@@ -257,6 +277,7 @@ void tty_port_destroy(struct tty_port *port)
 {
 	tty_buffer_cancel_work(port);
 	tty_buffer_free_all(port);
+	WRITE_ONCE(port->buf.flip_wq, NULL);
 }
 EXPORT_SYMBOL(tty_port_destroy);
 
@@ -703,6 +724,7 @@ int tty_port_install(struct tty_port *port, struct tty_driver *driver,
 		struct tty_struct *tty)
 {
 	tty->port = port;
+	tty_port_link_driver_wq(port, driver);
 	return tty_standard_install(driver, tty);
 }
 EXPORT_SYMBOL_GPL(tty_port_install);
diff --git a/include/linux/tty_buffer.h b/include/linux/tty_buffer.h
index 31125e3be..48adcb0e8 100644
--- a/include/linux/tty_buffer.h
+++ b/include/linux/tty_buffer.h
@@ -34,6 +34,7 @@ static inline u8 *flag_buf_ptr(struct tty_buffer *b, unsigned int ofs)
 
 struct tty_bufhead {
 	struct tty_buffer *head;	/* Queue head */
+	struct workqueue_struct *flip_wq;
 	struct work_struct work;
 	struct mutex	   lock;
 	atomic_t	   priority;
diff --git a/include/linux/tty_driver.h b/include/linux/tty_driver.h
index 188ee9b76..f77af6472 100644
--- a/include/linux/tty_driver.h
+++ b/include/linux/tty_driver.h
@@ -69,6 +69,10 @@ struct serial_struct;
  *	Do not create numbered ``/dev`` nodes. For example, create
  *	``/dev/ttyprintk`` and not ``/dev/ttyprintk0``. Applicable only when a
  *	driver for a single tty device is being allocated.
+ *
+ * @TTY_DRIVER_NO_WORKQUEUE:
+ *	Do not create workqueue when tty_register_driver(). Whenever set, flip
+ *	buffer workqueue can be set by tty_port_link_wq() for every port.
  */
 enum tty_driver_flag {
 	TTY_DRIVER_INSTALLED		= BIT(0),
@@ -79,6 +83,7 @@ enum tty_driver_flag {
 	TTY_DRIVER_HARDWARE_BREAK	= BIT(5),
 	TTY_DRIVER_DYNAMIC_ALLOC	= BIT(6),
 	TTY_DRIVER_UNNUMBERED_NODE	= BIT(7),
+	TTY_DRIVER_NO_WORKQUEUE	= BIT(8),
 };
 
 enum tty_driver_type {
@@ -506,6 +511,7 @@ struct tty_operations {
  * @flags: tty driver flags (%TTY_DRIVER_)
  * @proc_entry: proc fs entry, used internally
  * @other: driver of the linked tty; only used for the PTY driver
+ * @flip_wq: workqueue to queue flip buffer work on
  * @ttys: array of active &struct tty_struct, set by tty_standard_install()
  * @ports: array of &struct tty_port; can be set during initialization by
  *	   tty_port_link_device() and similar
@@ -539,6 +545,7 @@ struct tty_driver {
 	unsigned long	flags;
 	struct proc_dir_entry *proc_entry;
 	struct tty_driver *other;
+	struct workqueue_struct *flip_wq;
 
 	/*
 	 * Pointer to the tty data structures
diff --git a/include/linux/tty_port.h b/include/linux/tty_port.h
index 660c254f1..d2a7882c0 100644
--- a/include/linux/tty_port.h
+++ b/include/linux/tty_port.h
@@ -138,6 +138,7 @@ struct tty_port {
 					   kernel */
 
 void tty_port_init(struct tty_port *port);
+void tty_port_link_wq(struct tty_port *port, struct workqueue_struct *flip_wq);
 void tty_port_link_device(struct tty_port *port, struct tty_driver *driver,
 		unsigned index);
 struct device *tty_port_register_device(struct tty_port *port,
@@ -165,6 +166,18 @@ static inline struct tty_port *tty_port_get(struct tty_port *port)
 	return NULL;
 }
 
+/*
+ * Never overwrite the workqueue set by tty_port_link_wq().
+ * No effect when %TTY_DRIVER_NO_WORKQUEUE is set, as driver->flip_wq is
+ * %NULL.
+ */
+static inline void tty_port_link_driver_wq(struct tty_port *port,
+					   struct tty_driver *driver)
+{
+	if (!port->buf.flip_wq)
+		tty_port_link_wq(port, driver->flip_wq);
+}
+
 /* If the cts flow control is enabled, return true. */
 static inline bool tty_port_cts_enabled(const struct tty_port *port)
 {
-- 
2.34.1


^ permalink raw reply related

* [PATCH 3/3] arm64: dts: add support for A9 based Amlogic BY401
From: Xianwei Zhao via B4 Relay @ 2026-02-05  6:04 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
	Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
	Greg Kroah-Hartman, Jiri Slaby
  Cc: devicetree, linux-kernel, linux-serial, linux-arm-kernel,
	linux-amlogic, Xianwei Zhao
In-Reply-To: <20260205-a9-baisc-dts-v1-0-1212b46f95a7@amlogic.com>

From: Xianwei Zhao <xianwei.zhao@amlogic.com>

Add basic support for the A9 based Amlogic BY401 board, which describes
the following components: CPU, GIC, IRQ, Timer and UART.
These are capable of booting up into the serial console.

Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
---
 arch/arm64/boot/dts/amlogic/Makefile               |   1 +
 .../boot/dts/amlogic/amlogic-a9-a311y3-by401.dts   |  41 +++++++
 arch/arm64/boot/dts/amlogic/amlogic-a9.dtsi        | 127 +++++++++++++++++++++
 3 files changed, 169 insertions(+)

diff --git a/arch/arm64/boot/dts/amlogic/Makefile b/arch/arm64/boot/dts/amlogic/Makefile
index 15f9c817e502..57bc440fa55c 100644
--- a/arch/arm64/boot/dts/amlogic/Makefile
+++ b/arch/arm64/boot/dts/amlogic/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 dtb-$(CONFIG_ARCH_MESON) += amlogic-a4-a113l2-ba400.dtb
 dtb-$(CONFIG_ARCH_MESON) += amlogic-a5-a113x2-av400.dtb
+dtb-$(CONFIG_ARCH_MESON) += amlogic-a9-a311y3-by401.dtb
 dtb-$(CONFIG_ARCH_MESON) += amlogic-c3-c302x-aw409.dtb
 dtb-$(CONFIG_ARCH_MESON) += amlogic-c3-c308l-aw419.dtb
 dtb-$(CONFIG_ARCH_MESON) += amlogic-s6-s905x5-bl209.dtb
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-a9-a311y3-by401.dts b/arch/arm64/boot/dts/amlogic/amlogic-a9-a311y3-by401.dts
new file mode 100644
index 000000000000..ad35a3292d49
--- /dev/null
+++ b/arch/arm64/boot/dts/amlogic/amlogic-a9-a311y3-by401.dts
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved.
+ */
+
+/dts-v1/;
+
+#include "amlogic-a9.dtsi"
+/ {
+	model = "Amlogic A311DY3 BY401 Development Board";
+	compatible = "amlogic,by401", "amlogic,a9";
+	interrupt-parent = <&gic>;
+	#address-cells = <2>;
+	#size-cells = <2>;
+
+	aliases {
+		serial0 = &uart_b;
+	};
+
+	memory@0 {
+		device_type = "memory";
+		reg = <0x0 0x0 0x0 0x80000000>;
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		/* 35 MiB reserved for ARM Trusted Firmware */
+		secmon_reserved: secmon@5000000 {
+			compatible = "shared-dma-pool";
+			reg = <0x0 0x05000000 0x0 0x2300000>;
+			no-map;
+		};
+	};
+};
+
+&uart_b {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/amlogic/amlogic-a9.dtsi b/arch/arm64/boot/dts/amlogic/amlogic-a9.dtsi
new file mode 100644
index 000000000000..a3744547edde
--- /dev/null
+++ b/arch/arm64/boot/dts/amlogic/amlogic-a9.dtsi
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2026 Amlogic, Inc. All rights reserved.
+ */
+
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	cpus {
+		#address-cells = <2>;
+		#size-cells = <0>;
+
+		cpu0: cpu@0 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a55";
+			reg = <0x0 0x0>;
+			enable-method = "psci";
+		};
+
+		cpu1: cpu@100 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a55";
+			reg = <0x0 0x100>;
+			enable-method = "psci";
+		};
+
+		cpu2: cpu@200 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a55";
+			reg = <0x0 0x200>;
+			enable-method = "psci";
+		};
+
+		cpu3: cpu@300 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a55";
+			reg = <0x0 0x300>;
+			enable-method = "psci";
+		};
+
+		cpu4: cpu@400 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a55";
+			reg = <0x0 0x400>;
+			enable-method = "psci";
+		};
+
+		cpu5: cpu@500 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a55";
+			reg = <0x0 0x500>;
+			enable-method = "psci";
+		};
+
+		cpu6: cpu@600 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a78";
+			reg = <0x0 0x600>;
+			enable-method = "psci";
+		};
+
+		cpu7: cpu@700 {
+			device_type = "cpu";
+			compatible = "arm,cortex-a78";
+			reg = <0x0 0x700>;
+			enable-method = "psci";
+		};
+	};
+
+	timer {
+		compatible = "arm,armv8-timer";
+		interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+			     <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
+	};
+
+	psci {
+		compatible = "arm,psci-1.0";
+		method = "smc";
+	};
+
+	xtal: xtal-clk {
+		compatible = "fixed-clock";
+		clock-frequency = <24000000>;
+		clock-output-names = "xtal";
+		#clock-cells = <0>;
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		gic: interrupt-controller@ff800000 {
+			compatible = "arm,gic-v3";
+			#interrupt-cells = <3>;
+			#address-cells = <0>;
+			interrupt-controller;
+			reg = <0x0 0xff800000 0 0x1000>,
+			      <0x0 0xff840000 0 0x8000>;
+			interrupts = <GIC_PPI 9 0xf04>;
+		};
+
+		aobus: bus@ffa00000 {
+			compatible = "simple-bus";
+			reg = <0x0 0xffa00000 0x0 0x100000>;
+			#address-cells = <2>;
+			#size-cells = <2>;
+			ranges = <0x0 0x0 0x0 0xffa00000 0x0 0x100000>;
+
+			uart_b: serial@1e000 {
+				compatible = "amlogic,a9-uart",
+					     "amlogic,meson-s4-uart";
+				reg = <0x0 0x1e000 0x0 0x18>;
+				interrupts = <GIC_SPI 66 IRQ_TYPE_EDGE_RISING>;
+				clocks = <&xtal>, <&xtal>, <&xtal>;
+				clock-names = "xtal", "pclk", "baud";
+				status = "disabled";
+			};
+		};
+	};
+};
+

-- 
2.52.0



^ permalink raw reply related

* [PATCH 1/3] dt-bindings: arm: amlogic: add A311Y3 support
From: Xianwei Zhao via B4 Relay @ 2026-02-05  6:04 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
	Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
	Greg Kroah-Hartman, Jiri Slaby
  Cc: devicetree, linux-kernel, linux-serial, linux-arm-kernel,
	linux-amlogic, Xianwei Zhao
In-Reply-To: <20260205-a9-baisc-dts-v1-0-1212b46f95a7@amlogic.com>

From: Xianwei Zhao <xianwei.zhao@amlogic.com>

Add bindings for the Amlogic BY401 board, using A311Y3 Soc from
Amlogic A9 family chip.

Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
---
 Documentation/devicetree/bindings/arm/amlogic.yaml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/Documentation/devicetree/bindings/arm/amlogic.yaml b/Documentation/devicetree/bindings/arm/amlogic.yaml
index a885278bc4e2..9f73a0054fb2 100644
--- a/Documentation/devicetree/bindings/arm/amlogic.yaml
+++ b/Documentation/devicetree/bindings/arm/amlogic.yaml
@@ -234,6 +234,12 @@ properties:
               - amlogic,av400
           - const: amlogic,a5
 
+      - description: Boards with the Amlogic A9 A311Y3 SoC
+        items:
+          - enum:
+              - amlogic,by401
+          - const: amlogic,a9
+
       - description: Boards with the Amlogic C3 C302X/C308L SoC
         items:
           - enum:

-- 
2.52.0



^ permalink raw reply related

* [PATCH 2/3] dt-bindings: serial: amlogic,meson-uart: Add compatible string for A9
From: Xianwei Zhao via B4 Relay @ 2026-02-05  6:04 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
	Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
	Greg Kroah-Hartman, Jiri Slaby
  Cc: devicetree, linux-kernel, linux-serial, linux-arm-kernel,
	linux-amlogic, Xianwei Zhao
In-Reply-To: <20260205-a9-baisc-dts-v1-0-1212b46f95a7@amlogic.com>

From: Xianwei Zhao <xianwei.zhao@amlogic.com>

Amlogic A9 SoCs uses the same UART controller as S4 SoCs.
There is no need for an extra compatible line in the driver,
but add A9 compatible line for documentation.

Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
---
 Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml b/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml
index d8ad1bb6172d..a2702319685d 100644
--- a/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/amlogic,meson-uart.yaml
@@ -56,6 +56,7 @@ properties:
         items:
           - enum:
               - amlogic,a4-uart
+              - amlogic,a9-uart
               - amlogic,s6-uart
               - amlogic,s7-uart
               - amlogic,s7d-uart

-- 
2.52.0



^ permalink raw reply related

* [PATCH 0/3] baisc devicetree support for Amlogic A9
From: Xianwei Zhao via B4 Relay @ 2026-02-05  6:04 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
	Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
	Greg Kroah-Hartman, Jiri Slaby
  Cc: devicetree, linux-kernel, linux-serial, linux-arm-kernel,
	linux-amlogic, Xianwei Zhao

Add the new A9 SoC/board device tree and related bindings.

Signed-off-by: Xianwei Zhao <xianwei.zhao@amlogic.com>
---
Xianwei Zhao (3):
      dt-bindings: arm: amlogic: add A311Y3 support
      dt-bindings: serial: amlogic,meson-uart: Add compatible string for A9
      arm64: dts: add support for A9 based Amlogic BY401

 Documentation/devicetree/bindings/arm/amlogic.yaml |   6 +
 .../bindings/serial/amlogic,meson-uart.yaml        |   1 +
 arch/arm64/boot/dts/amlogic/Makefile               |   1 +
 .../boot/dts/amlogic/amlogic-a9-a311y3-by401.dts   |  41 +++++++
 arch/arm64/boot/dts/amlogic/amlogic-a9.dtsi        | 127 +++++++++++++++++++++
 5 files changed, 176 insertions(+)
---
base-commit: e3194dfb772304a1b7ca3bcfccacefec3468b7bf
change-id: 20260205-a9-baisc-dts-cbbbe2e01f80

Best regards,
-- 
Xianwei Zhao <xianwei.zhao@amlogic.com>



^ permalink raw reply

* Re: [PATCH v1] serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN
From: Jiayuan Chen @ 2026-02-04  9:57 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: linux-serial, Jiayuan Chen, Jiri Slaby, Petr Mladek,
	Marcos Paulo de Souza, Krzysztof Kozlowski,
	Dr. David Alan Gilbert, Joseph Tilahun, Sjur Braendeland,
	David S. Miller, linux-kernel
In-Reply-To: <2026020453-corrode-lecturer-9b36@gregkh>

February 4, 2026 at 16:53, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org mailto:gregkh@linuxfoundation.org?to=%22Greg%20Kroah-Hartman%22%20%3Cgregkh%40linuxfoundation.org%3E > wrote:


> 
> On Wed, Feb 04, 2026 at 08:29:06AM +0000, Jiayuan Chen wrote:
> 
> > 
> > 2026/2/4 16:20, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org mailto:gregkh@linuxfoundation.org?to=%22Greg%20Kroah-Hartman%22%20%3Cgregkh%40linuxfoundation.org%3E > wrote:
> >  
> >  
> >  
> >  On Wed, Feb 04, 2026 at 03:43:20PM +0800, Jiayuan Chen wrote:
> >  
> >  > 
> >  > From: Jiayuan Chen <jiayuan.chen@shopee.com>
> >  > 
> >  > uart_write_room() and uart_write() behave inconsistently when
> >  > xmit_buf is NULL (which happens for PORT_UNKNOWN ports that were
> >  > never properly initialized):
> >  > 
> >  How does this happen? Why were they not initialized properly, what
> >  drivers/hardware cause this?
> >  
> >  
> >  In QEMU environment, /dev/ttyS3 is PORT_UNKNOWN type (no real UART hardware).
> >  When uart_port_startup() sees uport->type == PORT_UNKNOWN, it returns early
> >  without allocating xmit_buf:
> >  if (uport->type == PORT_UNKNOWN)
> >  return 1; // xmit_buf never allocated
> >  So xmit_buf remains NULL.
> > 
> But the flags for the port will have TTY_IO_ERROR set on it, which
> should hopefully mean that no data is attempted to be sent through this
> (or a ldisc would be bound to it.)
> 
> How does this port work at all? Why is QEMU advertising a broken port
> that can not do anything?
> 
> And is this the only place such a check would ever be needed? What
> changed recently to suddenly require this?


  This is an artificially constructed reproducer. I chose
  /dev/ttyS3 specifically because it's PORT_UNKNOWN in QEMU. In real-world
  usage, users wouldn't do this intentionally.

> > 
> > > 
> >  > - uart_write_room() returns kfifo_avail() which can be > 0
> >  > - uart_write() checks xmit_buf and returns 0 if NULL
> >  > 
> >  > This inconsistency causes an infinite loop in drivers that rely on
> >  > tty_write_room() to determine if they can write:
> >  > 
> >  > while (tty_write_room(tty) > 0) {
> >  > written = tty->ops->write(...);
> >  > // written is always 0, loop never exits
> >  > }
> >  > 
> >  > For example, caif_serial's handle_tx() enters an infinite loop when
> >  > used with PORT_UNKNOWN serial ports, causing system hangs.
> >  > 
> >  > Fix by making uart_write_room() also check xmit_buf and return 0 if
> >  > it's NULL, consistent with uart_write().
> >  > 
> >  > Reproducer: https://gist.github.com/mrpre/d9a694cc0e19828ee3bc3b37983fde13
> >  > 
> >  > Fixes: 9b27105b4a44 ("net-caif-driver: add CAIF serial driver (ldisc)")
> >  > 
> >  This really isn't a fix for that driver, but rather something else.
> >  
> >  You're right, this is awkward. The API inconsistency between uart_write_room()
> >  and uart_write() has existed since 2.6.12, but it only became visible as a
> >  deadloop when CAIF was introduced - because CAIF's handle_tx() relies on
> >  tty_write_room() to decide whether to call write().
> >  The fix location is in uart, but the trigger condition requires CAIF (or
> >  similar drivers). I can remove the Fixes tag if you prefer.
> > 
> Ok, I think this goes a bit deeper. This might be due to the kfifo
> rewrite of the serial drivers, as in older kernels we did not have a
> kfifo, so if it was not initialized the code checking path is much
> different.
> 
> As a "check" can you see if this fails for you on the latest 5.10.y
> tree? That is before the kfifo code was added to the uart layer.

This issue still exists in 5.10.248


[   56.519143] watchdog: BUG: soft lockup - CPU#2 stuck for 22s! [caif_deadloop_r:457]
[   56.520868] Modules linked in:
[   56.520903] CPU: 2 PID: 457 Comm: caif_deadloop_r Not tainted 5.10.248 #1
[   56.520914] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   56.520971] RIP: 0010:_raw_spin_unlock_irqrestore+0x15/0x20
[   56.520977] Code: e8 a0 5f 38 ff 4c 29 e8 49 39 c6 73 d8 80 0b 04 eb 8d cc cc cc 0f 1f 44 00 00 55 48 89 e5 e8 8a 4e 3b ff 66 90 48 89 f7 57 9d <0f> 1f 44 00 00 5d c3 cc cc cc cc 0f 1f 47
[   56.520986] RSP: 0018:ffffc90000f8bb60 EFLAGS: 00000282
[   56.520988] RAX: 0000000000000001 RBX: ffff888100b984e0 RCX: ffff8881024eb800
[   56.520990] RDX: 0000000000000001 RSI: 0000000000000282 RDI: 0000000000000282
[   56.520991] RBP: ffffc90000f8bb60 R08: ffff8881024eb800 R09: 0000000000000000
[   56.520992] R10: ffff88810086ed00 R11: 0000000000000000 R12: 0000000000000080
[   56.520993] R13: ffff888102423e10 R14: ffff8881024eb800 R15: ffffffff841eeb58
[   56.520996] FS:  00007f5c618c7740(0000) GS:ffff888137c00000(0000) knlGS:0000000000000000
[   56.520997] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   56.520998] CR2: 00007f1767cce200 CR3: 0000000008622005 CR4: 0000000000770ee0
[   56.521003] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[   56.521004] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[   56.521005] PKRU: 55555554
[   56.521010] Call Trace:
[   56.521087]  uart_write+0x1ec/0x240
[   56.521112]  handle_tx+0x9a/0x1a0
[   56.521115]  caif_xmit+0x61/0x70
[   56.521141]  dev_hard_start_xmit+0xa6/0x1e0
[   56.521144]  __dev_queue_xmit+0x7b3/0xaa0
[   56.521165]  ? packet_parse_headers+0x17a/0x250
[   56.521169]  dev_queue_xmit+0x10/0x20
[   56.521175]  packet_sendmsg+0x8eb/0x1740
[   56.521197]  ? __wake_up_common_lock+0x88/0xc0
[   56.521214]  __sock_sendmsg+0x70/0x80
[   56.521217]  __sys_sendto+0x142/0x190
[   56.521223]  __x64_sys_sendto+0x24/0x30
[   56.521233]  do_syscall_64+0x37/0x50
[   56.521236]  entry_SYSCALL_64_after_hwframe+0x67/0xd1
[   56.521251] RIP: 0033:0x7f5c619f60d7
[   56.521276] Code: c7 c0 ff ff ff ff eb be 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 80 3d 75 ef 0d 00 00 41 89 ca 74 10 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 69 c3 55 48 89 e5 50
[   56.521277] RSP: 002b:00007ffd7a4f64b8 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
[   56.521279] RAX: ffffffffffffffda RBX: 00007ffd7a4f67a8 RCX: 00007f5c619f60d7
[   56.521281] RDX: 0000000000000080 RSI: 00007ffd7a4f64f0 RDI: 0000000000000004
[   56.521282] RBP: 00007ffd7a4f6680 R08: 00007ffd7a4f64d0 R09: 0000000000000014
[   56.521283] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000001
[   56.521285] R13: 0000000000000000 R14: 000055c2c648ed58 R15: 00007f5c61b1a000


$ scripts/decode_stacktrace.sh vmlinux < dmesg.txt


[   56.519143] watchdog: BUG: soft lockup - CPU#2 stuck for 22s! [caif_deadloop_r:457]
[   56.520868] Modules linked in:
[   56.520903] CPU: 2 PID: 457 Comm: caif_deadloop_r Not tainted 5.10.248 #1
[   56.520914] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   56.520971] RIP: 0010:_raw_spin_unlock_irqrestore (./arch/x86/include/asm/paravirt.h:653 ./include/linux/spinlock_api_smp.h:160 kernel/locking/spinlock.c:191)
[ 56.520977] Code: e8 a0 5f 38 ff 4c 29 e8 49 39 c6 73 d8 80 0b 04 eb 8d cc cc cc 0f 1f 44 00 00 55 48 89 e5 e8 8a 4e 3b ff 66 90 48 89 f7 57 9d <0f> 1f 44 00 00 5d c3 cc cc cc cc 0f 1f 47
All code
========
   0:	e8 a0 5f 38 ff       	call   0xffffffffff385fa5
   5:	4c 29 e8             	sub    %r13,%rax
   8:	49 39 c6             	cmp    %rax,%r14
   b:	73 d8                	jae    0xffffffffffffffe5
   d:	80 0b 04             	orb    $0x4,(%rbx)
  10:	eb 8d                	jmp    0xffffffffffffff9f
  12:	cc                   	int3
  13:	cc                   	int3
  14:	cc                   	int3
  15:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  1a:	55                   	push   %rbp
  1b:	48 89 e5             	mov    %rsp,%rbp
  1e:	e8 8a 4e 3b ff       	call   0xffffffffff3b4ead
  23:	66 90                	xchg   %ax,%ax
  25:	48 89 f7             	mov    %rsi,%rdi
  28:	57                   	push   %rdi
  29:	9d                   	popf
  2a:*	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)		<-- trapping instruction
  2f:	5d                   	pop    %rbp
  30:	c3                   	ret
  31:	cc                   	int3
  32:	cc                   	int3
  33:	cc                   	int3
  34:	cc                   	int3
  35:	0f                   	.byte 0xf
  36:	1f                   	(bad)
  37:	47                   	rex.RXB

Code starting with the faulting instruction
===========================================
   0:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
   5:	5d                   	pop    %rbp
   6:	c3                   	ret
   7:	cc                   	int3
   8:	cc                   	int3
   9:	cc                   	int3
   a:	cc                   	int3
   b:	0f                   	.byte 0xf
   c:	1f                   	(bad)
   d:	47                   	rex.RXB
[   56.520986] RSP: 0018:ffffc90000f8bb60 EFLAGS: 00000282
[   56.520988] RAX: 0000000000000001 RBX: ffff888100b984e0 RCX: ffff8881024eb800
[   56.520990] RDX: 0000000000000001 RSI: 0000000000000282 RDI: 0000000000000282
[   56.520991] RBP: ffffc90000f8bb60 R08: ffff8881024eb800 R09: 0000000000000000
[   56.520992] R10: ffff88810086ed00 R11: 0000000000000000 R12: 0000000000000080
[   56.520993] R13: ffff888102423e10 R14: ffff8881024eb800 R15: ffffffff841eeb58
[   56.520996] FS:  00007f5c618c7740(0000) GS:ffff888137c00000(0000) knlGS:0000000000000000
[   56.520997] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   56.520998] CR2: 00007f1767cce200 CR3: 0000000008622005 CR4: 0000000000770ee0
[   56.521003] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[   56.521004] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[   56.521005] PKRU: 55555554
[   56.521010] Call Trace:
[   56.521087] uart_write (drivers/tty/serial/serial_core.c:72 drivers/tty/serial/serial_core.c:598)
[   56.521112] handle_tx (drivers/net/caif/caif_serial.c:237)
[   56.521115] caif_xmit (drivers/net/caif/caif_serial.c:284)
[   56.521141] dev_hard_start_xmit (./include/linux/netdevice.h:4833 ./include/linux/netdevice.h:4847 net/core/dev.c:3601 net/core/dev.c:3617)
[   56.521144] __dev_queue_xmit (./include/linux/netdevice.h:3322 (discriminator 25) net/core/dev.c:4204 (discriminator 25))
[   56.521165] ? packet_parse_headers (./include/linux/skbuff.h:2616 (discriminator 1) net/packet/af_packet.c:1954 (discriminator 1))
[   56.521169] dev_queue_xmit (net/core/dev.c:4237)
[   56.521175] packet_sendmsg (net/packet/af_packet.c:3086 (discriminator 1) net/packet/af_packet.c:3118 (discriminator 1))
[   56.521197] ? __wake_up_common_lock (kernel/sched/wait.c:126 (discriminator 1))
[   56.521214] __sock_sendmsg (net/socket.c:651 (discriminator 1) net/socket.c:663 (discriminator 1))
[   56.521217] __sys_sendto (./include/linux/file.h:33 net/socket.c:2008)
[   56.521223] __x64_sys_sendto (net/socket.c:2013)
[   56.521233] do_syscall_64 (arch/x86/entry/common.c:46 (discriminator 1))
[   56.521236] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:117)
[   56.521251] RIP: 0033:0x7f5c619f60d7
[ 56.521276] Code: c7 c0 ff ff ff ff eb be 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 80 3d 75 ef 0d 00 00 41 89 ca 74 10 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 69 c3 55 48 89 e5 50
All code
========
   0:	c7 c0 ff ff ff ff    	mov    $0xffffffff,%eax
   6:	eb be                	jmp    0xffffffffffffffc6
   8:	66 2e 0f 1f 84 00 00 	cs nopw 0x0(%rax,%rax,1)
   f:	00 00 00
  12:	90                   	nop
  13:	f3 0f 1e fa          	endbr64
  17:	80 3d 75 ef 0d 00 00 	cmpb   $0x0,0xdef75(%rip)        # 0xdef93
  1e:	41 89 ca             	mov    %ecx,%r10d
  21:	74 10                	je     0x33
  23:	b8 2c 00 00 00       	mov    $0x2c,%eax
  28:	0f 05                	syscall
  2a:*	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax		<-- trapping instruction
  30:	77 69                	ja     0x9b
  32:	c3                   	ret
  33:	55                   	push   %rbp
  34:	48 89 e5             	mov    %rsp,%rbp
  37:	50                   	push   %rax

Code starting with the faulting instruction
===========================================
   0:	48 3d 00 f0 ff ff    	cmp    $0xfffffffffffff000,%rax
   6:	77 69                	ja     0x71
   8:	c3                   	ret
   9:	55                   	push   %rbp
   a:	48 89 e5             	mov    %rsp,%rbp
   d:	50                   	push   %rax
[   56.521277] RSP: 002b:00007ffd7a4f64b8 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
[   56.521279] RAX: ffffffffffffffda RBX: 00007ffd7a4f67a8 RCX: 00007f5c619f60d7
[   56.521281] RDX: 0000000000000080 RSI: 00007ffd7a4f64f0 RDI: 0000000000000004
[   56.521282] RBP: 00007ffd7a4f6680 R08: 00007ffd7a4f64d0 R09: 0000000000000014
[   56.521283] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000001
[   56.521285] R13: 0000000000000000 R14: 000055c2c648ed58 R15: 00007f5c61b1a000

> > 
> > > ---
> >  > drivers/tty/serial/serial_core.c | 5 ++++-
> >  > 1 file changed, 4 insertions(+), 1 deletion(-)
> >  > 
> >  > diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> >  > index 2805cad10511..0b2edf185cc7 100644
> >  > --- a/drivers/tty/serial/serial_core.c
> >  > +++ b/drivers/tty/serial/serial_core.c
> >  > @@ -643,7 +643,10 @@ static unsigned int uart_write_room(struct tty_struct *tty)
> >  > unsigned int ret;
> >  > 
> >  > port = uart_port_ref_lock(state, &flags);
> >  > - ret = kfifo_avail(&state->port.xmit_fifo);
> >  > + if (!state->port.xmit_buf)
> >  > 
> >  This feels odd. What ports have no transmit buffers? And why would
> >  this be the only check that is needed for such broken devices?
> >  
> >  Maybe let's fix the root cause here, the driver that does not have a
> >  transmit buffer at all?
> >  
> >  
> >  Do you suggest we should prevent setting line discipline (like N_CAIF)
> >  on PORT_UNKNOWN ports? Or should CAIF check the port type before using it?
> >  Note that CAIF is currently in orphan status (no active maintainer), so
> >  I'm not sure about the process for modifying it. The serial core fix
> >  might be more straightforward.
> > 
> I think you found a real bug here, that is independent of the caif code,
> and might just be due to the kfifo stuff. See above for my questions
> here, and if so, your patch is correct, it's just that the Fixes: tag is
> a bit off.
> 
> thanks,
> 
> greg k-h
>

^ permalink raw reply

* Re: [PATCH v1] serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN
From: Greg Kroah-Hartman @ 2026-02-04  8:53 UTC (permalink / raw)
  To: Jiayuan Chen
  Cc: linux-serial, Jiayuan Chen, Jiri Slaby, Petr Mladek,
	Marcos Paulo de Souza, Krzysztof Kozlowski,
	Dr. David Alan Gilbert, Joseph Tilahun, Sjur Braendeland,
	David S. Miller, linux-kernel
In-Reply-To: <fa7e9e86df95ffcc301c60367654e084cbff5d83@linux.dev>

On Wed, Feb 04, 2026 at 08:29:06AM +0000, Jiayuan Chen wrote:
> 2026/2/4 16:20, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org mailto:gregkh@linuxfoundation.org?to=%22Greg%20Kroah-Hartman%22%20%3Cgregkh%40linuxfoundation.org%3E > wrote:
> 
> 
> > 
> > On Wed, Feb 04, 2026 at 03:43:20PM +0800, Jiayuan Chen wrote:
> > 
> > > 
> > > From: Jiayuan Chen <jiayuan.chen@shopee.com>
> > >  
> > >  uart_write_room() and uart_write() behave inconsistently when
> > >  xmit_buf is NULL (which happens for PORT_UNKNOWN ports that were
> > >  never properly initialized):
> > > 
> > How does this happen? Why were they not initialized properly, what
> > drivers/hardware cause this?
> 
> 
>   In QEMU environment, /dev/ttyS3 is PORT_UNKNOWN type (no real UART hardware).
>   When uart_port_startup() sees uport->type == PORT_UNKNOWN, it returns early
>   without allocating xmit_buf:
>       if (uport->type == PORT_UNKNOWN)
>           return 1;   // xmit_buf never allocated
>   So xmit_buf remains NULL.

But the flags for the port will have TTY_IO_ERROR set on it, which
should hopefully mean that no data is attempted to be sent through this
(or a ldisc would be bound to it.)

How does this port work at all?  Why is QEMU advertising a broken port
that can not do anything?

And is this the only place such a check would ever be needed?  What
changed recently to suddenly require this?

> > > 
> > > - uart_write_room() returns kfifo_avail() which can be > 0
> > >  - uart_write() checks xmit_buf and returns 0 if NULL
> > >  
> > >  This inconsistency causes an infinite loop in drivers that rely on
> > >  tty_write_room() to determine if they can write:
> > >  
> > >  while (tty_write_room(tty) > 0) {
> > >  written = tty->ops->write(...);
> > >  // written is always 0, loop never exits
> > >  }
> > >  
> > >  For example, caif_serial's handle_tx() enters an infinite loop when
> > >  used with PORT_UNKNOWN serial ports, causing system hangs.
> > >  
> > >  Fix by making uart_write_room() also check xmit_buf and return 0 if
> > >  it's NULL, consistent with uart_write().
> > >  
> > >  Reproducer: https://gist.github.com/mrpre/d9a694cc0e19828ee3bc3b37983fde13
> > >  
> > >  Fixes: 9b27105b4a44 ("net-caif-driver: add CAIF serial driver (ldisc)")
> > > 
> > This really isn't a fix for that driver, but rather something else.
> 
>   You're right, this is awkward. The API inconsistency between uart_write_room()
>   and uart_write() has existed since 2.6.12, but it only became visible as a
>   deadloop when CAIF was introduced - because CAIF's handle_tx() relies on
>   tty_write_room() to decide whether to call write().
>   The fix location is in uart, but the trigger condition requires CAIF (or
>   similar drivers). I can remove the Fixes tag if you prefer.

Ok, I think this goes a bit deeper.  This might be due to the kfifo
rewrite of the serial drivers, as in older kernels we did not have a
kfifo, so if it was not initialized the code checking path is much
different.

As a "check" can you see if this fails for you on the latest 5.10.y
tree?  That is before the kfifo code was added to the uart layer.

> > > ---
> > >  drivers/tty/serial/serial_core.c | 5 ++++-
> > >  1 file changed, 4 insertions(+), 1 deletion(-)
> > >  
> > >  diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> > >  index 2805cad10511..0b2edf185cc7 100644
> > >  --- a/drivers/tty/serial/serial_core.c
> > >  +++ b/drivers/tty/serial/serial_core.c
> > >  @@ -643,7 +643,10 @@ static unsigned int uart_write_room(struct tty_struct *tty)
> > >  unsigned int ret;
> > >  
> > >  port = uart_port_ref_lock(state, &flags);
> > >  - ret = kfifo_avail(&state->port.xmit_fifo);
> > >  + if (!state->port.xmit_buf)
> > > 
> > This feels odd. What ports have no transmit buffers? And why would
> > this be the only check that is needed for such broken devices?
> > 
> > Maybe let's fix the root cause here, the driver that does not have a
> > transmit buffer at all?
> 
> 
>   Do you suggest we should prevent setting line discipline (like N_CAIF)
>   on PORT_UNKNOWN ports? Or should CAIF check the port type before using it?
>   Note that CAIF is currently in orphan status (no active maintainer), so
>   I'm not sure about the process for modifying it. The serial core fix
>   might be more straightforward.

I think you found a real bug here, that is independent of the caif code,
and might just be due to the kfifo stuff.  See above for my questions
here, and if so, your patch is correct, it's just that the Fixes: tag is
a bit off.

thanks,

greg k-h

^ permalink raw reply

* Re: [PATCH v1] serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN
From: Jiayuan Chen @ 2026-02-04  8:29 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: linux-serial, Jiayuan Chen, Jiri Slaby, Petr Mladek,
	Marcos Paulo de Souza, Krzysztof Kozlowski,
	Dr. David Alan Gilbert, Joseph Tilahun, Sjur Braendeland,
	David S. Miller, linux-kernel
In-Reply-To: <2026020459-lisp-display-0506@gregkh>

2026/2/4 16:20, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org mailto:gregkh@linuxfoundation.org?to=%22Greg%20Kroah-Hartman%22%20%3Cgregkh%40linuxfoundation.org%3E > wrote:


> 
> On Wed, Feb 04, 2026 at 03:43:20PM +0800, Jiayuan Chen wrote:
> 
> > 
> > From: Jiayuan Chen <jiayuan.chen@shopee.com>
> >  
> >  uart_write_room() and uart_write() behave inconsistently when
> >  xmit_buf is NULL (which happens for PORT_UNKNOWN ports that were
> >  never properly initialized):
> > 
> How does this happen? Why were they not initialized properly, what
> drivers/hardware cause this?


  In QEMU environment, /dev/ttyS3 is PORT_UNKNOWN type (no real UART hardware).
  When uart_port_startup() sees uport->type == PORT_UNKNOWN, it returns early
  without allocating xmit_buf:
      if (uport->type == PORT_UNKNOWN)
          return 1;   // xmit_buf never allocated
  So xmit_buf remains NULL.

> > 
> > - uart_write_room() returns kfifo_avail() which can be > 0
> >  - uart_write() checks xmit_buf and returns 0 if NULL
> >  
> >  This inconsistency causes an infinite loop in drivers that rely on
> >  tty_write_room() to determine if they can write:
> >  
> >  while (tty_write_room(tty) > 0) {
> >  written = tty->ops->write(...);
> >  // written is always 0, loop never exits
> >  }
> >  
> >  For example, caif_serial's handle_tx() enters an infinite loop when
> >  used with PORT_UNKNOWN serial ports, causing system hangs.
> >  
> >  Fix by making uart_write_room() also check xmit_buf and return 0 if
> >  it's NULL, consistent with uart_write().
> >  
> >  Reproducer: https://gist.github.com/mrpre/d9a694cc0e19828ee3bc3b37983fde13
> >  
> >  Fixes: 9b27105b4a44 ("net-caif-driver: add CAIF serial driver (ldisc)")
> > 
> This really isn't a fix for that driver, but rather something else.

  You're right, this is awkward. The API inconsistency between uart_write_room()
  and uart_write() has existed since 2.6.12, but it only became visible as a
  deadloop when CAIF was introduced - because CAIF's handle_tx() relies on
  tty_write_room() to decide whether to call write().
  The fix location is in uart, but the trigger condition requires CAIF (or
  similar drivers). I can remove the Fixes tag if you prefer.

> > 
> > Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
> >  Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> > 
> This doesn't make sense, signing off twice for the same person?
> 
> As you did this from your shopee.com account, that should be sufficient.

Sorry, I'll keep only one Signed-off-by.

> > 
> > ---
> >  drivers/tty/serial/serial_core.c | 5 ++++-
> >  1 file changed, 4 insertions(+), 1 deletion(-)
> >  
> >  diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> >  index 2805cad10511..0b2edf185cc7 100644
> >  --- a/drivers/tty/serial/serial_core.c
> >  +++ b/drivers/tty/serial/serial_core.c
> >  @@ -643,7 +643,10 @@ static unsigned int uart_write_room(struct tty_struct *tty)
> >  unsigned int ret;
> >  
> >  port = uart_port_ref_lock(state, &flags);
> >  - ret = kfifo_avail(&state->port.xmit_fifo);
> >  + if (!state->port.xmit_buf)
> > 
> This feels odd. What ports have no transmit buffers? And why would
> this be the only check that is needed for such broken devices?
> 
> Maybe let's fix the root cause here, the driver that does not have a
> transmit buffer at all?


  Do you suggest we should prevent setting line discipline (like N_CAIF)
  on PORT_UNKNOWN ports? Or should CAIF check the port type before using it?
  Note that CAIF is currently in orphan status (no active maintainer), so
  I'm not sure about the process for modifying it. The serial core fix
  might be more straightforward.

  I'm happy to take either direction.
  Thanks,
  Jiayuan

> thanks,
> 
> greg k-h
>

^ permalink raw reply

* Re: [PATCH v1] serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN
From: Greg Kroah-Hartman @ 2026-02-04  8:20 UTC (permalink / raw)
  To: Jiayuan Chen
  Cc: linux-serial, Jiayuan Chen, Jiri Slaby, Petr Mladek,
	Marcos Paulo de Souza, Krzysztof Kozlowski,
	Dr. David Alan Gilbert, Joseph Tilahun, Sjur Braendeland,
	David S. Miller, linux-kernel
In-Reply-To: <20260204074327.226165-1-jiayuan.chen@linux.dev>

On Wed, Feb 04, 2026 at 03:43:20PM +0800, Jiayuan Chen wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
> 
> uart_write_room() and uart_write() behave inconsistently when
> xmit_buf is NULL (which happens for PORT_UNKNOWN ports that were
> never properly initialized):

How does this happen?  Why were they not initialized properly, what
drivers/hardware cause this?

> - uart_write_room() returns kfifo_avail() which can be > 0
> - uart_write() checks xmit_buf and returns 0 if NULL
> 
> This inconsistency causes an infinite loop in drivers that rely on
> tty_write_room() to determine if they can write:
> 
>   while (tty_write_room(tty) > 0) {
>       written = tty->ops->write(...);
>       // written is always 0, loop never exits
>   }
> 
> For example, caif_serial's handle_tx() enters an infinite loop when
> used with PORT_UNKNOWN serial ports, causing system hangs.
> 
> Fix by making uart_write_room() also check xmit_buf and return 0 if
> it's NULL, consistent with uart_write().
> 
> Reproducer: https://gist.github.com/mrpre/d9a694cc0e19828ee3bc3b37983fde13
> 
> Fixes: 9b27105b4a44 ("net-caif-driver: add CAIF serial driver (ldisc)")

This really isn't a fix for that driver, but rather something else.

> Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>

This doesn't make sense, signing off twice for the same person?

As you did this from your shopee.com account, that should be sufficient.

> ---
>  drivers/tty/serial/serial_core.c | 5 ++++-
>  1 file changed, 4 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
> index 2805cad10511..0b2edf185cc7 100644
> --- a/drivers/tty/serial/serial_core.c
> +++ b/drivers/tty/serial/serial_core.c
> @@ -643,7 +643,10 @@ static unsigned int uart_write_room(struct tty_struct *tty)
>  	unsigned int ret;
>  
>  	port = uart_port_ref_lock(state, &flags);
> -	ret = kfifo_avail(&state->port.xmit_fifo);
> +	if (!state->port.xmit_buf)

This feels odd.  What ports have no transmit buffers?  And why would
this be the only check that is needed for such broken devices?

Maybe let's fix the root cause here, the driver that does not have a
transmit buffer at all?

thanks,

greg k-h

^ permalink raw reply

* [PATCH v1] serial: core: fix infinite loop in handle_tx() for PORT_UNKNOWN
From: Jiayuan Chen @ 2026-02-04  7:43 UTC (permalink / raw)
  To: linux-serial
  Cc: Jiayuan Chen, Jiayuan Chen, Greg Kroah-Hartman, Jiri Slaby,
	Petr Mladek, Marcos Paulo de Souza, Krzysztof Kozlowski,
	Dr. David Alan Gilbert, Joseph Tilahun, Sjur Braendeland,
	David S. Miller, linux-kernel

From: Jiayuan Chen <jiayuan.chen@shopee.com>

uart_write_room() and uart_write() behave inconsistently when
xmit_buf is NULL (which happens for PORT_UNKNOWN ports that were
never properly initialized):

- uart_write_room() returns kfifo_avail() which can be > 0
- uart_write() checks xmit_buf and returns 0 if NULL

This inconsistency causes an infinite loop in drivers that rely on
tty_write_room() to determine if they can write:

  while (tty_write_room(tty) > 0) {
      written = tty->ops->write(...);
      // written is always 0, loop never exits
  }

For example, caif_serial's handle_tx() enters an infinite loop when
used with PORT_UNKNOWN serial ports, causing system hangs.

Fix by making uart_write_room() also check xmit_buf and return 0 if
it's NULL, consistent with uart_write().

Reproducer: https://gist.github.com/mrpre/d9a694cc0e19828ee3bc3b37983fde13

Fixes: 9b27105b4a44 ("net-caif-driver: add CAIF serial driver (ldisc)")
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
 drivers/tty/serial/serial_core.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 2805cad10511..0b2edf185cc7 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -643,7 +643,10 @@ static unsigned int uart_write_room(struct tty_struct *tty)
 	unsigned int ret;
 
 	port = uart_port_ref_lock(state, &flags);
-	ret = kfifo_avail(&state->port.xmit_fifo);
+	if (!state->port.xmit_buf)
+		ret = 0;
+	else
+		ret = kfifo_avail(&state->port.xmit_fifo);
 	uart_port_unlock_deref(port, flags);
 	return ret;
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH v4 7/7] serial: 8250_dw: Ensure BUSY is deasserted
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, Ilpo Järvinen, Matt Porter,
	Tim Kryger, Heikki Krogerus, Markus Mayer, Jamie Iles,
	linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
Existance of BUSY depends on uart_16550_compatible, if UART HW is
configured with it those registers can always be written.

There currently is dw8250_force_idle() which attempts to achieve
non-BUSY state by disabling FIFO, however, the solution is unreliable
when Rx keeps getting more and more characters.

Create a sequence of operations that ensures UART cannot keep BUSY
asserted indefinitely. The new sequence relies on enabling loopback mode
temporarily to prevent incoming Rx characters keeping UART BUSY.

Ensure no Tx in ongoing while the UART is switches into the loopback
mode (requires exporting serial8250_fifo_wait_for_lsr_thre() and adding
DMA Tx pause/resume functions).

According to tests performed by Adriana Nicolae <adriana@arista.com>,
simply disabling FIFO or clearing FIFOs only once does not always
ensure BUSY is deasserted but up to two tries may be needed. This could
be related to ongoing Rx of a character (a guess, not known for sure).
Therefore, retry FIFO clearing a few times (retry limit 4 is arbitrary
number but using, e.g., p->fifosize seems overly large). Tests
performed by others did not exhibit similar challenge but it does not
seem harmful to leave the FIFO clearing loop in place for all DW UARTs
with BUSY functionality.

Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
writes. In case of plain LCR writes, opportunistically try to update
LCR first and only invoke dw8250_idle_enter() if the write did not
succeed (it has been observed that in practice most LCR writes do
succeed without complications).

This issue was first reported by qianfan Zhao who put lots of debugging
effort into understanding the solution space.

Fixes: c49436b657d0 ("serial: 8250_dw: Improve unwritable LCR workaround")
Fixes: 7d4008ebb1c9 ("tty: add a DesignWare 8250 driver")
Cc: <stable@vger.kernel.org>
Reported-by: qianfan Zhao <qianfanguijin@163.com>
Link: https://lore.kernel.org/linux-serial/289bb78a-7509-1c5c-2923-a04ed3b6487d@163.com/
Reported-by: Adriana Nicolae <adriana@arista.com>
Link: https://lore.kernel.org/linux-serial/20250819182322.3451959-1-adriana@arista.com/
Reported-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250.h      |  25 +++++
 drivers/tty/serial/8250/8250_dw.c   | 163 +++++++++++++++++++++-------
 drivers/tty/serial/8250/8250_port.c |  28 ++---
 3 files changed, 161 insertions(+), 55 deletions(-)

diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 8caecfc85d93..77fe0588fd6b 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -175,7 +175,9 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up,
 	return value;
 }
 
+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_rpm_get(struct uart_8250_port *p);
 void serial8250_rpm_put(struct uart_8250_port *p);
@@ -400,6 +402,26 @@ static inline bool serial8250_tx_dma_running(struct uart_8250_port *p)
 
 	return dma && dma->tx_running;
 }
+
+static inline void serial8250_tx_dma_pause(struct uart_8250_port *p)
+{
+	struct uart_8250_dma *dma = p->dma;
+
+	if (!dma->tx_running)
+		return;
+
+	dmaengine_pause(dma->txchan);
+}
+
+static inline void serial8250_tx_dma_resume(struct uart_8250_port *p)
+{
+	struct uart_8250_dma *dma = p->dma;
+
+	if (!dma->tx_running)
+		return;
+
+	dmaengine_resume(dma->txchan);
+}
 #else
 static inline int serial8250_tx_dma(struct uart_8250_port *p)
 {
@@ -421,6 +443,9 @@ static inline bool serial8250_tx_dma_running(struct uart_8250_port *p)
 {
 	return false;
 }
+
+static inline void serial8250_tx_dma_pause(struct uart_8250_port *p) { }
+static inline void serial8250_tx_dma_resume(struct uart_8250_port *p) { }
 #endif
 
 static inline int ns16550a_goto_highspeed(struct uart_8250_port *up)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index edae359b1c3f..315f4315cf69 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -16,6 +16,7 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/io.h>
+#include <linux/lockdep.h>
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
 #include <linux/notifier.h>
@@ -47,6 +48,8 @@
 
 #define DW_UART_MCR_SIRE		BIT(6)
 
+#define DW_UART_USR_BUSY		BIT(0)
+
 /* Renesas specific register fields */
 #define RZN1_UART_xDMACR_DMA_EN		BIT(0)
 #define RZN1_UART_xDMACR_1_WORD_BURST	(0 << 1)
@@ -89,6 +92,7 @@ struct dw8250_data {
 
 	unsigned int		skip_autocfg:1;
 	unsigned int		uart_16550_compatible:1;
+	unsigned int		in_idle:1;
 
 	u8			no_int_count;
 };
@@ -121,78 +125,151 @@ static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u3
 	return value;
 }
 
+static void dw8250_idle_exit(struct uart_port *p)
+{
+	struct dw8250_data *d = to_dw8250_data(p->private_data);
+	struct uart_8250_port *up = up_to_u8250p(p);
+
+	if (d->uart_16550_compatible)
+		return;
+
+	if (up->capabilities & UART_CAP_FIFO)
+		serial_port_out(p, UART_FCR, up->fcr);
+	serial_port_out(p, UART_MCR, up->mcr);
+	serial_port_out(p, UART_IER, up->ier);
+
+	/* DMA Rx is restarted by IRQ handler as needed. */
+	if (up->dma)
+		serial8250_tx_dma_resume(up);
+
+	d->in_idle = 0;
+}
+
 /*
- * This function is being called as part of the uart_port::serial_out()
- * routine. Hence, it must not call serial_port_out() or serial_out()
- * against the modified registers here, i.e. LCR.
+ * Ensure BUSY is not asserted. If DW UART is configured with
+ * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
+ * BUSY is asserted.
+ *
+ * Context: port's lock must be held
  */
-static void dw8250_force_idle(struct uart_port *p)
+static int dw8250_idle_enter(struct uart_port *p)
 {
+	struct dw8250_data *d = to_dw8250_data(p->private_data);
+	unsigned int usr_reg = d->pdata ? d->pdata->usr_reg : DW_UART_USR;
 	struct uart_8250_port *up = up_to_u8250p(p);
-	unsigned int lsr;
+	int retries;
+	u32 lsr;
 
-	/*
-	 * The following call currently performs serial_out()
-	 * against the FCR register. Because it differs to LCR
-	 * there will be no infinite loop, but if it ever gets
-	 * modified, we might need a new custom version of it
-	 * that avoids infinite recursion.
-	 */
-	serial8250_clear_and_reinit_fifos(up);
+	lockdep_assert_held_once(&p->lock);
+
+	if (d->uart_16550_compatible)
+		return 0;
+
+	d->in_idle = 1;
+
+	/* Prevent triggering interrupt from RBR filling */
+	serial_port_out(p, UART_IER, 0);
+
+	if (up->dma) {
+		serial8250_rx_dma_flush(up);
+		if (serial8250_tx_dma_running(up))
+			serial8250_tx_dma_pause(up);
+	}
 
 	/*
-	 * With PSLVERR_RESP_EN parameter set to 1, the device generates an
-	 * error response when an attempt to read an empty RBR with FIFO
-	 * enabled.
+	 * Wait until Tx becomes empty + one extra frame time to ensure all bits
+	 * have been sent on the wire.
+	 *
+	 * FIXME: frame_time delay is too long with very low baudrates.
 	 */
-	if (up->fcr & UART_FCR_ENABLE_FIFO) {
-		lsr = serial_port_in(p, UART_LSR);
-		if (!(lsr & UART_LSR_DR))
-			return;
+	serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+	ndelay(p->frame_time);
+
+	serial_port_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
+
+	retries = 4;	/* Arbitrary limit, 2 was always enough in tests */
+	do {
+		serial8250_clear_fifos(up);
+		if (!(serial_port_in(p, usr_reg) & DW_UART_USR_BUSY))
+			break;
+		/* FIXME: frame_time delay is too long with very low baudrates. */
+		ndelay(p->frame_time);
+	} while (--retries);
+
+	lsr = serial_lsr_in(up);
+	if (lsr & UART_LSR_DR) {
+		serial_port_in(p, UART_RX);
+		up->lsr_saved_flags = 0;
 	}
 
-	serial_port_in(p, UART_RX);
+	/* Now guaranteed to have BUSY deasserted? Just sanity check */
+	if (serial_port_in(p, usr_reg) & DW_UART_USR_BUSY) {
+		dw8250_idle_exit(p);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
+			       unsigned int quot, unsigned int quot_frac)
+{
+	struct uart_8250_port *up = up_to_u8250p(p);
+	int ret;
+
+	ret = dw8250_idle_enter(p);
+	if (ret < 0)
+		return;
+
+	serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+	if (!(serial_port_in(p, UART_LCR) & UART_LCR_DLAB))
+		goto idle_failed;
+
+	serial_dl_write(up, quot);
+	serial_port_out(p, UART_LCR, up->lcr);
+
+idle_failed:
+	dw8250_idle_exit(p);
 }
 
 /*
  * This function is being called as part of the uart_port::serial_out()
- * routine. Hence, it must not call serial_port_out() or serial_out()
- * against the modified registers here, i.e. LCR.
+ * routine. Hence, special care must be taken when serial_port_out() or
+ * serial_out() against the modified registers here, i.e. LCR (d->in_idle is
+ * used to break recursion loop).
  */
 static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value)
 {
 	struct dw8250_data *d = to_dw8250_data(p->private_data);
-	void __iomem *addr = p->membase + (offset << p->regshift);
-	int tries = 1000;
+	u32 lcr;
+	int ret;
 
 	if (offset != UART_LCR || d->uart_16550_compatible)
 		return;
 
+	lcr = serial_port_in(p, UART_LCR);
+
 	/* Make sure LCR write wasn't ignored */
-	while (tries--) {
-		u32 lcr = serial_port_in(p, offset);
+	if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
+		return;
 
-		if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
-			return;
+	if (d->in_idle)
+		goto write_err;
 
-		dw8250_force_idle(p);
+	ret = dw8250_idle_enter(p);
+	if (ret < 0)
+		goto write_err;
 
-#ifdef CONFIG_64BIT
-		if (p->type == PORT_OCTEON)
-			__raw_writeq(value & 0xff, addr);
-		else
-#endif
-		if (p->iotype == UPIO_MEM32)
-			writel(value, addr);
-		else if (p->iotype == UPIO_MEM32BE)
-			iowrite32be(value, addr);
-		else
-			writeb(value, addr);
-	}
+	serial_port_out(p, UART_LCR, value);
+	dw8250_idle_exit(p);
+	return;
+
+write_err:
 	/*
 	 * FIXME: this deadlocks if port->lock is already held
 	 * dev_err(p->dev, "Couldn't set LCR to %d\n", value);
 	 */
+	return;		/* Silences "label at the end of compound statement" */
 }
 
 /*
@@ -632,8 +709,10 @@ static int dw8250_probe(struct platform_device *pdev)
 	p->type		= PORT_8250;
 	p->flags	= UPF_FIXED_PORT;
 	p->dev		= dev;
+
 	p->set_ldisc	= dw8250_set_ldisc;
 	p->set_termios	= dw8250_set_termios;
+	p->set_divisor	= dw8250_set_divisor;
 
 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 	if (!data)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index fb0b8397cd4b..8b31b72e4676 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -489,7 +489,7 @@ serial_port_out_sync(struct uart_port *p, int offset, int value)
 /*
  * FIFO support.
  */
-static void serial8250_clear_fifos(struct uart_8250_port *p)
+void serial8250_clear_fifos(struct uart_8250_port *p)
 {
 	if (p->capabilities & UART_CAP_FIFO) {
 		serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO);
@@ -498,6 +498,7 @@ static void serial8250_clear_fifos(struct uart_8250_port *p)
 		serial_out(p, UART_FCR, 0);
 	}
 }
+EXPORT_SYMBOL_NS_GPL(serial8250_clear_fifos, "SERIAL_8250");
 
 static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t);
 static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t);
@@ -3200,6 +3201,17 @@ 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)
+{
+	unsigned int i;
+
+	for (i = 0; i < count; i++) {
+		if (wait_for_lsr(up, UART_LSR_THRE))
+			return;
+	}
+}
+EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, "SERIAL_8250");
+
 #ifdef CONFIG_SERIAL_8250_CONSOLE
 
 static void serial8250_console_putchar(struct uart_port *port, unsigned char ch)
@@ -3241,16 +3253,6 @@ static void serial8250_console_restore(struct uart_8250_port *up)
 	serial8250_out_MCR(up, up->mcr | UART_MCR_DTR | UART_MCR_RTS);
 }
 
-static void fifo_wait_for_lsr(struct uart_8250_port *up, unsigned int count)
-{
-	unsigned int i;
-
-	for (i = 0; i < count; i++) {
-		if (wait_for_lsr(up, UART_LSR_THRE))
-			return;
-	}
-}
-
 /*
  * Print a string to the serial port using the device FIFO
  *
@@ -3269,7 +3271,7 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
 
 	while (s != end) {
 		/* Allow timeout for each byte of a possibly full FIFO */
-		fifo_wait_for_lsr(up, fifosize);
+		serial8250_fifo_wait_for_lsr_thre(up, fifosize);
 
 		for (i = 0; i < fifosize && s != end; ++i) {
 			if (*s == '\n' && !cr_sent) {
@@ -3287,7 +3289,7 @@ 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
 	 */
-	fifo_wait_for_lsr(up, tx_count);
+	serial8250_fifo_wait_for_lsr_thre(up, tx_count);
 }
 
 /*
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 6/7] serial: 8250: Add late synchronize_irq() to shutdown to handle DW UART BUSY
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, Jamie Iles, linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, Ilpo Järvinen, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

When DW UART is !uart_16550_compatible, it can indicate BUSY at any
point (when under constant Rx pressure) unless a complex sequence of
steps is performed. Any LCR write can run a foul with the condition
that prevents writing LCR while the UART is BUSY, which triggers
BUSY_DETECT interrupt that seems unmaskable using IER bits.

Normal flow is that dw8250_handle_irq() handles BUSY_DETECT condition
by reading USR register. This BUSY feature, however, breaks the
assumptions made in serial8250_do_shutdown(), which runs
synchronize_irq() after clearing IER and assumes no interrupts can
occur after that point but then proceeds to update LCR, which on DW
UART can trigger an interrupt.

If serial8250_do_shutdown() releases the interrupt handler before the
handler has run and processed the BUSY_DETECT condition by read the USR
register, the IRQ is not deasserted resulting in interrupt storm that
triggers "irq x: nobody cared" warning leading to disabling the IRQ.

Add late synchronize_irq() into serial8250_do_shutdown() to ensure
BUSY_DETECT from DW UART is handled before port's interrupt handler is
released. Alternative would be to add DW UART specific shutdown
function but it would mostly duplicate the generic code and the extra
synchronize_irq() seems pretty harmless in serial8250_do_shutdown().

Fixes: 7d4008ebb1c9 ("tty: add a DesignWare 8250 driver")
Cc: stable@vger.kernel.org
Reported-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_port.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index bc223eb1f474..fb0b8397cd4b 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2401,6 +2401,12 @@ void serial8250_do_shutdown(struct uart_port *port)
 	 * the IRQ chain.
 	 */
 	serial_port_in(port, UART_RX);
+	/*
+	 * LCR writes on DW UART can trigger late (unmaskable) IRQs.
+	 * Handle them before releasing the handler.
+	 */
+	synchronize_irq(port->irq);
+
 	serial8250_rpm_put(up);
 
 	up->ops->release_irq(up);
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 5/7] serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, Ilpo Järvinen, linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

INTC10EE UART can end up into an interrupt storm where it reports
IIR_NO_INT (0x1). If the storm happens during active UART operation, it
is promptly stopped by IIR value change due to Rx or Tx events.
However, when there is no activity, either due to idle serial line or
due to specific circumstances such as during shutdown that writes
IER=0, there is nothing to stop the storm.

During shutdown the storm is particularly problematic because
serial8250_do_shutdown() calls synchronize_irq() that will hang in
waiting for the storm to finish which never happens.

This problem can also result in triggering a warning:

  irq 45: nobody cared (try booting with the "irqpoll" option)
  [...snip...]
  handlers:
    serial8250_interrupt
  Disabling IRQ #45

Normal means to reset interrupt status by reading LSR, MSR, USR, or RX
register do not result in the UART deasserting the IRQ.

Add a quirk to INTC10EE UARTs to enable Tx interrupts if UART's Tx is
currently empty and inactive. Rework IIR_NO_INT to keep track of the
number of consecutive IIR_NO_INT, and on fourth one perform the quirk.
Enabling Tx interrupts should change IIR value from IIR_NO_INT to
IIR_THRI which has been observed to stop the storm.

Fixes: e92fad024929 ("serial: 8250_dw: Add ACPI ID for Granite Rapids-D UART")
Cc: stable@vger.kernel.org
Reported-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_dw.c | 67 +++++++++++++++++++++++++++++--
 1 file changed, 63 insertions(+), 4 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 964750d17186..edae359b1c3f 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -61,6 +61,13 @@
 #define DW_UART_QUIRK_IS_DMA_FC		BIT(3)
 #define DW_UART_QUIRK_APMC0D08		BIT(4)
 #define DW_UART_QUIRK_CPR_VALUE		BIT(5)
+#define DW_UART_QUIRK_IER_KICK		BIT(6)
+
+/*
+ * Number of consecutive IIR_NO_INT interrupts required to trigger interrupt
+ * storm prevention code.
+ */
+#define DW_UART_QUIRK_IER_KICK_THRES	4
 
 struct dw8250_platform_data {
 	u8 usr_reg;
@@ -82,6 +89,8 @@ struct dw8250_data {
 
 	unsigned int		skip_autocfg:1;
 	unsigned int		uart_16550_compatible:1;
+
+	u8			no_int_count;
 };
 
 static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
@@ -308,6 +317,29 @@ static u32 dw8250_serial_in32be(struct uart_port *p, unsigned int offset)
        return dw8250_modify_msr(p, offset, value);
 }
 
+/*
+ * INTC10EE UART can IRQ storm while reporting IIR_NO_INT. Inducing IIR value
+ * change has been observed to break the storm.
+ *
+ * If Tx is empty (THRE asserted), we use here IER_THRI to cause IIR_NO_INT ->
+ * IIR_THRI transition.
+ */
+static void dw8250_quirk_ier_kick(struct uart_port *p)
+{
+	struct uart_8250_port *up = up_to_u8250p(p);
+	u32 lsr;
+
+	if (up->ier & UART_IER_THRI)
+		return;
+
+	lsr = serial_lsr_in(up);
+	if (!(lsr & UART_LSR_THRE))
+		return;
+
+	serial_port_out(p, UART_IER, up->ier | UART_IER_THRI);
+	serial_port_in(p, UART_LCR);		/* safe, no side-effects */
+	serial_port_out(p, UART_IER, up->ier);
+}
 
 static int dw8250_handle_irq(struct uart_port *p)
 {
@@ -318,18 +350,30 @@ static int dw8250_handle_irq(struct uart_port *p)
 	unsigned int quirks = d->pdata->quirks;
 	unsigned int status;
 
+	guard(uart_port_lock_irqsave)(p);
+
 	switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
 	case UART_IIR_NO_INT:
+		if (d->uart_16550_compatible || up->dma)
+			return 0;
+
+		if (quirks & DW_UART_QUIRK_IER_KICK &&
+		    d->no_int_count == (DW_UART_QUIRK_IER_KICK_THRES - 1))
+			dw8250_quirk_ier_kick(p);
+		d->no_int_count = (d->no_int_count + 1) % DW_UART_QUIRK_IER_KICK_THRES;
+
 		return 0;
 
 	case UART_IIR_BUSY:
 		/* Clear the USR */
 		serial_port_in(p, d->pdata->usr_reg);
 
+		d->no_int_count = 0;
+
 		return 1;
 	}
 
-	guard(uart_port_lock_irqsave)(p);
+	d->no_int_count = 0;
 
 	/*
 	 * There are ways to get Designware-based UARTs into a state where
@@ -562,6 +606,14 @@ static void dw8250_reset_control_assert(void *data)
 	reset_control_assert(data);
 }
 
+static void dw8250_shutdown(struct uart_port *port)
+{
+	struct dw8250_data *d = to_dw8250_data(port->private_data);
+
+	serial8250_do_shutdown(port);
+	d->no_int_count = 0;
+}
+
 static int dw8250_probe(struct platform_device *pdev)
 {
 	struct uart_8250_port uart = {}, *up = &uart;
@@ -685,10 +737,12 @@ static int dw8250_probe(struct platform_device *pdev)
 		dw8250_quirks(p, data);
 
 	/* If the Busy Functionality is not implemented, don't handle it */
-	if (data->uart_16550_compatible)
+	if (data->uart_16550_compatible) {
 		p->handle_irq = NULL;
-	else if (data->pdata)
+	} else if (data->pdata) {
 		p->handle_irq = dw8250_handle_irq;
+		p->shutdown = dw8250_shutdown;
+	}
 
 	dw8250_setup_dma_filter(p, data);
 
@@ -815,6 +869,11 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_data = {
 	.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
 };
 
+static const struct dw8250_platform_data dw8250_intc10ee = {
+	.usr_reg = DW_UART_USR,
+	.quirks = DW_UART_QUIRK_IER_KICK,
+};
+
 static const struct of_device_id dw8250_of_match[] = {
 	{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
 	{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
@@ -844,7 +903,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = {
 	{ "INT33C5", (kernel_ulong_t)&dw8250_dw_apb },
 	{ "INT3434", (kernel_ulong_t)&dw8250_dw_apb },
 	{ "INT3435", (kernel_ulong_t)&dw8250_dw_apb },
-	{ "INTC10EE", (kernel_ulong_t)&dw8250_dw_apb },
+	{ "INTC10EE", (kernel_ulong_t)&dw8250_intc10ee },
 	{ },
 };
 MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 4/7] serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, Ilpo Järvinen, linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

dw8250_handle_irq() takes port's lock multiple times with no good
reason to release it in between and calls serial8250_handle_irq()
that also takes port's lock.

Take port's lock only once in dw8250_handle_irq() and use
serial8250_handle_irq_locked() to avoid releasing port's lock in
between.

As IIR_NO_INT check in serial8250_handle_irq() was outside of port's
lock, it has to be done already in dw8250_handle_irq().

DW UART can, in addition to IIR_NO_INT, report BUSY_DETECT (0x7) which
collided with the IIR_NO_INT (0x1) check in serial8250_handle_irq()
(because & is used instead of ==) meaning that no other work is done by
serial8250_handle_irq() during an BUSY_DETECT interrupt.

This allows reorganizing code in dw8250_handle_irq() to do both
IIR_NO_INT and BUSY_DETECT handling right at the start simplifying
the logic.

Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Cc: stable@vger.kernel.org
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_dw.c | 37 ++++++++++++++++++-------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 7500b1ff1ac1..964750d17186 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -9,6 +9,9 @@
  * LCR is written whilst busy.  If it is, then a busy detect interrupt is
  * raised, the LCR needs to be rewritten and the uart status register read.
  */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/device.h>
@@ -40,6 +43,8 @@
 #define RZN1_UART_RDMACR 0x110 /* DMA Control Register Receive Mode */
 
 /* DesignWare specific register fields */
+#define DW_UART_IIR_IID			GENMASK(3, 0)
+
 #define DW_UART_MCR_SIRE		BIT(6)
 
 /* Renesas specific register fields */
@@ -312,7 +317,19 @@ static int dw8250_handle_irq(struct uart_port *p)
 	bool rx_timeout = (iir & 0x3f) == UART_IIR_RX_TIMEOUT;
 	unsigned int quirks = d->pdata->quirks;
 	unsigned int status;
-	unsigned long flags;
+
+	switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
+	case UART_IIR_NO_INT:
+		return 0;
+
+	case UART_IIR_BUSY:
+		/* Clear the USR */
+		serial_port_in(p, d->pdata->usr_reg);
+
+		return 1;
+	}
+
+	guard(uart_port_lock_irqsave)(p);
 
 	/*
 	 * There are ways to get Designware-based UARTs into a state where
@@ -325,20 +342,15 @@ static int dw8250_handle_irq(struct uart_port *p)
 	 * so we limit the workaround only to non-DMA mode.
 	 */
 	if (!up->dma && rx_timeout) {
-		uart_port_lock_irqsave(p, &flags);
 		status = serial_lsr_in(up);
 
 		if (!(status & (UART_LSR_DR | UART_LSR_BI)))
 			serial_port_in(p, UART_RX);
-
-		uart_port_unlock_irqrestore(p, flags);
 	}
 
 	/* Manually stop the Rx DMA transfer when acting as flow controller */
 	if (quirks & DW_UART_QUIRK_IS_DMA_FC && up->dma && up->dma->rx_running && rx_timeout) {
-		uart_port_lock_irqsave(p, &flags);
 		status = serial_lsr_in(up);
-		uart_port_unlock_irqrestore(p, flags);
 
 		if (status & (UART_LSR_DR | UART_LSR_BI)) {
 			dw8250_writel_ext(p, RZN1_UART_RDMACR, 0);
@@ -346,17 +358,9 @@ static int dw8250_handle_irq(struct uart_port *p)
 		}
 	}
 
-	if (serial8250_handle_irq(p, iir))
-		return 1;
-
-	if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
-		/* Clear the USR */
-		serial_port_in(p, d->pdata->usr_reg);
+	serial8250_handle_irq_locked(p, iir);
 
-		return 1;
-	}
-
-	return 0;
+	return 1;
 }
 
 static void dw8250_clk_work_cb(struct work_struct *work)
@@ -858,6 +862,7 @@ static struct platform_driver dw8250_platform_driver = {
 
 module_platform_driver(dw8250_platform_driver);
 
+MODULE_IMPORT_NS("SERIAL_8250");
 MODULE_AUTHOR("Jamie Iles");
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver");
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 3/7] serial: 8250: Add serial8250_handle_irq_locked()
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, Ilpo Järvinen, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

8250_port exports serial8250_handle_irq() to HW specific 8250 drivers.
It takes port's lock within but a HW specific 8250 driver may want to
take port's lock itself, do something, and then call the generic
handler in 8250_port but to do that, the caller has to release port's
lock for no good reason.

Introduce serial8250_handle_irq_locked() which a HW specific driver can
call while already holding port's lock.

As this is new export, put it straight into a namespace (where all 8250
exports should eventually be moved).

Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Cc: stable@vger.kernel.org
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_port.c | 24 ++++++++++++++++--------
 include/linux/serial_8250.h         |  1 +
 2 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index f7a3c5555204..bc223eb1f474 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -18,6 +18,7 @@
 #include <linux/irq.h>
 #include <linux/console.h>
 #include <linux/gpio/consumer.h>
+#include <linux/lockdep.h>
 #include <linux/sysrq.h>
 #include <linux/delay.h>
 #include <linux/platform_device.h>
@@ -1782,20 +1783,16 @@ static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir)
 }
 
 /*
- * This handles the interrupt from one port.
+ * Context: port's lock must be held by the caller.
  */
-int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
+void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir)
 {
 	struct uart_8250_port *up = up_to_u8250p(port);
 	struct tty_port *tport = &port->state->port;
 	bool skip_rx = false;
-	unsigned long flags;
 	u16 status;
 
-	if (iir & UART_IIR_NO_INT)
-		return 0;
-
-	uart_port_lock_irqsave(port, &flags);
+	lockdep_assert_held_once(&port->lock);
 
 	status = serial_lsr_in(up);
 
@@ -1828,8 +1825,19 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
 		else if (!up->dma->tx_running)
 			__stop_tx(up);
 	}
+}
+EXPORT_SYMBOL_NS_GPL(serial8250_handle_irq_locked, "SERIAL_8250");
 
-	uart_unlock_and_check_sysrq_irqrestore(port, flags);
+/*
+ * This handles the interrupt from one port.
+ */
+int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
+{
+	if (iir & UART_IIR_NO_INT)
+		return 0;
+
+	guard(uart_port_lock_irqsave)(port);
+	serial8250_handle_irq_locked(port, iir);
 
 	return 1;
 }
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h
index 01efdce0fda0..a95b2d143d24 100644
--- a/include/linux/serial_8250.h
+++ b/include/linux/serial_8250.h
@@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl);
 void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud,
 			       unsigned int quot);
 int fsl8250_handle_irq(struct uart_port *port);
+void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir);
 int serial8250_handle_irq(struct uart_port *port, unsigned int iir);
 u16 serial8250_rx_chars(struct uart_8250_port *up, u16 lsr);
 void serial8250_read_char(struct uart_8250_port *up, u16 lsr);
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 2/7] serial: 8250_dw: Avoid unnecessary LCR writes
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, Ilpo Järvinen, linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

When DW UART is configured with BUSY flag, LCR writes may not always
succeed which can make any LCR write complex and very expensive.
Performing write directly can trigger IRQ and the driver has to perform
complex and distruptive sequence while retrying the write.

Therefore, it's better to avoid doing LCR write that would not change
the value of the LCR register. Add LCR write avoidance code into the
8250_dw driver's .serial_out() functions.

Reported-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Cc: stable@vger.kernel.org
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_dw.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 27af83f0ff46..7500b1ff1ac1 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -181,6 +181,22 @@ static void dw8250_check_lcr(struct uart_port *p, unsigned int offset, u32 value
 	 */
 }
 
+/*
+ * With BUSY, LCR writes can be very expensive (IRQ + complex retry logic).
+ * If the write does not change the value of the LCR register, skip it entirely.
+ */
+static bool dw8250_can_skip_reg_write(struct uart_port *p, unsigned int offset, u32 value)
+{
+	struct dw8250_data *d = to_dw8250_data(p->private_data);
+	u32 lcr;
+
+	if (offset != UART_LCR || d->uart_16550_compatible)
+		return false;
+
+	lcr = serial_port_in(p, offset);
+	return lcr == value;
+}
+
 /* Returns once the transmitter is empty or we run out of retries */
 static void dw8250_tx_wait_empty(struct uart_port *p)
 {
@@ -207,12 +223,18 @@ static void dw8250_tx_wait_empty(struct uart_port *p)
 
 static void dw8250_serial_out(struct uart_port *p, unsigned int offset, u32 value)
 {
+	if (dw8250_can_skip_reg_write(p, offset, value))
+		return;
+
 	writeb(value, p->membase + (offset << p->regshift));
 	dw8250_check_lcr(p, offset, value);
 }
 
 static void dw8250_serial_out38x(struct uart_port *p, unsigned int offset, u32 value)
 {
+	if (dw8250_can_skip_reg_write(p, offset, value))
+		return;
+
 	/* Allow the TX to drain before we reconfigure */
 	if (offset == UART_LCR)
 		dw8250_tx_wait_empty(p);
@@ -237,6 +259,9 @@ static u32 dw8250_serial_inq(struct uart_port *p, unsigned int offset)
 
 static void dw8250_serial_outq(struct uart_port *p, unsigned int offset, u32 value)
 {
+	if (dw8250_can_skip_reg_write(p, offset, value))
+		return;
+
 	value &= 0xff;
 	__raw_writeq(value, p->membase + (offset << p->regshift));
 	/* Read back to ensure register write ordering. */
@@ -248,6 +273,9 @@ static void dw8250_serial_outq(struct uart_port *p, unsigned int offset, u32 val
 
 static void dw8250_serial_out32(struct uart_port *p, unsigned int offset, u32 value)
 {
+	if (dw8250_can_skip_reg_write(p, offset, value))
+		return;
+
 	writel(value, p->membase + (offset << p->regshift));
 	dw8250_check_lcr(p, offset, value);
 }
@@ -261,6 +289,9 @@ static u32 dw8250_serial_in32(struct uart_port *p, unsigned int offset)
 
 static void dw8250_serial_out32be(struct uart_port *p, unsigned int offset, u32 value)
 {
+	if (dw8250_can_skip_reg_write(p, offset, value))
+		return;
+
 	iowrite32be(value, p->membase + (offset << p->regshift));
 	dw8250_check_lcr(p, offset, value);
 }
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 1/7] serial: 8250: Protect LCR write in shutdown
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae, linux-kernel
  Cc: Bandal, Shankar, Murthy, Shanth, Ilpo Järvinen, stable
In-Reply-To: <20260203171049.4353-1-ilpo.jarvinen@linux.intel.com>

The 8250_dw driver needs to potentially perform very complex operations
during LCR writes because its BUSY handling prevents updates to LCR
while UART is BUSY (which is not fully under our control without those
complex operations). Thus, LCR writes should occur under port's lock.

Move LCR write under port's lock in serial8250_do_shutdown(). Also
split the LCR RMW so that the logic is on a separate line for clarity.

Reported-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Bandal, Shankar" <shankar.bandal@intel.com>
Tested-by: "Murthy, Shanth" <shanth.murthy@intel.com>
Cc: stable@vger.kernel.org
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
---
 drivers/tty/serial/8250/8250_port.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 719faf92aa8a..f7a3c5555204 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2350,6 +2350,7 @@ static int serial8250_startup(struct uart_port *port)
 void serial8250_do_shutdown(struct uart_port *port)
 {
 	struct uart_8250_port *up = up_to_u8250p(port);
+	u32 lcr;
 
 	serial8250_rpm_get(up);
 	/*
@@ -2376,13 +2377,13 @@ void serial8250_do_shutdown(struct uart_port *port)
 			port->mctrl &= ~TIOCM_OUT2;
 
 		serial8250_set_mctrl(port, port->mctrl);
+
+		/* Disable break condition */
+		lcr = serial_port_in(port, UART_LCR);
+		lcr &= ~UART_LCR_SBC;
+		serial_port_out(port, UART_LCR, lcr);
 	}
 
-	/*
-	 * Disable break condition and FIFOs
-	 */
-	serial_port_out(port, UART_LCR,
-			serial_port_in(port, UART_LCR) & ~UART_LCR_SBC);
 	serial8250_clear_fifos(up);
 
 	rsa_disable(up);
-- 
2.39.5


^ permalink raw reply related

* [PATCH v4 0/7] 8250 DW UART fixes when under constant Rx pressure
From: Ilpo Järvinen @ 2026-02-03 17:10 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, linux-serial, Andy Shevchenko,
	qianfan Zhao, Adriana Nicolae
  Cc: linux-kernel, Bandal, Shankar, Murthy, Shanth, Ilpo Järvinen

Hi all,

Here are fixes to 8250 DW UART conditions that mostly occur in scenarios
under constant Rx pressure which are made complicated by BUSY handling
of DW UARTs (used when !uart_16550_compatible).

A few of the changes touch also 8250_port but it's mostly moving existing
code around (except for the extra synchronize_irq() in shutdown).

Pending cleanups (to be sent separately):
 - 8250_dw: UART_IIR_RX_TIMEOUT move to switch/case 
 - 8250: include sorting
 - 8250: iir types to u8

v4:
- Fix warning about label at the end (due to commented out code) being
  an extension by adding an otherwise unnecessary return.

v3:
- Rollback dw8250_idle_enter() failures within
- usr_reg = ... ? ... : ...;
- Leave LCR write fail dev_err() where it was (commented out)

v2:
- Added Fixes tags pointing DW & INTC10EE introducing commits
- Added Cc stable to prerequisite patches
- Separate adding serial8250_handle_irq_locked() to own patch
- Put new exports to SERIAL_8250 namespace (eventually all 8250
  exports should move there, but out-of-scope for this series)
- Changes to no_int_count
	- Change type to u8
	- Use modulo in increment, add a define for it
	- Perform kick only on 4th NO_INT
- Use serial_port_in/out() throughout the series
- Add FIXME comments to ndelay(frame_time) about very low baud rates
- Add cleanup.h
- Tweak lockdep.h place among misordered includes
- Wording tweaks to changelogs and comments

Ilpo Järvinen (7):
  serial: 8250: Protect LCR write in shutdown
  serial: 8250_dw: Avoid unnecessary LCR writes
  serial: 8250: Add serial8250_handle_irq_locked()
  serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
  serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
  serial: 8250: Add late synchronize_irq() to shutdown to handle DW UART
    BUSY
  serial: 8250_dw: Ensure BUSY is deasserted

 drivers/tty/serial/8250/8250.h      |  25 +++
 drivers/tty/serial/8250/8250_dw.c   | 296 ++++++++++++++++++++++------
 drivers/tty/serial/8250/8250_port.c |  69 ++++---
 include/linux/serial_8250.h         |   1 +
 4 files changed, 304 insertions(+), 87 deletions(-)


base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
-- 
2.39.5


^ permalink raw reply

* [PATCH v3 00/10] Add support for Renesas RZ/G3L SoC and SMARC-EVK platform
From: Biju @ 2026-02-03 10:30 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Vinod Koul, Michael Turquette,
	Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Geert Uytterhoeven, Magnus Damm
  Cc: Biju Das, linux-kernel, linux-serial, dmaengine, devicetree,
	linux-clk, linux-renesas-soc, Prabhakar Mahadev Lad, Biju Das

From: Biju Das <biju.das.jz@bp.renesas.com>

Hi all,

This patch series adds initial support for the Renesas RZ/G3L SoC and
RZ/G3L SMARC EVK platform. The RZ/G3L device is a general-purpose
microprocessor with a quad-core CA-55, single core CM-33, Mali-G31
3-D Graphics and other peripherals.

Support for the below list of blocks is added in the SoC DTSI (r9a08g046.dtsi):

 - EXT CLK
 - 4X CA55
 - SCIF
 - CPG
 - GIC
 - ARMv8 Timer

This series also adds SCIF support for the RZ/G3L SMARC EVK board (r9a08g046l48-smarc.dts).

v2->v3:
 * Added macros R9A08G046_ETH{0,1}_CLK_{TX,RX}_I_RMII in r9a08g046-cpg.h.
 * Keep the tag from Conor as it is trivial change for just adding macros.
v1->v2:
 * Dropped scif bindings patch as it is accepted.
 * Collected tags.
 * Squashed the patch#3 and #4
 * Documented GE3D/VCP for all SoC variants
 * Documented external ethernet clocks as it is a clock source for MUX
   inside CPG
 * Updated commit description for bindings.
 * Keep the tag from Conor as it is trivial change for adding more
   clks.
 * Added CLK_ETH{0,1}_TXC_TX_CLK_IN and CLK_ETH{0,1}_RXC_RX_CLK_IN clocks
   in clk table.
 * Dropped R9A08G046_IA55_PCLK from critical clock list.
 * Added external clocks eth{0,1}_txc_tx_clk and eth{0,1}_rxc_rx_clk
   in soc dtsi as it needed for cpg as it is a clock source for mux.
 * Updated cpg node.
 * Dropped gpio.h header from SoM dtsi.
 * Dropped scif node as it is already included in common platform
   file.

Test logs:
/ # uname -r
6.19.0-rc8-next-20260202-g61054f67d824
/ # cat /proc/cpuinfo
processor       : 0
BogoMIPS        : 48.00
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x2
CPU part        : 0xd05
CPU revision    : 0

processor       : 1
BogoMIPS        : 48.00
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x2
CPU part        : 0xd05
CPU revision    : 0

processor       : 2
BogoMIPS        : 48.00
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x2
CPU part        : 0xd05
CPU revision    : 0

processor       : 3
BogoMIPS        : 48.00
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer : 0x41
CPU architecture: 8
CPU variant     : 0x2
CPU part        : 0xd05
CPU revision    : 0

/ # cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 11:        271        105        273         62    GICv3  27 Level     arch_timer
 14:          0          0          0          0    GICv3 185 Edge      error
 15:          0          0          0          0    GICv3 186 Edge      11820000.dma-controller:0
 16:          0          0          0          0    GICv3 187 Edge      11820000.dma-controller:1
 17:          0          0          0          0    GICv3 188 Edge      11820000.dma-controller:2
 18:          0          0          0          0    GICv3 189 Edge      11820000.dma-controller:3
 19:          0          0          0          0    GICv3 190 Edge      11820000.dma-controller:4
 20:          0          0          0          0    GICv3 191 Edge      11820000.dma-controller:5
 21:          0          0          0          0    GICv3 192 Edge      11820000.dma-controller:6
 22:          0          0          0          0    GICv3 193 Edge      11820000.dma-controller:7
 23:          0          0          0          0    GICv3 194 Edge      11820000.dma-controller:8
 24:          0          0          0          0    GICv3 195 Edge      11820000.dma-controller:9
 25:          0          0          0          0    GICv3 196 Edge      11820000.dma-controller:10
 26:          0          0          0          0    GICv3 197 Edge      11820000.dma-controller:11
 27:          0          0          0          0    GICv3 198 Edge      11820000.dma-controller:12
 28:          0          0          0          0    GICv3 199 Edge      11820000.dma-controller:13
 29:          0          0          0          0    GICv3 200 Edge      11820000.dma-controller:14
 30:          0          0          0          0    GICv3 201 Edge      11820000.dma-controller:15
 31:          0          0          0          0    GICv3 418 Level     100ac000.serial:rx err
 32:          4          0          0          0    GICv3 420 Level     100ac000.serial:rx full
 33:        222          0          0          0    GICv3 421 Level     100ac000.serial:tx empty
 34:          0          0          0          0    GICv3 419 Level     100ac000.serial:break
 35:         13          0          0          0    GICv3 422 Level     100ac000.serial:rx ready
IPI0:        20          9         37         20       Rescheduling interrupts
IPI1:       238        226        223        248       Function call interrupts
IPI2:         0          0          0          0       CPU stop interrupts
IPI3:         0          0          0          0       CPU stop NMIs
IPI4:         0          0          0          0       Timer broadcast interrupts
IPI5:         0          0          0          0       IRQ work interrupts
IPI6:         0          0          0          0       CPU backtrace interrupts
IPI7:         0          0          0          0       KGDB roundup interrupts
Err:          0
/ # cat /proc/meminfo
MemTotal:        1887812 kB
MemFree:         1848236 kB
MemAvailable:    1815628 kB
/ # cat /sys/devices/soc0/family
RZ/G3L
/ # cat /sys/devices/soc0/machine
Renesas SMARC EVK version 2 based on r9a08g046l48
/ # cat /sys/devices/soc0/soc_id
r9a08g046
/ # cat /sys/devices/soc0/revision
0
/ # dmesg | grep r9a
[    0.000000] Machine model: Renesas SMARC EVK version 2 based on r9a08g046l48
[    0.039307] renesas-rz-sysc 11020000.system-controller: Detected Renesas RZ/G3L r9a08g046 Rev 0

Biju Das (10):
  dt-bindings: dma: rz-dmac: Document RZ/G3L SoC
  dt-bindings: soc: renesas: Document RZ/G3L SoC variants, SMARC SoM and
    Carrier-II EVK
  dt-bindings: soc: renesas: renesas,rzg2l-sysc: Document RZ/G3L SoC
  soc: renesas: rz-sysc: Add SoC identification for RZ/G3L SoC
  dt-bindings: clock: Document RZ/G3L SoC
  clk: renesas: Add support for RZ/G3L SoC
  arm64: dts: renesas: Add initial DTSI for RZ/G3L SoC
  arm64: dts: renesas: Add initial support for RZ/G3L SMARC SoM
  arm64: dts: renesas: renesas-smarc2: Move usb3 nodes to board DTS
  arm64: dts: renesas: Add initial device tree for RZ/G3L SMARC EVK
    board

 .../bindings/clock/renesas,rzg2l-cpg.yaml     |  40 +-
 .../bindings/dma/renesas,rz-dmac.yaml         |   1 +
 .../soc/renesas/renesas,rzg2l-sysc.yaml       |   1 +
 .../bindings/soc/renesas/renesas.yaml         |  13 +
 arch/arm64/boot/dts/renesas/Makefile          |   2 +
 arch/arm64/boot/dts/renesas/r9a08g046.dtsi    | 251 +++++++++++++
 .../boot/dts/renesas/r9a08g046l48-smarc.dts   |  37 ++
 arch/arm64/boot/dts/renesas/r9a08g046l48.dtsi |  13 +
 .../boot/dts/renesas/r9a09g047e57-smarc.dts   |   6 +
 .../boot/dts/renesas/renesas-smarc2.dtsi      |   8 -
 .../boot/dts/renesas/rzg3l-smarc-som.dtsi     |  20 +
 drivers/clk/renesas/Kconfig                   |   7 +-
 drivers/clk/renesas/Makefile                  |   1 +
 drivers/clk/renesas/r9a08g046-cpg.c           | 144 ++++++++
 drivers/clk/renesas/rzg2l-cpg.c               |   6 +
 drivers/clk/renesas/rzg2l-cpg.h               |   1 +
 drivers/soc/renesas/Kconfig                   |  12 +
 drivers/soc/renesas/Makefile                  |   1 +
 drivers/soc/renesas/r9a08g046-sysc.c          |  91 +++++
 drivers/soc/renesas/rz-sysc.c                 |   3 +
 drivers/soc/renesas/rz-sysc.h                 |   1 +
 include/dt-bindings/clock/r9a08g046-cpg.h     | 343 ++++++++++++++++++
 22 files changed, 988 insertions(+), 14 deletions(-)
 create mode 100644 arch/arm64/boot/dts/renesas/r9a08g046.dtsi
 create mode 100644 arch/arm64/boot/dts/renesas/r9a08g046l48-smarc.dts
 create mode 100644 arch/arm64/boot/dts/renesas/r9a08g046l48.dtsi
 create mode 100644 arch/arm64/boot/dts/renesas/rzg3l-smarc-som.dtsi
 create mode 100644 drivers/clk/renesas/r9a08g046-cpg.c
 create mode 100644 drivers/soc/renesas/r9a08g046-sysc.c
 create mode 100644 include/dt-bindings/clock/r9a08g046-cpg.h

-- 
2.43.0


^ permalink raw reply

* [PATCH 1/3] vt: add modifier support to cursor keys
From: Nicolas Pitre @ 2026-02-03  4:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Alexey Gladkov
  Cc: Nicolas Pitre, linux-serial, linux-kernel
In-Reply-To: <20260203045457.1049793-1-nico@fluxnic.net>

From: Nicolas Pitre <npitre@baylibre.com>

Generate xterm-style CSI sequences with modifier parameters for arrow
keys when Shift, Alt, or Ctrl are held. For example, Shift+Up produces
ESC [ 1 ; 2 A instead of plain ESC [ A.

The modifier encoding follows the standard xterm convention:
  mod = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0)

When no modifiers are pressed, the original behavior is preserved.

Explicit keymap bindings for modified cursor keys (e.g., "shift keycode
103 = Find") take precedence over this automatic modifier encoding.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
---
 drivers/tty/vt/keyboard.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index d65fc60dd7be..dacc2267de6b 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -765,14 +765,39 @@ static void k_fn(struct vc_data *vc, unsigned char value, char up_flag)
 		pr_err("k_fn called with value=%d\n", value);
 }
 
+/*
+ * Compute xterm-style modifier parameter for CSI sequences.
+ * Returns 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0)
+ */
+static int csi_modifier_param(void)
+{
+	int mod = 1;
+
+	if (shift_state & (BIT(KG_SHIFT) | BIT(KG_SHIFTL) | BIT(KG_SHIFTR)))
+		mod += 1;
+	if (shift_state & (BIT(KG_ALT) | BIT(KG_ALTGR)))
+		mod += 2;
+	if (shift_state & (BIT(KG_CTRL) | BIT(KG_CTRLL) | BIT(KG_CTRLR)))
+		mod += 4;
+	return mod;
+}
+
 static void k_cur(struct vc_data *vc, unsigned char value, char up_flag)
 {
 	static const char cur_chars[] = "BDCA";
+	int mod;
 
 	if (up_flag)
 		return;
 
-	applkey(vc, cur_chars[value], vc_kbd_mode(kbd, VC_CKMODE));
+	mod = csi_modifier_param();
+	if (mod > 1) {
+		char buf[] = { 0x1b, '[', '1', ';', '0' + mod, cur_chars[value], 0x00 };
+
+		puts_queue(vc, buf);
+	} else {
+		applkey(vc, cur_chars[value], vc_kbd_mode(kbd, VC_CKMODE));
+	}
 }
 
 static void k_pad(struct vc_data *vc, unsigned char value, char up_flag)
-- 
2.52.0


^ permalink raw reply related

* [PATCH 0/3] vt: add modifier support to cursor and navigation keys
From: Nicolas Pitre @ 2026-02-03  4:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Alexey Gladkov
  Cc: Nicolas Pitre, linux-serial, linux-kernel

This series adds xterm-style modifier encoding to cursor keys and navigation
keys on the Linux console.

Modern terminal applications (shells, editors, TUI programs) rely on
modifier+key combinations like Ctrl+Left, Shift+Home, or Alt+Delete for
navigation and selection. The xterm protocol encodes these as CSI sequences
with a modifier parameter (e.g., ESC [ 1 ; 5 D for Ctrl+Left).

While the existing func string table mechanism could technically support
these sequences, each modifier combination would require a separate entry,
quickly exhausting the limited string table space. This series instead
generates the sequences programmatically, providing full modifier support
without consuming string table entries.

This series addresses that in three patches:

1. Add modifier encoding to cursor keys (Up/Down/Left/Right). When
Shift, Alt, or Ctrl are held, the arrow keys now emit sequences like
ESC [ 1 ; 2 A instead of plain ESC [ A.

2. Add a new KT_CSI keysym type for navigation keys (Home, End, Insert,
Delete, PgUp, PgDn) and function keys. These generate CSI tilde
sequences (ESC [ n ~) with automatic modifier encoding.

3. Add automatic fallback to the plain keymap for modifier-aware key
types. This eliminates the need for explicit bindings for each
modifier combination - a single plain keymap entry handles all
modifier variants.

The modifier encoding follows the standard xterm convention:
  mod = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0)

Explicit keymap bindings take precedence, preserving backward
compatibility with existing configurations.

Corresponding patches for the kbd package (loadkeys/dumpkeys) are ready
and will be submitted once this kernel support is available.

diffstat:
 drivers/tty/vt/keyboard.c     | 80 ++++++++++++++++++++++++++++++++--
 include/uapi/linux/keyboard.h | 29 +++++++++++++
 2 files changed, 103 insertions(+), 6 deletions(-)


^ permalink raw reply

* [PATCH 2/3] vt: add KT_CSI keysym type for modifier-aware CSI sequences
From: Nicolas Pitre @ 2026-02-03  4:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Alexey Gladkov
  Cc: Nicolas Pitre, linux-serial, linux-kernel
In-Reply-To: <20260203045457.1049793-1-nico@fluxnic.net>

From: Nicolas Pitre <npitre@baylibre.com>

Add a new keysym type KT_CSI that generates CSI tilde sequences with
automatic modifier encoding. The keysym value encodes the CSI parameter
number, producing sequences like ESC [ <value> ~ or ESC [ <value> ; <mod> ~
when Shift, Alt, or Ctrl modifiers are held.

This allows navigation keys (Home, End, Insert, Delete, PgUp, PgDn) and
function keys to generate modifier-aware escape sequences without
consuming string table entries for each modifier combination.

Define key symbols for navigation keys (K_CSI_HOME, K_CSI_END, etc.)
and function keys (K_CSI_F1 through K_CSI_F20) using standard xterm
CSI parameter values.

The modifier encoding follows the xterm convention:
  mod = 1 + (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0)

Allowed CSI parameter values range from 0 to 99.

Note: The Linux console historically uses a non-standard double-bracket
format for F1-F5 (ESC [ [ A through ESC [ [ E) rather than the xterm
tilde format (ESC [ 11 ~ through ESC [ 15 ~). The K_CSI_F1 through
K_CSI_F5 definitions use the xterm format. Converting F1-F5 to KT_CSI
would require updating the "linux" terminfo entry to match. Navigation
keys and F6-F20 already use the tilde format and are fully compatible.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
---
 drivers/tty/vt/keyboard.c     | 38 ++++++++++++++++++++++++++++++-----
 include/uapi/linux/keyboard.h | 29 ++++++++++++++++++++++++++
 2 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index dacc2267de6b..0b323cefc647 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -74,7 +74,7 @@ static inline int kbd_defleds(void)
 	k_self,		k_fn,		k_spec,		k_pad,\
 	k_dead,		k_cons,		k_cur,		k_shift,\
 	k_meta,		k_ascii,	k_lock,		k_lowercase,\
-	k_slock,	k_dead2,	k_brl,		k_ignore
+	k_slock,	k_dead2,	k_brl,		k_csi
 
 typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value,
 			    char up_flag);
@@ -127,6 +127,7 @@ static const unsigned char max_vals[] = {
 	[ KT_SLOCK	] = NR_LOCK - 1,
 	[ KT_DEAD2	] = 255,
 	[ KT_BRL	] = NR_BRL - 1,
+	[ KT_CSI	] = 99,
 };
 
 static const int NR_TYPES = ARRAY_SIZE(max_vals);
@@ -644,10 +645,6 @@ static void fn_null(struct vc_data *vc)
 /*
  * Special key handlers
  */
-static void k_ignore(struct vc_data *vc, unsigned char value, char up_flag)
-{
-}
-
 static void k_spec(struct vc_data *vc, unsigned char value, char up_flag)
 {
 	if (up_flag)
@@ -1029,6 +1026,37 @@ static void k_brl(struct vc_data *vc, unsigned char value, char up_flag)
 	}
 }
 
+/*
+ * Handle KT_CSI keysym type: generate CSI tilde sequences with modifier
+ * support. The value encodes the CSI parameter number, producing sequences
+ * like ESC [ <value> ~ or ESC [ <value> ; <mod> ~ when modifiers are held.
+ */
+static void k_csi(struct vc_data *vc, unsigned char value, char up_flag)
+{
+	char buf[10];
+	int i = 0;
+	int mod;
+
+	if (up_flag)
+		return;
+
+	mod = csi_modifier_param();
+
+	buf[i++] = 0x1b;
+	buf[i++] = '[';
+	if (value >= 10)
+		buf[i++] = '0' + value / 10;
+	buf[i++] = '0' + value % 10;
+	if (mod > 1) {
+		buf[i++] = ';';
+		buf[i++] = '0' + mod;
+	}
+	buf[i++] = '~';
+	buf[i] = 0x00;
+
+	puts_queue(vc, buf);
+}
+
 #if IS_ENABLED(CONFIG_INPUT_LEDS) && IS_ENABLED(CONFIG_LEDS_TRIGGERS)
 
 struct kbd_led_trigger {
diff --git a/include/uapi/linux/keyboard.h b/include/uapi/linux/keyboard.h
index 36d230cedf12..48ecb0cefb45 100644
--- a/include/uapi/linux/keyboard.h
+++ b/include/uapi/linux/keyboard.h
@@ -41,6 +41,7 @@
 #define KT_SLOCK	12
 #define KT_DEAD2	13
 #define KT_BRL		14
+#define KT_CSI		15	/* CSI sequences with modifier support */
 
 #define K(t,v)		(((t)<<8)|(v))
 #define KTYP(x)		((x) >> 8)
@@ -461,5 +462,33 @@
 
 #define NR_BRL		11
 
+/* KT_CSI keys: value is the CSI parameter number for ESC [ <value> ~ */
+#define K_CSI_HOME	K(KT_CSI, 1)	/* ESC [ 1 ~ */
+#define K_CSI_INSERT	K(KT_CSI, 2)	/* ESC [ 2 ~ */
+#define K_CSI_DELETE	K(KT_CSI, 3)	/* ESC [ 3 ~ */
+#define K_CSI_END	K(KT_CSI, 4)	/* ESC [ 4 ~ */
+#define K_CSI_PGUP	K(KT_CSI, 5)	/* ESC [ 5 ~ */
+#define K_CSI_PGDN	K(KT_CSI, 6)	/* ESC [ 6 ~ */
+#define K_CSI_F1	K(KT_CSI, 11)	/* ESC [ 11 ~ */
+#define K_CSI_F2	K(KT_CSI, 12)	/* ESC [ 12 ~ */
+#define K_CSI_F3	K(KT_CSI, 13)	/* ESC [ 13 ~ */
+#define K_CSI_F4	K(KT_CSI, 14)	/* ESC [ 14 ~ */
+#define K_CSI_F5	K(KT_CSI, 15)	/* ESC [ 15 ~ */
+#define K_CSI_F6	K(KT_CSI, 17)	/* ESC [ 17 ~ */
+#define K_CSI_F7	K(KT_CSI, 18)	/* ESC [ 18 ~ */
+#define K_CSI_F8	K(KT_CSI, 19)	/* ESC [ 19 ~ */
+#define K_CSI_F9	K(KT_CSI, 20)	/* ESC [ 20 ~ */
+#define K_CSI_F10	K(KT_CSI, 21)	/* ESC [ 21 ~ */
+#define K_CSI_F11	K(KT_CSI, 23)	/* ESC [ 23 ~ */
+#define K_CSI_F12	K(KT_CSI, 24)	/* ESC [ 24 ~ */
+#define K_CSI_F13	K(KT_CSI, 25)	/* ESC [ 25 ~ */
+#define K_CSI_F14	K(KT_CSI, 26)	/* ESC [ 26 ~ */
+#define K_CSI_F15	K(KT_CSI, 28)	/* ESC [ 28 ~ */
+#define K_CSI_F16	K(KT_CSI, 29)	/* ESC [ 29 ~ */
+#define K_CSI_F17	K(KT_CSI, 31)	/* ESC [ 31 ~ */
+#define K_CSI_F18	K(KT_CSI, 32)	/* ESC [ 32 ~ */
+#define K_CSI_F19	K(KT_CSI, 33)	/* ESC [ 33 ~ */
+#define K_CSI_F20	K(KT_CSI, 34)	/* ESC [ 34 ~ */
+
 #define MAX_DIACR	256
 #endif /* _UAPI__LINUX_KEYBOARD_H */
-- 
2.52.0


^ permalink raw reply related

* [PATCH 3/3] vt: add fallback to plain map for modifier-aware key types
From: Nicolas Pitre @ 2026-02-03  4:52 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Alexey Gladkov
  Cc: Nicolas Pitre, linux-serial, linux-kernel
In-Reply-To: <20260203045457.1049793-1-nico@fluxnic.net>

From: Nicolas Pitre <npitre@baylibre.com>

When a key is pressed with modifiers (Shift, Ctrl, Alt, etc.) and the
modifier-specific keymap has no binding (K_HOLE) or doesn't exist, fall
back to the plain keymap if the plain entry is a modifier-aware type
(KT_CUR or KT_CSI).

This allows arrow keys and CSI navigation keys to automatically handle
all modifier combinations with just a single plain map entry. The key
handlers (k_cur and k_csi) read the modifier state at runtime and encode
it into the output sequence.

For example, with just:
    keycode 103 = Up
    keycode 104 = Csi_Home

All these combinations now work automatically:
    Up         -> ESC [ A
    Shift+Up   -> ESC [ 1 ; 2 A
    Ctrl+Up    -> ESC [ 1 ; 5 A
    Home       -> ESC [ 1 ~
    Shift+Home -> ESC [ 1 ; 2 ~
    Ctrl+Home  -> ESC [ 1 ; 5 ~

Previously, each modifier combination required an explicit keymap entry,
which was tedious and consumed keymap slots.

Explicit modifier bindings still take precedence - the fallback only
triggers when the modifier-specific entry is empty.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
---
 drivers/tty/vt/keyboard.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index 0b323cefc647..a145048e4da3 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -1498,6 +1498,21 @@ static void kbd_keycode(unsigned int keycode, int down, bool hw_raw)
 	param.ledstate = kbd->ledflagstate;
 	key_map = key_maps[shift_final];
 
+	/*
+	 * Fall back to the plain map if modifiers are active, the modifier-
+	 * specific map is missing or has no entry, and the plain map has a
+	 * modifier-aware key type (KT_CUR or KT_CSI). These handlers encode
+	 * the modifier state into the emitted escape sequence.
+	 */
+	if (shift_final && keycode < NR_KEYS &&
+	    (!key_map || key_map[keycode] == K_HOLE) && key_maps[0]) {
+		unsigned short plain = key_maps[0][keycode];
+		unsigned char type = KTYP(plain);
+
+		if (type >= 0xf0 && (type - 0xf0 == KT_CUR || type - 0xf0 == KT_CSI))
+			key_map = key_maps[0];
+	}
+
 	rc = atomic_notifier_call_chain(&keyboard_notifier_list,
 					KBD_KEYCODE, &param);
 	if (rc == NOTIFY_STOP || !key_map) {
-- 
2.52.0


^ permalink raw reply related

* [PATCH] serial: 8250: add CONFIG_SERIAL_8250_PROBE_BAUD option
From: Sheng Yu @ 2026-02-01  4:36 UTC (permalink / raw)
  To: gregkh; +Cc: jirislaby, linux-serial, linux-kernel, Sheng Yu

Currently, the 8250 driver defaults to 9600 baud if no console options
are provided via the command line. This can result in garbled output if
the firmware or bootloader has already initialized the UART to a
different speed.

Introduce CONFIG_SERIAL_8250_PROBE_BAUD. When enabled, the driver will
attempt to read the current baud rate from the hardware registers if
no options are specified, rather than forcing the 9600 default.

Signed-off-by: Sheng Yu <yushenglive@gmail.com>
---
 drivers/tty/serial/8250/8250_core.c |  2 +-
 drivers/tty/serial/8250/8250_port.c |  5 ++++-
 drivers/tty/serial/8250/Kconfig     | 12 ++++++++++++
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index 0e81f78c6063..c11b19921a1f 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -427,7 +427,7 @@ static int univ8250_console_setup(struct console *co, char *options)
 	/* link port to console */
 	uart_port_set_cons(port, co);
 
-	retval = serial8250_console_setup(port, options, false);
+	retval = serial8250_console_setup(port, options, IS_ENABLED(CONFIG_SERIAL_8250_PROBE_BAUD));
 	if (retval != 0)
 		uart_port_set_cons(port, NULL);
 	return retval;
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 719faf92aa8a..5309b921e7b7 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -3399,8 +3399,11 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
 
 	if (options)
 		uart_parse_options(options, &baud, &parity, &bits, &flow);
-	else if (probe)
+	else if (probe) {
 		baud = probe_baud(port);
+		pr_info("console [%s%d] probed baud rate: %d\n",
+			port->cons->name, port->cons->index, baud);
+	}
 
 	ret = uart_set_options(port, port->cons, baud, parity, bits, flow);
 	if (ret)
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index c488ff6f2865..bee6a82023d4 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -596,3 +596,15 @@ config SERIAL_OF_PLATFORM
 	  are probed through devicetree, including Open Firmware based
 	  PowerPC systems and embedded systems on architectures using the
 	  flattened device tree format.
+
+config SERIAL_8250_PROBE_BAUD
+	bool "Probe baud rate if console options are missing"
+	depends on SERIAL_8250
+	help
+	  If the "console=" command line parameter is missing options (e.g.,
+	  "console=ttyS0" instead of "console=ttyS0,115200n8"), this option
+	  allows the kernel to probe the baud rate from hardware instead of
+	  defaulting to 9600.
+
+	  If a baud rate is explicitly provided in the options, that value
+	  is always respected.
-- 
2.51.0


^ permalink raw reply related

* [PATCH] serial: 8250: add CONFIG_SERIAL_8250_PROBE_BAUD option
From: Sheng Yu @ 2026-02-01  4:18 UTC (permalink / raw)
  To: gregkh; +Cc: jirislaby, linux-serial, linux-kernel, Sheng Yu

Currently, the 8250 driver defaults to 9600 baud if no console options
are provided via the command line. This can result in garbled output if
the firmware or bootloader has already initialized the UART to a
different speed.

Introduce CONFIG_SERIAL_8250_PROBE_BAUD. When enabled, the driver will
attempt to read the current baud rate from the hardware registers if
no options are specified, rather than forcing the 9600 default.

Signed-off-by: Sheng Yu <yushenglive@gmail.com>
---
 drivers/tty/serial/8250/8250_core.c |  2 +-
 drivers/tty/serial/8250/8250_port.c |  6 +++++-
 drivers/tty/serial/8250/Kconfig     | 12 ++++++++++++
 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 0e81f78c6063..c11b19921a1f 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -427,7 +427,7 @@ static int univ8250_console_setup(struct console *co, char *options)
 	/* link port to console */
 	uart_port_set_cons(port, co);
 
-	retval = serial8250_console_setup(port, options, false);
+	retval = serial8250_console_setup(port, options, IS_ENABLED(CONFIG_SERIAL_8250_PROBE_BAUD));
 	if (retval != 0)
 		uart_port_set_cons(port, NULL);
 	return retval;
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 719faf92aa8a..dbc0ef56f995 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -3399,8 +3399,12 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
 
 	if (options)
 		uart_parse_options(options, &baud, &parity, &bits, &flow);
-	else if (probe)
+	else if (probe) {
 		baud = probe_baud(port);
+		pr_info("console [%s%d] probed baud rate: %d\n",
+			port->cons->name, port->cons->index, baud);
+
+	}
 
 	ret = uart_set_options(port, port->cons, baud, parity, bits, flow);
 	if (ret)
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index c488ff6f2865..bee6a82023d4 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -596,3 +596,15 @@ config SERIAL_OF_PLATFORM
 	  are probed through devicetree, including Open Firmware based
 	  PowerPC systems and embedded systems on architectures using the
 	  flattened device tree format.
+
+config SERIAL_8250_PROBE_BAUD
+	bool "Probe baud rate if console options are missing"
+	depends on SERIAL_8250
+	help
+	  If the "console=" command line parameter is missing options (e.g.,
+	  "console=ttyS0" instead of "console=ttyS0,115200n8"), this option
+	  allows the kernel to probe the baud rate from hardware instead of
+	  defaulting to 9600.
+
+	  If a baud rate is explicitly provided in the options, that value
+	  is always respected.
-- 
2.51.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox