* Re: [PATCH] serial: 8250: add CONFIG_SERIAL_8250_PROBE_BAUD option
From: Greg KH @ 2026-02-08 15:28 UTC (permalink / raw)
To: Sheng Yu; +Cc: jirislaby, linux-serial, linux-kernel
In-Reply-To: <20260201043615.524435-1-yushenglive@gmail.com>
On Sat, Jan 31, 2026 at 08:36:14PM -0800, Sheng Yu wrote:
> 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
>
>
Hi,
This is the friendly patch-bot of Greg Kroah-Hartman. You have sent him
a patch that has triggered this response. He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created. Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.
You are receiving this message because of the following common error(s)
as indicated below:
- This looks like a new version of a previously submitted patch, but you
did not list below the --- line any changes from the previous version.
Please read the section entitled "The canonical patch format" in the
kernel file, Documentation/process/submitting-patches.rst for what
needs to be done here to properly describe this.
If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.
thanks,
greg k-h's patch email bot
^ permalink raw reply
* Re: [PATCH 0/3] vt: add modifier support to cursor and navigation keys
From: Greg Kroah-Hartman @ 2026-02-08 15:28 UTC (permalink / raw)
To: Nicolas Pitre
Cc: Jiri Slaby, Alexey Gladkov, Nicolas Pitre, linux-serial,
linux-kernel
In-Reply-To: <20260203045457.1049793-1-nico@fluxnic.net>
On Mon, Feb 02, 2026 at 11:52:45PM -0500, Nicolas Pitre wrote:
> 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(-)
>
>
Argh, sorry, I saw this patch series too late for this merge window.
I'll review it after -rc1 is out.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: Greg KH @ 2026-02-08 14:58 UTC (permalink / raw)
To: duoming
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <7ece79f6.6d747.19c3dbe4a1f.Coremail.duoming@zju.edu.cn>
On Sun, Feb 08, 2026 at 10:53:26PM +0800, duoming@zju.edu.cn wrote:
> On Sun, 8 Feb 2026 15:34:49 +0100 Greg KH wrote:
> > > > > > > When IPWireless PCMCIA card is being detached, the ipw_hardware is
> > > > > > > deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> > > > > > > still be running or pending, leading to use-after-free bugs when the
> > > > > > > already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
> > > > > >
> > > > > > Nice, do you have this hardware to test this with?
> > > > >
> > > > > I don't have the real hardware. In order to reproduce the bug, I simulate
> > > > > the IPWireless PCMCIA card in the qemu by allocating and configuring the
> > > > > necessary resources(I/O ports, memory regions, interrupts and so on) to
> > > > > correspond with the hardware expected by the driver in the initialization
> > > > > code of the virtual device.
> > > >
> > > > I wonder if this device even is still around, given that pcmcia is all
> > > > but dead for a very long time.
> > > >
> > > > > > > One race condition scenario is as follows:
> > > > > > >
> > > > > > > CPU 0 (cleanup) | CPU 1 (interrupt)
> > > > > > > ipwireless_hardware_free() | ipwireless_interrupt()
> > > > > > > ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> > > > > > > do_close_hardware() | tasklet_schedule()
> > > > > > > synchronize_irq() |
> > > > > > > kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> > > > > > > | hw = from_tasklet() //USE
> > > > > > > | hw-> //USE
> > > > > > >
> > > > > > > Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> > > > > > > is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> > > > > > > synchronize with any pending or running tasklet. Since do_close_hardware()
> > > > > > > could prevent further interrupts, place tasklet_kill() after it to avoid
> > > > > > > the tasklet being rescheduled by ipwireless_interrupt().
> > > > > >
> > > > > > How was this issue found and tested?
> > > > >
> > > > > The issue was found by static analysis. I test it through the following steps:
> > > > > 1. Simulating the IPWireless PCMCIA device in the qemu and enable it to trigger interrupts.
> > > > > 2. Controlling the removal and attachment of device via sysfs.
> > > >
> > > > So this is with the bind/unbind logic, or some other way? If you are
> > > > unloading the driver, that is something that only root can do, and this
> > > > is a debugging facility, not a "real" way to control drivers and devices
> > > > (yes, the virt drivers abuse this to no end, every time I see this I
> > > > laugh...)
> > >
> > > When the PCMCIA device is attached, we can operate the file
> > > /sys/bus/pcmcia/devices/.../remove to detach the device.
> >
> > 'remove' should be removing the driver from the device, something that
> > is only allowed by root and is not a normal operation at all. race
> > conditions there are "at your own risk" for all drivers as it's pretty
> > much the same as unloading the module, it is there for developer ease
> > only.
>
> I did this only to verify the existence of the bug. In real word scenarios,
> the device removal code can be triggered by removing the real pcmcia hardware.
How? Doesn't the interrupt not happen, and the device get cleaned up by
the bus when it is noticed and then removed? ipwireless_hardware_free()
should not be called if ipwireless_interrupt() is ever happening in that
case just by virtue of the fact that the interrupt will not be there.
> > > > > 3. Triggering interrupts by writing data to device registers via /dev/mem memory mapping
> > > > > in userspace.
> > > >
> > > > Interrupts would not happen if the device is removed. Or is this only
> > > > if the driver is unbound?
> > >
> > > The interrupts should be triggered before the deivce is removed.
> >
> > But when the device is physically removed from the system, no more
> > interrupts will happen.
>
> I think the tasklet is a deferred mechanism. Although interrupts cannot
> happen after device is removed, the tasklet handler may still be executing
> or pending. This is why tasklet_kill() needs to be added.
Ok, there might be a small race, but given that no one has ever seen
this with real hardware (and you need physical access for it), it's
pretty small. But again, I'll let the maintainers here decide if it
should be accepted or not.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: duoming @ 2026-02-08 14:53 UTC (permalink / raw)
To: Greg KH
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <2026020814-aorta-reptilian-e91a@gregkh>
On Sun, 8 Feb 2026 15:34:49 +0100 Greg KH wrote:
> > > > > > When IPWireless PCMCIA card is being detached, the ipw_hardware is
> > > > > > deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> > > > > > still be running or pending, leading to use-after-free bugs when the
> > > > > > already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
> > > > >
> > > > > Nice, do you have this hardware to test this with?
> > > >
> > > > I don't have the real hardware. In order to reproduce the bug, I simulate
> > > > the IPWireless PCMCIA card in the qemu by allocating and configuring the
> > > > necessary resources(I/O ports, memory regions, interrupts and so on) to
> > > > correspond with the hardware expected by the driver in the initialization
> > > > code of the virtual device.
> > >
> > > I wonder if this device even is still around, given that pcmcia is all
> > > but dead for a very long time.
> > >
> > > > > > One race condition scenario is as follows:
> > > > > >
> > > > > > CPU 0 (cleanup) | CPU 1 (interrupt)
> > > > > > ipwireless_hardware_free() | ipwireless_interrupt()
> > > > > > ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> > > > > > do_close_hardware() | tasklet_schedule()
> > > > > > synchronize_irq() |
> > > > > > kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> > > > > > | hw = from_tasklet() //USE
> > > > > > | hw-> //USE
> > > > > >
> > > > > > Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> > > > > > is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> > > > > > synchronize with any pending or running tasklet. Since do_close_hardware()
> > > > > > could prevent further interrupts, place tasklet_kill() after it to avoid
> > > > > > the tasklet being rescheduled by ipwireless_interrupt().
> > > > >
> > > > > How was this issue found and tested?
> > > >
> > > > The issue was found by static analysis. I test it through the following steps:
> > > > 1. Simulating the IPWireless PCMCIA device in the qemu and enable it to trigger interrupts.
> > > > 2. Controlling the removal and attachment of device via sysfs.
> > >
> > > So this is with the bind/unbind logic, or some other way? If you are
> > > unloading the driver, that is something that only root can do, and this
> > > is a debugging facility, not a "real" way to control drivers and devices
> > > (yes, the virt drivers abuse this to no end, every time I see this I
> > > laugh...)
> >
> > When the PCMCIA device is attached, we can operate the file
> > /sys/bus/pcmcia/devices/.../remove to detach the device.
>
> 'remove' should be removing the driver from the device, something that
> is only allowed by root and is not a normal operation at all. race
> conditions there are "at your own risk" for all drivers as it's pretty
> much the same as unloading the module, it is there for developer ease
> only.
I did this only to verify the existence of the bug. In real word scenarios,
the device removal code can be triggered by removing the real pcmcia hardware.
> > > > 3. Triggering interrupts by writing data to device registers via /dev/mem memory mapping
> > > > in userspace.
> > >
> > > Interrupts would not happen if the device is removed. Or is this only
> > > if the driver is unbound?
> >
> > The interrupts should be triggered before the deivce is removed.
>
> But when the device is physically removed from the system, no more
> interrupts will happen.
I think the tasklet is a deferred mechanism. Although interrupts cannot
happen after device is removed, the tasklet handler may still be executing
or pending. This is why tasklet_kill() needs to be added.
Best regards,
Duoming Zhou
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: Greg KH @ 2026-02-08 14:34 UTC (permalink / raw)
To: duoming
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <777dbdbf.6d6f0.19c3d8b1a1c.Coremail.duoming@zju.edu.cn>
On Sun, Feb 08, 2026 at 09:57:32PM +0800, duoming@zju.edu.cn wrote:
> On Date: Sun, 8 Feb 2026 12:00:08 +0100 Greg KH wrote:
> > > > > When IPWireless PCMCIA card is being detached, the ipw_hardware is
> > > > > deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> > > > > still be running or pending, leading to use-after-free bugs when the
> > > > > already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
> > > >
> > > > Nice, do you have this hardware to test this with?
> > >
> > > I don't have the real hardware. In order to reproduce the bug, I simulate
> > > the IPWireless PCMCIA card in the qemu by allocating and configuring the
> > > necessary resources(I/O ports, memory regions, interrupts and so on) to
> > > correspond with the hardware expected by the driver in the initialization
> > > code of the virtual device.
> >
> > I wonder if this device even is still around, given that pcmcia is all
> > but dead for a very long time.
> >
> > > > > One race condition scenario is as follows:
> > > > >
> > > > > CPU 0 (cleanup) | CPU 1 (interrupt)
> > > > > ipwireless_hardware_free() | ipwireless_interrupt()
> > > > > ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> > > > > do_close_hardware() | tasklet_schedule()
> > > > > synchronize_irq() |
> > > > > kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> > > > > | hw = from_tasklet() //USE
> > > > > | hw-> //USE
> > > > >
> > > > > Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> > > > > is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> > > > > synchronize with any pending or running tasklet. Since do_close_hardware()
> > > > > could prevent further interrupts, place tasklet_kill() after it to avoid
> > > > > the tasklet being rescheduled by ipwireless_interrupt().
> > > >
> > > > How was this issue found and tested?
> > >
> > > The issue was found by static analysis. I test it through the following steps:
> > > 1. Simulating the IPWireless PCMCIA device in the qemu and enable it to trigger interrupts.
> > > 2. Controlling the removal and attachment of device via sysfs.
> >
> > So this is with the bind/unbind logic, or some other way? If you are
> > unloading the driver, that is something that only root can do, and this
> > is a debugging facility, not a "real" way to control drivers and devices
> > (yes, the virt drivers abuse this to no end, every time I see this I
> > laugh...)
>
> When the PCMCIA device is attached, we can operate the file
> /sys/bus/pcmcia/devices/.../remove to detach the device.
'remove' should be removing the driver from the device, something that
is only allowed by root and is not a normal operation at all. race
conditions there are "at your own risk" for all drivers as it's pretty
much the same as unloading the module, it is there for developer ease
only.
> > > 3. Triggering interrupts by writing data to device registers via /dev/mem memory mapping
> > > in userspace.
> >
> > Interrupts would not happen if the device is removed. Or is this only
> > if the driver is unbound?
>
> The interrupts should be triggered before the deivce is removed.
But when the device is physically removed from the system, no more
interrupts will happen.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH RFC 2/4] rust: add basic serial device bus abstractions
From: Markus Probst @ 2026-02-08 14:30 UTC (permalink / raw)
To: Danilo Krummrich
Cc: Kari Argillander, Rob Herring, Greg Kroah-Hartman, Jiri Slaby,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
linux-serial, linux-kernel, rust-for-linux
In-Reply-To: <DFNN75KWL8B9.1YHK1ZRV43W7O@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 2861 bytes --]
On Tue, 2026-01-13 at 18:37 +0100, Danilo Krummrich wrote:
> On Tue Jan 13, 2026 at 5:15 PM CET, Markus Probst wrote:
> > > > +impl<T: Driver + 'static> Adapter<T> {
> > > > + const OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
> > > > + receive_buf: if T::HAS_RECEIVE {
> > > > + Some(Self::receive_buf_callback)
> > > > + } else {
> > > > + None
> > > > + },
> > > > + write_wakeup: if T::HAS_WRITE_WAKEUP {
> > > > + Some(Self::write_wakeup_callback)
> > > > + } else {
> > > > + Some(bindings::serdev_device_write_wakeup)
> > > > + },
> > > > + };
> > > > + const INITIAL_OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
> > > > + receive_buf: Some(Self::initial_receive_buf_callback),
> > > > + write_wakeup: if T::HAS_WRITE_WAKEUP_INITIAL {
> > > > + Some(Self::initial_write_wakeup_callback)
> > > > + } else {
> > > > + Some(bindings::serdev_device_write_wakeup)
> > > > + },
> > > > + };
> > > > + const NO_OPS: &'static bindings::serdev_device_ops = &bindings::serdev_device_ops {
> > > > + receive_buf: None,
> > > > + write_wakeup: Some(bindings::serdev_device_write_wakeup),
> > > > + };
> > > > +
> > > > + extern "C" fn probe_callback(sdev: *mut bindings::serdev_device) -> kernel::ffi::c_int {
> > > > + // SAFETY: The serial device bus only ever calls the probe callback with a valid pointer to
> > > > + // a `struct serdev_device`.
> > > > + //
> > > > + // INVARIANT: `sdev` is valid for the duration of `probe_callback()`.
> > > > + let sdev = unsafe { &*sdev.cast::<Device<device::CoreInternal>>() };
> > > > + let info = <Self as driver::Adapter>::id_info(sdev.as_ref());
> > > > +
> > > > + from_result(|| {
> > > > + let data = try_pin_init!(Drvdata {
> > > > + driver <- T::probe(sdev, info),
> > > > + initial_data: Some(Default::default()).into(),
> > > > + late_probe_data: None.into(),
> > > > + });
> > > > +
> > > > + sdev.as_ref().set_drvdata(data)?;
>
> This does not work, a driver can obtain its device private data with
> Device::<Bound>::drvdata() [1].
>
> For this the driver must assert the correct type, but since you use a private
> type instead of the type given by the driver, i.e. T, Device::<Bound>::drvdata()
> will always fail for the driver.
>
> [1] https://rust.docs.kernel.org/kernel/device/struct.Device.html#method.drvdata
>
What do you suggest as alternative? Besides from the "initial data"
debate, I still need to store at least one `Completion` to ensure
safety.
Thanks
- Markus Probst
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: duoming @ 2026-02-08 13:57 UTC (permalink / raw)
To: Greg KH
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <2026020859-caretaker-duckbill-0fb3@gregkh>
On Date: Sun, 8 Feb 2026 12:00:08 +0100 Greg KH wrote:
> > > > When IPWireless PCMCIA card is being detached, the ipw_hardware is
> > > > deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> > > > still be running or pending, leading to use-after-free bugs when the
> > > > already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
> > >
> > > Nice, do you have this hardware to test this with?
> >
> > I don't have the real hardware. In order to reproduce the bug, I simulate
> > the IPWireless PCMCIA card in the qemu by allocating and configuring the
> > necessary resources(I/O ports, memory regions, interrupts and so on) to
> > correspond with the hardware expected by the driver in the initialization
> > code of the virtual device.
>
> I wonder if this device even is still around, given that pcmcia is all
> but dead for a very long time.
>
> > > > One race condition scenario is as follows:
> > > >
> > > > CPU 0 (cleanup) | CPU 1 (interrupt)
> > > > ipwireless_hardware_free() | ipwireless_interrupt()
> > > > ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> > > > do_close_hardware() | tasklet_schedule()
> > > > synchronize_irq() |
> > > > kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> > > > | hw = from_tasklet() //USE
> > > > | hw-> //USE
> > > >
> > > > Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> > > > is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> > > > synchronize with any pending or running tasklet. Since do_close_hardware()
> > > > could prevent further interrupts, place tasklet_kill() after it to avoid
> > > > the tasklet being rescheduled by ipwireless_interrupt().
> > >
> > > How was this issue found and tested?
> >
> > The issue was found by static analysis. I test it through the following steps:
> > 1. Simulating the IPWireless PCMCIA device in the qemu and enable it to trigger interrupts.
> > 2. Controlling the removal and attachment of device via sysfs.
>
> So this is with the bind/unbind logic, or some other way? If you are
> unloading the driver, that is something that only root can do, and this
> is a debugging facility, not a "real" way to control drivers and devices
> (yes, the virt drivers abuse this to no end, every time I see this I
> laugh...)
When the PCMCIA device is attached, we can operate the file
/sys/bus/pcmcia/devices/.../remove to detach the device.
> > 3. Triggering interrupts by writing data to device registers via /dev/mem memory mapping
> > in userspace.
>
> Interrupts would not happen if the device is removed. Or is this only
> if the driver is unbound?
The interrupts should be triggered before the deivce is removed.
Best regards,
Duoming Zhou
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: Greg KH @ 2026-02-08 11:00 UTC (permalink / raw)
To: duoming
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <41b47e38.6ddb4.19c3ccb8e4d.Coremail.duoming@zju.edu.cn>
On Sun, Feb 08, 2026 at 06:28:19PM +0800, duoming@zju.edu.cn wrote:
> On Sun, 8 Feb 2026 07:38:00 +0100 Greg KH wrote:
> > > When IPWireless PCMCIA card is being detached, the ipw_hardware is
> > > deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> > > still be running or pending, leading to use-after-free bugs when the
> > > already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
> >
> > Nice, do you have this hardware to test this with?
>
> I don't have the real hardware. In order to reproduce the bug, I simulate
> the IPWireless PCMCIA card in the qemu by allocating and configuring the
> necessary resources(I/O ports, memory regions, interrupts and so on) to
> correspond with the hardware expected by the driver in the initialization
> code of the virtual device.
I wonder if this device even is still around, given that pcmcia is all
but dead for a very long time.
> > > One race condition scenario is as follows:
> > >
> > > CPU 0 (cleanup) | CPU 1 (interrupt)
> > > ipwireless_hardware_free() | ipwireless_interrupt()
> > > ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> > > do_close_hardware() | tasklet_schedule()
> > > synchronize_irq() |
> > > kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> > > | hw = from_tasklet() //USE
> > > | hw-> //USE
> > >
> > > Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> > > is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> > > synchronize with any pending or running tasklet. Since do_close_hardware()
> > > could prevent further interrupts, place tasklet_kill() after it to avoid
> > > the tasklet being rescheduled by ipwireless_interrupt().
> >
> > How was this issue found and tested?
>
> The issue was found by static analysis. I test it through the following steps:
> 1. Simulating the IPWireless PCMCIA device in the qemu and enable it to trigger interrupts.
> 2. Controlling the removal and attachment of device via sysfs.
So this is with the bind/unbind logic, or some other way? If you are
unloading the driver, that is something that only root can do, and this
is a debugging facility, not a "real" way to control drivers and devices
(yes, the virt drivers abuse this to no end, every time I see this I
laugh...)
> 3. Triggering interrupts by writing data to device registers via /dev/mem memory mapping
> in userspace.
Interrupts would not happen if the device is removed. Or is this only
if the driver is unbound?
> 4. In order to ensure that there are unfinished tasklet during the removal process, I
> manually inject delays such as mdelay() into tasklet handler.
That's a lot of work for a piece of obsolete hardware, but hey, thanks
for doing this!
> > > Fixes: 099dc4fb6265 ("ipwireless: driver for PC Card 3G/UMTS modem")
> > > Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
> >
> > No CC: stable? Why not?
>
> Thanks for checking, You are right, it should go to the stable.
Let's see what the maintainers of this driver say.
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: duoming @ 2026-02-08 10:28 UTC (permalink / raw)
To: Greg KH
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <2026020828-unretired-mannish-8465@gregkh>
On Sun, 8 Feb 2026 07:38:00 +0100 Greg KH wrote:
> > When IPWireless PCMCIA card is being detached, the ipw_hardware is
> > deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> > still be running or pending, leading to use-after-free bugs when the
> > already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
>
> Nice, do you have this hardware to test this with?
I don't have the real hardware. In order to reproduce the bug, I simulate
the IPWireless PCMCIA card in the qemu by allocating and configuring the
necessary resources(I/O ports, memory regions, interrupts and so on) to
correspond with the hardware expected by the driver in the initialization
code of the virtual device.
> >
> > One race condition scenario is as follows:
> >
> > CPU 0 (cleanup) | CPU 1 (interrupt)
> > ipwireless_hardware_free() | ipwireless_interrupt()
> > ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> > do_close_hardware() | tasklet_schedule()
> > synchronize_irq() |
> > kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> > | hw = from_tasklet() //USE
> > | hw-> //USE
> >
> > Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> > is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> > synchronize with any pending or running tasklet. Since do_close_hardware()
> > could prevent further interrupts, place tasklet_kill() after it to avoid
> > the tasklet being rescheduled by ipwireless_interrupt().
>
> How was this issue found and tested?
The issue was found by static analysis. I test it through the following steps:
1. Simulating the IPWireless PCMCIA device in the qemu and enable it to trigger interrupts.
2. Controlling the removal and attachment of device via sysfs.
3. Triggering interrupts by writing data to device registers via /dev/mem memory mapping
in userspace.
4. In order to ensure that there are unfinished tasklet during the removal process, I
manually inject delays such as mdelay() into tasklet handler.
> > Fixes: 099dc4fb6265 ("ipwireless: driver for PC Card 3G/UMTS modem")
> > Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
>
> No CC: stable? Why not?
Thanks for checking, You are right, it should go to the stable.
Best regards,
Duoming Zhou
^ permalink raw reply
* Re: [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: Greg KH @ 2026-02-08 6:38 UTC (permalink / raw)
To: Duoming Zhou
Cc: linux-serial, linux-kernel, jikos, dsterba, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo
In-Reply-To: <20260208062538.29608-1-duoming@zju.edu.cn>
On Sun, Feb 08, 2026 at 02:25:38PM +0800, Duoming Zhou wrote:
> When IPWireless PCMCIA card is being detached, the ipw_hardware is
> deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
> still be running or pending, leading to use-after-free bugs when the
> already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
Nice, do you have this hardware to test this with?
>
> One race condition scenario is as follows:
>
> CPU 0 (cleanup) | CPU 1 (interrupt)
> ipwireless_hardware_free() | ipwireless_interrupt()
> ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
> do_close_hardware() | tasklet_schedule()
> synchronize_irq() |
> kfree(hw) //FREE | ipwireless_do_tasklet() //handler
> | hw = from_tasklet() //USE
> | hw-> //USE
>
> Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
> is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
> synchronize with any pending or running tasklet. Since do_close_hardware()
> could prevent further interrupts, place tasklet_kill() after it to avoid
> the tasklet being rescheduled by ipwireless_interrupt().
How was this issue found and tested?
> Fixes: 099dc4fb6265 ("ipwireless: driver for PC Card 3G/UMTS modem")
> Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
No CC: stable? Why not?
thanks,
greg k-h
^ permalink raw reply
* [PATCH] tty: ipwireless: Fix use-after-free in tasklet during device removal
From: Duoming Zhou @ 2026-02-08 6:25 UTC (permalink / raw)
To: linux-serial
Cc: linux-kernel, jikos, dsterba, gregkh, jirislaby, kuba,
alexander.deucher, akpm, pkshih, tglx, mingo, Duoming Zhou
When IPWireless PCMCIA card is being detached, the ipw_hardware is
deallocated in ipwireless_hardware_free(). However, the hw->tasklet may
still be running or pending, leading to use-after-free bugs when the
already freed ipw_hardware is accessed again in ipwireless_do_tasklet().
One race condition scenario is as follows:
CPU 0 (cleanup) | CPU 1 (interrupt)
ipwireless_hardware_free() | ipwireless_interrupt()
ipwireless_stop_interrupts()| ipwireless_handle_v1_interrupt()
do_close_hardware() | tasklet_schedule()
synchronize_irq() |
kfree(hw) //FREE | ipwireless_do_tasklet() //handler
| hw = from_tasklet() //USE
| hw-> //USE
Fix this by ensuring hw->tasklet is properly canceled before ipw_hardware
is released. Add tasklet_kill() in ipwireless_stop_interrupts() to
synchronize with any pending or running tasklet. Since do_close_hardware()
could prevent further interrupts, place tasklet_kill() after it to avoid
the tasklet being rescheduled by ipwireless_interrupt().
Fixes: 099dc4fb6265 ("ipwireless: driver for PC Card 3G/UMTS modem")
Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
---
drivers/tty/ipwireless/hardware.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/tty/ipwireless/hardware.c b/drivers/tty/ipwireless/hardware.c
index e18848267be..c736cba751f 100644
--- a/drivers/tty/ipwireless/hardware.c
+++ b/drivers/tty/ipwireless/hardware.c
@@ -1725,6 +1725,7 @@ void ipwireless_stop_interrupts(struct ipw_hardware *hw)
/* Prevent the hardware from sending any more interrupts */
do_close_hardware(hw);
+ tasklet_kill(&hw->tasklet);
}
}
--
2.34.1
^ permalink raw reply related
* [PATCH] vt: keyboard: add NULL check for vc_cons[fg_console].d in kbd_keycode and kbd_rawcode
From: Daniel Hodges @ 2026-02-08 0:31 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby
Cc: linux-kernel, linux-serial, Daniel Hodges,
syzbot+c3693b491545af43db87, syzbot+03f79366754268a0f20c
kbd_keycode() and kbd_rawcode() dereference vc_cons[fg_console].d
without checking if it is NULL. The foreground console should normally
always be allocated, but there could be a time during console setup or
teardown where this pointer could be NULL, leading to a general
protection fault.
Syzkaller triggers this by injecting USB HID input events that reach
kbd_event() while the console state may not be fully consistent. The crash
manifests as a null-ptr-deref in __queue_work when put_queue() or
puts_queue() calls tty_flip_buffer_push() on the uninitialized vc port.
Add a NULL check for vc at the start of both kbd_rawcode() and
kbd_keycode() to bail out early if the foreground console is not allocated.
Reported-by: syzbot+c3693b491545af43db87@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=c3693b491545af43db87
Reported-by: syzbot+03f79366754268a0f20c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=03f79366754268a0f20c
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Daniel Hodges <git@danielhodges.dev>
---
drivers/tty/vt/keyboard.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index a2116e135a82..975830013d24 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -1389,6 +1389,9 @@ static void kbd_rawcode(unsigned char data)
{
struct vc_data *vc = vc_cons[fg_console].d;
+ if (!vc)
+ return;
+
kbd = &kbd_table[vc->vc_num];
if (kbd->kbdmode == VC_RAW)
put_queue(vc, data);
@@ -1405,6 +1408,9 @@ static void kbd_keycode(unsigned int keycode, int down, bool hw_raw)
struct keyboard_notifier_param param = { .vc = vc, .value = keycode, .down = down };
int rc;
+ if (!vc)
+ return;
+
tty = vc->port.tty;
if (tty && (!tty->driver_data)) {
--
2.52.0
^ permalink raw reply related
* [PATCH] tty: vt: keyboard: fix general protection fault in k_meta
From: Soham Kute @ 2026-02-07 19:27 UTC (permalink / raw)
To: gregkh, jirislaby
Cc: linux-serial, linux-kernel, Soham Kute,
syzbot+03f79366754268a0f20c
syzbot reported a general protection fault in k_meta() caused by
dereferencing an invalid keyboard pointer when checking META mode.
Add a defensive check to ensure the keyboard pointer is valid before
calling vc_kbd_mode().
Reported-by: syzbot+03f79366754268a0f20c@syzkaller.appspotmail.com
Signed-off-by: Soham Kute <officialsohamkute@gmail.com>
---
drivers/tty/vt/keyboard.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index d65fc60dd..b535d7a42 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -880,7 +880,7 @@ static void k_meta(struct vc_data *vc, unsigned char value, char up_flag)
if (up_flag)
return;
- if (vc_kbd_mode(kbd, VC_META)) {
+ if (kbd && vc_kbd_mode(kbd, VC_META)) {
put_queue(vc, '\033');
put_queue(vc, value);
} else
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2] dt-bindings: serial: snps-dw-apb-uart: Add RV1103B compatible
From: Krzysztof Kozlowski @ 2026-02-07 18:37 UTC (permalink / raw)
To: Fabio Estevam, gregkh
Cc: robh, krzk+dt, conor+dt, linux-serial, devicetree, Fabio Estevam
In-Reply-To: <20260207130848.2833599-1-festevam@gmail.com>
On 07/02/2026 14:08, Fabio Estevam wrote:
> From: Fabio Estevam <festevam@nabladev.com>
>
> The RV1103B UART is compatible with the existing DesignWare APB UART
> binding. Add the rockchip,rv1103b-uart compatible string.
>
> Signed-off-by: Fabio Estevam <festevam@nabladev.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH v2] dt-bindings: serial: snps-dw-apb-uart: Add RV1103B compatible
From: Fabio Estevam @ 2026-02-07 13:08 UTC (permalink / raw)
To: gregkh; +Cc: robh, krzk+dt, conor+dt, linux-serial, devicetree, Fabio Estevam
From: Fabio Estevam <festevam@nabladev.com>
The RV1103B UART is compatible with the existing DesignWare APB UART
binding. Add the rockchip,rv1103b-uart compatible string.
Signed-off-by: Fabio Estevam <festevam@nabladev.com>
---
Changes since v1:
- Make commit log more concise.
Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
index 6efe43089a74..685c1eceb782 100644
--- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
@@ -71,6 +71,7 @@ properties:
- rockchip,rk3568-uart
- rockchip,rk3576-uart
- rockchip,rk3588-uart
+ - rockchip,rv1103b-uart
- rockchip,rv1108-uart
- rockchip,rv1126-uart
- sophgo,sg2044-uart
--
2.34.1
^ permalink raw reply related
* Re: [PATCH 07/11] dt-bindings: serial: snps-dw-apb-uart: Add support for RV1103B
From: Greg Kroah-Hartman @ 2026-02-07 11:09 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Fabio Estevam, heiko, robh, krzk+dt, conor+dt, devicetree,
linux-arm-kernel, linux-rockchip, Fabio Estevam, linux-serial
In-Reply-To: <20260207-unselfish-gorgeous-duck-cfc1ce@quoll>
On Sat, Feb 07, 2026 at 11:59:06AM +0100, Krzysztof Kozlowski wrote:
> On Fri, Feb 06, 2026 at 03:13:05PM -0300, Fabio Estevam wrote:
> > From: Fabio Estevam <festevam@nabladev.com>
> >
> > The UART used in the RV1103B SoC is still the same dw-apb-uart compatible
> > type as on the SoCs that came before, so add the RV1103B to the list
> > of variants.
> >
> > Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> > Cc: linux-serial@vger.kernel.org
> > Signed-off-by: Fabio Estevam <festevam@nabladev.com>
>
> Greg expressed MANY times that patches for him should be sent
> separately.
It's easy, I just ignore them now :)
^ permalink raw reply
* Re: [PATCH 07/11] dt-bindings: serial: snps-dw-apb-uart: Add support for RV1103B
From: Krzysztof Kozlowski @ 2026-02-07 10:59 UTC (permalink / raw)
To: Fabio Estevam
Cc: heiko, robh, krzk+dt, conor+dt, devicetree, linux-arm-kernel,
linux-rockchip, Fabio Estevam, Greg Kroah-Hartman, linux-serial
In-Reply-To: <20260206181309.2696095-8-festevam@gmail.com>
On Fri, Feb 06, 2026 at 03:13:05PM -0300, Fabio Estevam wrote:
> From: Fabio Estevam <festevam@nabladev.com>
>
> The UART used in the RV1103B SoC is still the same dw-apb-uart compatible
> type as on the SoCs that came before, so add the RV1103B to the list
> of variants.
>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: linux-serial@vger.kernel.org
> Signed-off-by: Fabio Estevam <festevam@nabladev.com>
Greg expressed MANY times that patches for him should be sent
separately.
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH 07/11] dt-bindings: serial: snps-dw-apb-uart: Add support for RV1103B
From: Fabio Estevam @ 2026-02-06 18:13 UTC (permalink / raw)
To: heiko
Cc: robh, krzk+dt, conor+dt, devicetree, linux-arm-kernel,
linux-rockchip, Fabio Estevam, Greg Kroah-Hartman, linux-serial
In-Reply-To: <20260206181309.2696095-1-festevam@gmail.com>
From: Fabio Estevam <festevam@nabladev.com>
The UART used in the RV1103B SoC is still the same dw-apb-uart compatible
type as on the SoCs that came before, so add the RV1103B to the list
of variants.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-serial@vger.kernel.org
Signed-off-by: Fabio Estevam <festevam@nabladev.com>
---
Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
index 6efe43089a74..685c1eceb782 100644
--- a/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
+++ b/Documentation/devicetree/bindings/serial/snps-dw-apb-uart.yaml
@@ -71,6 +71,7 @@ properties:
- rockchip,rk3568-uart
- rockchip,rk3576-uart
- rockchip,rk3588-uart
+ - rockchip,rv1103b-uart
- rockchip,rv1108-uart
- rockchip,rv1126-uart
- sophgo,sg2044-uart
--
2.34.1
^ permalink raw reply related
* [PATCH] tty: constify tty_ldisc_ops
From: Qingfang Deng @ 2026-02-06 6:20 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, linux-kernel, linux-serial
tty_ldisc_ops is not modified once registered, so make it const.
Signed-off-by: Qingfang Deng <dqfext@gmail.com>
---
drivers/tty/tty_ldisc.c | 16 ++++++++--------
include/linux/tty_ldisc.h | 6 +++---
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c
index d80e9d4c974b..470a47aca03e 100644
--- a/drivers/tty/tty_ldisc.c
+++ b/drivers/tty/tty_ldisc.c
@@ -44,7 +44,7 @@ enum {
static DEFINE_RAW_SPINLOCK(tty_ldiscs_lock);
/* Line disc dispatch table */
-static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
+static const struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
/**
* tty_register_ldisc - install a line discipline
@@ -55,7 +55,7 @@ static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];
*
* Locking: takes %tty_ldiscs_lock to guard against ldisc races
*/
-int tty_register_ldisc(struct tty_ldisc_ops *new_ldisc)
+int tty_register_ldisc(const struct tty_ldisc_ops *new_ldisc)
{
unsigned long flags;
@@ -80,7 +80,7 @@ EXPORT_SYMBOL(tty_register_ldisc);
* Locking: takes %tty_ldiscs_lock to guard against ldisc races
*/
-void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc)
+void tty_unregister_ldisc(const struct tty_ldisc_ops *ldisc)
{
unsigned long flags;
@@ -90,10 +90,10 @@ void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc)
}
EXPORT_SYMBOL(tty_unregister_ldisc);
-static struct tty_ldisc_ops *get_ldops(int disc)
+static const struct tty_ldisc_ops *get_ldops(int disc)
{
unsigned long flags;
- struct tty_ldisc_ops *ldops, *ret;
+ const struct tty_ldisc_ops *ldops, *ret;
raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
ret = ERR_PTR(-EINVAL);
@@ -107,7 +107,7 @@ static struct tty_ldisc_ops *get_ldops(int disc)
return ret;
}
-static void put_ldops(struct tty_ldisc_ops *ldops)
+static void put_ldops(const struct tty_ldisc_ops *ldops)
{
unsigned long flags;
@@ -139,7 +139,7 @@ int tty_ldisc_autoload = IS_BUILTIN(CONFIG_LDISC_AUTOLOAD);
static struct tty_ldisc *tty_ldisc_get(struct tty_struct *tty, int disc)
{
struct tty_ldisc *ld;
- struct tty_ldisc_ops *ldops;
+ const struct tty_ldisc_ops *ldops;
if (disc < N_TTY || disc >= NR_LDISCS)
return ERR_PTR(-EINVAL);
@@ -202,7 +202,7 @@ static void tty_ldiscs_seq_stop(struct seq_file *m, void *v)
static int tty_ldiscs_seq_show(struct seq_file *m, void *v)
{
int i = *(loff_t *)v;
- struct tty_ldisc_ops *ldops;
+ const struct tty_ldisc_ops *ldops;
ldops = get_ldops(i);
if (IS_ERR(ldops))
diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h
index c5cccc3fc1e8..d227a58e3e49 100644
--- a/include/linux/tty_ldisc.h
+++ b/include/linux/tty_ldisc.h
@@ -266,7 +266,7 @@ struct tty_ldisc_ops {
};
struct tty_ldisc {
- struct tty_ldisc_ops *ops;
+ const struct tty_ldisc_ops *ops;
struct tty_struct *tty;
};
@@ -281,8 +281,8 @@ struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *);
void tty_ldisc_flush(struct tty_struct *tty);
-int tty_register_ldisc(struct tty_ldisc_ops *new_ldisc);
-void tty_unregister_ldisc(struct tty_ldisc_ops *ldisc);
+int tty_register_ldisc(const struct tty_ldisc_ops *new_ldisc);
+void tty_unregister_ldisc(const struct tty_ldisc_ops *ldisc);
int tty_set_ldisc(struct tty_struct *tty, int disc);
#endif /* _LINUX_TTY_LDISC_H */
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v9] tty: tty_port: add workqueue to flip TTY buffer
From: Tommaso Merciai @ 2026-02-05 15:42 UTC (permalink / raw)
To: Xin Zhao
Cc: gregkh, jirislaby, m.szyprowski, geert, hch, tj, linux-kernel,
linux-serial
In-Reply-To: <20260205101948.594577-1-jackzxcui1989@163.com>
On Thu, Feb 05, 2026 at 06:19:48PM +0800, Xin Zhao wrote:
> 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.
>
Tested on Renesas RZ/G3E.
Tested-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Signed-off-by: Xin Zhao <jackzxcui1989@163.com>
> ---
Thanks & Regards,
Tommaso
>
> 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
* Re: [PATCH v9] tty: tty_port: add workqueue to flip TTY buffer
From: Marek Szyprowski @ 2026-02-05 15:15 UTC (permalink / raw)
To: Xin Zhao, gregkh, jirislaby, tommaso.merciai.xr, geert
Cc: hch, tj, linux-kernel, linux-serial
In-Reply-To: <20260205101948.594577-1-jackzxcui1989@163.com>
On 05.02.2026 11:19, Xin Zhao wrote:
> 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>
Works fine on my test boards.
Tested-by: Marek Szyprowski <m.szyprowski@samsung.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)
> {
Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland
^ permalink raw reply
* Re: [PATCH 2/3] dt-bindings: serial: amlogic,meson-uart: Add compatible string for A9
From: Krzysztof Kozlowski @ 2026-02-05 13:48 UTC (permalink / raw)
To: Xianwei Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
Greg Kroah-Hartman, Jiri Slaby, devicetree, linux-kernel,
linux-serial, linux-arm-kernel, linux-amlogic
In-Reply-To: <20260205-a9-baisc-dts-v1-2-1212b46f95a7@amlogic.com>
On Thu, Feb 05, 2026 at 06:04:03AM +0000, Xianwei Zhao wrote:
> 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(+)
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 1/3] dt-bindings: arm: amlogic: add A311Y3 support
From: Krzysztof Kozlowski @ 2026-02-05 13:48 UTC (permalink / raw)
To: Xianwei Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
Greg Kroah-Hartman, Jiri Slaby, devicetree, linux-kernel,
linux-serial, linux-arm-kernel, linux-amlogic
In-Reply-To: <20260205-a9-baisc-dts-v1-1-1212b46f95a7@amlogic.com>
On Thu, Feb 05, 2026 at 06:04:02AM +0000, Xianwei Zhao wrote:
> 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(+)
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 2/3] dt-bindings: serial: amlogic,meson-uart: Add compatible string for A9
From: Krzysztof Kozlowski @ 2026-02-05 13:47 UTC (permalink / raw)
To: Xianwei Zhao
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Neil Armstrong,
Martin Blumenstingl, Jerome Brunet, Kevin Hilman,
Greg Kroah-Hartman, Jiri Slaby, devicetree, linux-kernel,
linux-serial, linux-arm-kernel, linux-amlogic
In-Reply-To: <20260205-a9-baisc-dts-v1-2-1212b46f95a7@amlogic.com>
On Thu, Feb 05, 2026 at 06:04:03AM +0000, Xianwei Zhao wrote:
> 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 +
Don't combine independent subsystems which prefer to receive separate
patchsets. There is no reason why this serial patch should be included here...
Best regards,
Krzysztof
^ permalink raw reply
* [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
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox