From: John Ogness <john.ogness@linutronix.de>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jiri Slaby <jirislaby@kernel.org>
Cc: linux-kernel@vger.kernel.org,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Thomas Gleixner" <tglx@kernel.org>,
"Ingo Molnar" <mingo@kernel.org>,
"Xin Zhao" <jackzxcui1989@163.com>,
"Andy Shevchenko" <andy.shevchenko@gmail.com>,
"Dr. David Alan Gilbert" <linux@treblig.org>,
"Joseph Tilahun" <jtilahun@astranis.com>,
linux-serial@vger.kernel.org
Subject: Re: [PATCH tty v1] serial: 8250: Add support for console hardware flow control
Date: Wed, 01 Apr 2026 01:56:16 +0206 [thread overview]
Message-ID: <87pl4jtv7r.fsf@jogness.linutronix.de> (raw)
In-Reply-To: <20260331141502.6233-1-john.ogness@linutronix.de>
Hi,
Responding to Sashiko:
https://sashiko.dev/#/patchset/20260331141502.6233-1-john.ogness%40linutronix.de
On 2026-03-31, Sashiko wrote:
> This commit addresses issues with hardware flow control on the 8250 console.
> It modifies the driver and serial core to properly initialize CTS polling
> and handle the 'r' console option.
>
>> diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
>> index d2e2c5dfef99..4ca7cac6b8a7 100644
>> --- a/drivers/tty/serial/8250/8250_core.c
>> +++ b/drivers/tty/serial/8250/8250_core.c
>> @@ -716,6 +717,9 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
>> if (uart->port.type == PORT_8250_CIR)
>> return -ENODEV;
>>
>> + /* Preserve specified console hardware flow control. */
>> + console_hwflow = uart->port.flags & UPF_CONS_FLOW;
>> +
>> if (uart->port.dev)
>> uart_remove_one_port(&serial8250_reg, &uart->port);
>>
>> @@ -757,6 +761,9 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
>> goto err;
>> }
>>
>> + if (console_hwflow)
>> + uart->port.flags |= UPF_CONS_FLOW;
>> +
>
> Can this create a race window where console flow control is temporarily
> disabled? The UPF_CONS_FLOW flag is saved, but uart->port.flags is
> overwritten earlier in this function. If an asynchronous printk (such as
> from an NMI) triggers serial8250_console_write() before the flag is
> restored, it seems uart_console_hwflow_active() will evaluate to false,
> bypassing CTS polling and potentially dropping characters.
Correct. I missed that serial8250_register_8250_port() is called after
the console is already registered via univ8250_console_init(). I can
remove the window with something like the following alternative change
to serial8250_register_8250_port():
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index a428e88938eb7..ff4c9972d4576 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -693,6 +693,7 @@ static void serial_8250_overrun_backoff_work(struct work_struct *work)
int serial8250_register_8250_port(const struct uart_8250_port *up)
{
struct uart_8250_port *uart;
+ upf_t console_hwflow;
int ret;
if (up->port.uartclk == 0)
@@ -716,6 +717,9 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
if (uart->port.type == PORT_8250_CIR)
return -ENODEV;
+ /* Preserve specified console hardware flow control. */
+ console_hwflow = uart->port.flags & UPF_CONS_FLOW;
+
if (uart->port.dev)
uart_remove_one_port(&serial8250_reg, &uart->port);
@@ -729,7 +733,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
uart->port.fifosize = up->port.fifosize;
uart->port.regshift = up->port.regshift;
uart->port.iotype = up->port.iotype;
- uart->port.flags = up->port.flags | UPF_BOOT_AUTOCONF;
+ uart->port.flags = up->port.flags | UPF_BOOT_AUTOCONF | console_hwflow;
uart->bugs = up->bugs;
uart->port.mapbase = up->port.mapbase;
uart->port.mapsize = up->port.mapsize;
>> diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
>> index 328711b5df1a..71edac12473e 100644
>> --- a/drivers/tty/serial/8250/8250_port.c
>> +++ b/drivers/tty/serial/8250/8250_port.c
>> @@ -1988,7 +1988,7 @@ static void wait_for_xmitr(struct uart_8250_port *up, int bits)
>> wait_for_lsr(up, bits);
>>
>> /* Wait up to 1s for flow control if necessary */
>> - if (up->port.flags & UPF_CONS_FLOW) {
>> + if (uart_console_hwflow_active(&up->port)) {
>> for (tmout = 1000000; tmout; tmout--) {
>> unsigned int msr = serial_in(up, UART_MSR);
>> up->msr_saved_flags |= msr & MSR_SAVE_FLAGS;
>> if (msr & UART_MSR_CTS)
>> break;
>> udelay(1);
>> touch_nmi_watchdog();
>> }
>> }
>> }
>
> Does this sequence of checks cause data loss when CTS is deasserted?
> If wait_for_lsr() times out because the UART halts transmission, the holding
> register remains full. When CTS is subsequently asserted, the loop breaks
> and the function returns immediately without waiting for the holding register
> to actually empty. Will the next character overwrite the current one before it
> can be shifted out?
Correct. My patch does not address errors in the CTS polling, but
perhaps it should (since I am activating CTS polling). In this case I
suppose one final wait_for_lsr() is appropriate. Something like:
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 88f5309a940e9..18bc5600df181 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -1984,16 +1984,20 @@ static bool wait_for_lsr(struct uart_8250_port *up, int bits)
static void wait_for_xmitr(struct uart_8250_port *up, int bits)
{
unsigned int tmout;
+ bool lsr_timeout;
- wait_for_lsr(up, bits);
+ lsr_timeout = wait_for_lsr(up, bits);
/* Wait up to 1s for flow control if necessary */
if (uart_console_hwflow_active(&up->port)) {
for (tmout = 1000000; tmout; tmout--) {
unsigned int msr = serial_in(up, UART_MSR);
up->msr_saved_flags |= msr & MSR_SAVE_FLAGS;
- if (msr & UART_MSR_CTS)
+ if (msr & UART_MSR_CTS) {
+ if (lsr_timeout)
+ wait_for_lsr(up, bits);
break;
+ }
udelay(1);
touch_nmi_watchdog();
}
> Also, could this polling loop cause a system stall?
> If the remote peer deasserts CTS (for example, if a terminal is disconnected),
> wait_for_xmitr() will poll for 1,000,000 microseconds. Since this is called
> for every single character during a printk, a standard 50-character printk
> could potentially stall the entire system for 50 seconds while holding the
> console lock.
Yes, this is a problem (for !PREEMPT_RT). But that is how console
hardware flow control is implemented. It is out of scope of my patch to
completely re-implement the CTS polling mechanism. AFAICT the other
serial drivers that already support console hardware flow control have
the same problem. Does this need to be resolved as a pre-requisite to my
patch?
>> @@ -3421,6 +3427,10 @@ int serial8250_console_setup(struct uart_port *port, char *options, bool probe)
>> if (ret)
>> return ret;
>>
>> + /* Allow user-specified hardware flow control. */
>> + if (flow == 'r')
>> + port->flags |= UPF_CONS_FLOW;
>> +
>> if (port->dev)
>> pm_runtime_get_sync(port->dev);
>
> Should this flag be cleared if flow != 'r'? By only setting the flag, it
> appears hardware flow control cannot be dynamically disabled once it has
> been enabled.
The flag comes from a boot argument. Boot arguments never change during
runtime. The hardware flow control can still by dynamically disabled by
disabling CRTSCTS via termios. For example:
# stty -F /dev/ttyS0 -crtscts
>> diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
>> index 89cebdd27841..cce1fd728a1e 100644
>> --- a/drivers/tty/serial/serial_core.c
>> +++ b/drivers/tty/serial/serial_core.c
>> @@ -2235,6 +2235,15 @@ uart_set_options(struct uart_port *port, struct console *co,
>> port->mctrl |= TIOCM_DTR;
>>
>> port->ops->set_termios(port, &termios, &dummy);
>> +
>> + /*
>> + * If console hardware flow control was specified and is supported,
>> + * the related policy UPSTAT_CTS_ENABLE must be set to allow console
>> + * drivers to identify if CTS should be used for polling.
>> + */
>> + if (flow == 'r' && (termios.c_cflag & CRTSCTS))
>> + port->status |= UPSTAT_CTS_ENABLE;
>> +
>
> Similar to the previous question, should UPSTAT_CTS_ENABLE be cleared
> if flow != 'r' to prevent the state from leaking?
This is a valid question. My patch is trying to be maximally
conservative and only make changes to allow CTS polling to become active
when 'r' is specified. For all other cases I do not want behavior
changes. Particularly since this is a function within serial_core.
> Additionally, does port->status need to be protected by the port lock here?
> Since this is a read-modify-write operation, and uart_set_options() can
> be invoked dynamically (like from kgdboc via tty_find_polling_driver()),
> could this race with concurrent modifications to port->status?
Yes. I was only considering the boot case where the tty layer is not yet
active. :-/ It seems some drivers are only taking the port lock for
@status updates.
The comments in serial_core.h say "Must hold termios_rwsem, port mutex
and port lock to change" but AFAICT the main @status modifiers are only
taking the last 2 locks. Still, the port mutex cannot be taken in
uart_set_options().
I would really like to leverage the UPSTAT_CTS_ENABLE policy bit. I will
need to think about this some more...
John Ogness
prev parent reply other threads:[~2026-03-31 23:50 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-31 14:14 [PATCH tty v1] serial: 8250: Add support for console hardware flow control John Ogness
2026-03-31 23:50 ` John Ogness [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87pl4jtv7r.fsf@jogness.linutronix.de \
--to=john.ogness@linutronix.de \
--cc=andy.shevchenko@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=jackzxcui1989@163.com \
--cc=jirislaby@kernel.org \
--cc=jtilahun@astranis.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-serial@vger.kernel.org \
--cc=linux@treblig.org \
--cc=mingo@kernel.org \
--cc=tglx@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox