diff mbox series

[3/6] serial: core: Add new prep_tx for power management

Message ID 20210921103346.64824-4-tony@atomide.com (mailing list archive)
State New, archived
Headers show
Series Get rid of pm_runtime_irq_safe() for 8250_omap | expand

Commit Message

Tony Lindgren Sept. 21, 2021, 10:33 a.m. UTC
If the serial driver implements PM runtime with autosuspend, the port may
be powered off for TX. To wake up the port, let's add new prep_tx() call
for serial drivers to implement as needed. We call it from serial
write_room() and write() functions. If the serial port is not enabled,
we just return 0.

Signed-off-by: Tony Lindgren <tony@atomide.com>
---
 Documentation/driver-api/serial/driver.rst |  9 +++++++++
 drivers/tty/serial/serial_core.c           | 23 ++++++++++++++++++++++
 include/linux/serial_core.h                |  1 +
 3 files changed, 33 insertions(+)

Comments

Johan Hovold Sept. 23, 2021, 12:45 p.m. UTC | #1
On Tue, Sep 21, 2021 at 01:33:43PM +0300, Tony Lindgren wrote:
> If the serial driver implements PM runtime with autosuspend, the port may
> be powered off for TX. To wake up the port, let's add new prep_tx() call
> for serial drivers to implement as needed. We call it from serial
> write_room() and write() functions. If the serial port is not enabled,
> we just return 0.

This isn't right. If there's room in the driver buffer, there's no
reason to not accept those characters.

It's the drivers responsibility to resume writing when write() is
called and that me need to be done in a runtime resume callback in case
the device is suspended.

No need to be patching line disciplines for this.

Johan
Tony Lindgren Sept. 23, 2021, 3:02 p.m. UTC | #2
* Johan Hovold <johan@kernel.org> [210923 12:46]:
> On Tue, Sep 21, 2021 at 01:33:43PM +0300, Tony Lindgren wrote:
> > If the serial driver implements PM runtime with autosuspend, the port may
> > be powered off for TX. To wake up the port, let's add new prep_tx() call
> > for serial drivers to implement as needed. We call it from serial
> > write_room() and write() functions. If the serial port is not enabled,
> > we just return 0.
> 
> This isn't right. If there's room in the driver buffer, there's no
> reason to not accept those characters.

Maybe. We might get away with returning zero bytes written in write().
But to me it seems better to stop things early when write is known
to not succeed.

> It's the drivers responsibility to resume writing when write() is
> called and that me need to be done in a runtime resume callback in case
> the device is suspended.

I think we currently need to return zero bytes written from write()
when the serial port is not usable.

I don't think we can return a fake number of bytes written from write().

And even if we could return a fake number of bytes written, we could
run into issues doing the real write to the serial port.

> No need to be patching line disciplines for this.

Do you see issues with handling the errors in line disciplines?

Regards,

Tony
Johan Hovold Sept. 24, 2021, 2:37 p.m. UTC | #3
On Thu, Sep 23, 2021 at 06:02:27PM +0300, Tony Lindgren wrote:
> * Johan Hovold <johan@kernel.org> [210923 12:46]:
> > On Tue, Sep 21, 2021 at 01:33:43PM +0300, Tony Lindgren wrote:
> > > If the serial driver implements PM runtime with autosuspend, the port may
> > > be powered off for TX. To wake up the port, let's add new prep_tx() call
> > > for serial drivers to implement as needed. We call it from serial
> > > write_room() and write() functions. If the serial port is not enabled,
> > > we just return 0.
> > 
> > This isn't right. If there's room in the driver buffer, there's no
> > reason to not accept those characters.
> 
> Maybe. We might get away with returning zero bytes written in write().
> But to me it seems better to stop things early when write is known
> to not succeed.

But you shouldn't return zero from write() either. If there's room in
the write buffer we accept the data.
 
> > It's the drivers responsibility to resume writing when write() is
> > called and that me need to be done in a runtime resume callback in case
> > the device is suspended.
> 
> I think we currently need to return zero bytes written from write()
> when the serial port is not usable.
> 
> I don't think we can return a fake number of bytes written from write().

It's not a fake number. It's similar to if you have a port that is
stalled due to flow control. We buffer the data and continue writing
when the other end is ready to accept more.

> And even if we could return a fake number of bytes written, we could
> run into issues doing the real write to the serial port.
>
> > No need to be patching line disciplines for this.
> 
> Do you see issues with handling the errors in line disciplines?

It's just conceptually wrong to push retrying up the stack, possible all
the way to user space in case of non-blocking opens, just because the
device isn't already runtime active.

Johan
Tony Lindgren Sept. 24, 2021, 3:09 p.m. UTC | #4
* Johan Hovold <johan@kernel.org> [210924 14:38]:
> On Thu, Sep 23, 2021 at 06:02:27PM +0300, Tony Lindgren wrote:
> > * Johan Hovold <johan@kernel.org> [210923 12:46]:
> > > On Tue, Sep 21, 2021 at 01:33:43PM +0300, Tony Lindgren wrote:
> > > > If the serial driver implements PM runtime with autosuspend, the port may
> > > > be powered off for TX. To wake up the port, let's add new prep_tx() call
> > > > for serial drivers to implement as needed. We call it from serial
> > > > write_room() and write() functions. If the serial port is not enabled,
> > > > we just return 0.
> > > 
> > > This isn't right. If there's room in the driver buffer, there's no
> > > reason to not accept those characters.
> > 
> > Maybe. We might get away with returning zero bytes written in write().
> > But to me it seems better to stop things early when write is known
> > to not succeed.
> 
> But you shouldn't return zero from write() either. If there's room in
> the write buffer we accept the data.

And then waking up the serial port takes several tens of ms and the
buffer is full and we still need to deal with it :) But yeah I see
your point for the write buffer.

> > > It's the drivers responsibility to resume writing when write() is
> > > called and that me need to be done in a runtime resume callback in case
> > > the device is suspended.
> > 
> > I think we currently need to return zero bytes written from write()
> > when the serial port is not usable.
> > 
> > I don't think we can return a fake number of bytes written from write().
> 
> It's not a fake number. It's similar to if you have a port that is
> stalled due to flow control. We buffer the data and continue writing
> when the other end is ready to accept more.

OK. So based on what you suggested earlier I'll take a look at moving
the wake-up to __uart_start(), then have the device driver runtime PM
resume call uart_start() again. Looks like uart_start() is a void
function anyways.. If you have some better ideas there, please let me
know.

> > > No need to be patching line disciplines for this.
> > 
> > Do you see issues with handling the errors in line disciplines?
> 
> It's just conceptually wrong to push retrying up the stack, possible all
> the way to user space in case of non-blocking opens, just because the
> device isn't already runtime active.

Yes, I don't see a way around that currently. Maybe if we start making
use of uart_tx_stopped() or something similar that could be simplified.
And we'll be still hit these line discipline error handling cases
anyways depending on how long the serial port wake up takes.

Regards,

Tony
Johan Hovold Sept. 27, 2021, 2:05 p.m. UTC | #5
On Fri, Sep 24, 2021 at 06:09:18PM +0300, Tony Lindgren wrote:
> * Johan Hovold <johan@kernel.org> [210924 14:38]:
> > On Thu, Sep 23, 2021 at 06:02:27PM +0300, Tony Lindgren wrote:

> > > > No need to be patching line disciplines for this.
> > > 
> > > Do you see issues with handling the errors in line disciplines?
> > 
> > It's just conceptually wrong to push retrying up the stack, possible all
> > the way to user space in case of non-blocking opens, just because the
> > device isn't already runtime active.
> 
> Yes, I don't see a way around that currently. Maybe if we start making
> use of uart_tx_stopped() or something similar that could be simplified.
> And we'll be still hit these line discipline error handling cases
> anyways depending on how long the serial port wake up takes.

I didn't really look at the ldisc change so not saying it isn't needed
for other reasons such as a full write buffer. But then I'd expect it to
be presented as a bug fix (perhaps it was).

Johan
diff mbox series

Patch

diff --git a/Documentation/driver-api/serial/driver.rst b/Documentation/driver-api/serial/driver.rst
--- a/Documentation/driver-api/serial/driver.rst
+++ b/Documentation/driver-api/serial/driver.rst
@@ -136,6 +136,15 @@  hardware.
 
 	This call must not sleep
 
+  prep_tx(port)
+	Prepare port for transmitting characters.
+
+	Locking: port->lock taken.
+
+	Interrupts: locally disabled.
+
+	This call must not sleep
+
   start_tx(port)
 	Start transmitting characters.
 
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -118,6 +118,17 @@  static void uart_stop(struct tty_struct *tty)
 	uart_port_unlock(port, flags);
 }
 
+static int __uart_prep_tx(struct tty_struct *tty)
+{
+	struct uart_state *state = tty->driver_data;
+	struct uart_port *port = state->uart_port;
+
+	if (port && !uart_tx_stopped(port) && port->ops->prep_tx)
+		return port->ops->prep_tx(port);
+
+	return 0;
+}
+
 static void __uart_start(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
@@ -574,6 +585,12 @@  static int uart_write(struct tty_struct *tty,
 		return 0;
 	}
 
+	ret = __uart_prep_tx(tty);
+	if (ret < 0) {
+		uart_port_unlock(port, flags);
+		return 0;
+	}
+
 	while (port) {
 		c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
 		if (count < c)
@@ -600,6 +617,12 @@  static unsigned int uart_write_room(struct tty_struct *tty)
 	unsigned int ret;
 
 	port = uart_port_lock(state, flags);
+	ret = __uart_prep_tx(tty);
+	if (ret < 0) {
+		uart_port_unlock(port, flags);
+		return 0;
+	}
+
 	ret = uart_circ_chars_free(&state->xmit);
 	uart_port_unlock(port, flags);
 	return ret;
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -40,6 +40,7 @@  struct uart_ops {
 	void		(*set_mctrl)(struct uart_port *, unsigned int mctrl);
 	unsigned int	(*get_mctrl)(struct uart_port *);
 	void		(*stop_tx)(struct uart_port *);
+	int		(*prep_tx)(struct uart_port *);
 	void		(*start_tx)(struct uart_port *);
 	void		(*throttle)(struct uart_port *);
 	void		(*unthrottle)(struct uart_port *);