linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Jiri Slaby <jslaby@suse.cz>
To: gregkh@linuxfoundation.org
Cc: linux-serial@vger.kernel.org, linux-kernel@vger.kernel.org,
	Jiri Slaby <jslaby@suse.cz>
Subject: [PATCH 1/3] tty: serial: introduce uart_port_tx{,_limit}() helpers
Date: Mon, 11 Apr 2022 12:54:03 +0200	[thread overview]
Message-ID: <20220411105405.9519-2-jslaby@suse.cz> (raw)
In-Reply-To: <20220411105405.9519-1-jslaby@suse.cz>

Many serial drivers do the same thing:
* send x_char if set
* keep sending from the xmit circular buffer until either
  - the loop reaches the end of the xmit buffer
  - TX is stopped
  - HW fifo is full
* check for pending characters and:
  - wake up tty writers to fill for more data into xmit buffer
  - stop TX if there is nothing in the xmit buffer

The only differences are:
* how to write the character to the HW fifo
* the check of the end condition:
  - is the HW fifo full?
  - is limit of the written characters reached?

So unify the above into two helpers:
* uart_port_tx_limit() -- the generic one, it performs the above taking
  into account the written characters limit
* uart_port_tx() -- calls the above with ~0 as the limit. So it only
  checks the HW fullness.

We need three more hooks in struct uart_ops for all this to work:
* tx_ready() -- returns true if HW can accept more data.
* put_char() -- write a character to the device.
* tx_done() -- when the write loop is done, perform arbitrary action
  before potential invocation of ops->stop_tx() happens.

NOTE1: Maybe the three hooks in uart_ops above are overkill. We can
instead pass pointers to the three functions directly to the new helpers
as they are not used elsewhere. Similar to uart_console_write() and its
putchar().

NOTE2: These two new helper functions call the hooks per every character
processed. I was unable to measure any difference, provided most time is
spent by readb (or alike) in the hooks themselves.  First, LTO might
help to eliminate these explicit calls (we might need NOTE1 to be
implemented for this to be true). Second, if this turns out to be a
problem, we can introduce a macro to build the helper in the driver's
code instead of serial_core. That is, similar to wait_event().

Signed-off-by: Jiri Slaby <jslaby@suse.cz>
---
 Documentation/driver-api/serial/driver.rst | 28 ++++++++++++
 drivers/tty/serial/serial_core.c           | 53 ++++++++++++++++++++++
 include/linux/serial_core.h                |  9 ++++
 3 files changed, 90 insertions(+)

diff --git a/Documentation/driver-api/serial/driver.rst b/Documentation/driver-api/serial/driver.rst
index 06ec04ba086f..7dc3791addeb 100644
--- a/Documentation/driver-api/serial/driver.rst
+++ b/Documentation/driver-api/serial/driver.rst
@@ -80,6 +80,34 @@ hardware.
 
 	This call must not sleep
 
+  tx_ready(port)
+	The driver returns true if the HW can accept more data to be sent.
+
+	Locking: port->lock taken.
+
+	Interrupts: locally disabled.
+
+	This call must not sleep.
+
+  put_char(port, ch)
+	The driver is asked to write ch to the device.
+
+	Locking: port->lock taken.
+
+	Interrupts: locally disabled.
+
+	This call must not sleep.
+
+  tx_done(port)
+	When the write loop is done, the driver can perform arbitrary action
+	here before potential invocation of ops->stop_tx() happens.
+
+	Locking: port->lock taken.
+
+	Interrupts: locally disabled.
+
+	This call must not sleep.
+
   set_mctrl(port, mctrl)
 	This function sets the modem control lines for port described
 	by 'port' to the state described by mctrl.  The relevant bits
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 6a8963caf954..1be14e90066c 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -107,6 +107,59 @@ void uart_write_wakeup(struct uart_port *port)
 }
 EXPORT_SYMBOL(uart_write_wakeup);
 
+static bool uart_port_tx_always_ready(struct uart_port *port)
+{
+	return true;
+}
+
+/**
+ * uart_port_tx_limit -- transmit helper for uart_port
+ * @port: from which port to transmit
+ * @count: limit count
+ *
+ * uart_port_tx_limit() transmits characters from the xmit buffer to the
+ * hardware using @uart_port::ops::put_char(). It does so until @count
+ * characters are sent and while @uart_port::ops::tx_ready() still returns
+ * non-zero (if non-NULL).
+ *
+ * Return: number of characters in the xmit buffer when done.
+ */
+unsigned int uart_port_tx_limit(struct uart_port *port, unsigned int count)
+{
+	struct circ_buf *xmit = &port->state->xmit;
+	bool (*tx_ready)(struct uart_port *) = port->ops->tx_ready ? :
+		uart_port_tx_always_ready;
+	unsigned int pending;
+
+	for (; count && tx_ready(port); count--, port->icount.tx++) {
+		if (port->x_char) {
+			port->ops->put_char(port, port->x_char);
+			port->x_char = 0;
+			continue;
+		}
+
+		if (uart_circ_empty(xmit) || uart_tx_stopped(port))
+			break;
+
+		port->ops->put_char(port, xmit->buf[xmit->tail]);
+		xmit->tail = (xmit->tail + 1) % UART_XMIT_SIZE;
+	}
+
+	if (port->ops->tx_done)
+		port->ops->tx_done(port);
+
+	pending = uart_circ_chars_pending(xmit);
+	if (pending < WAKEUP_CHARS) {
+		uart_write_wakeup(port);
+
+		if (pending == 0)
+			port->ops->stop_tx(port);
+	}
+
+	return pending;
+}
+EXPORT_SYMBOL(uart_port_tx_limit);
+
 static void uart_stop(struct tty_struct *tty)
 {
 	struct uart_state *state = tty->driver_data;
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index d4828e69087a..c990a20b9989 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -37,6 +37,9 @@ struct gpio_desc;
  */
 struct uart_ops {
 	unsigned int	(*tx_empty)(struct uart_port *);
+	bool		(*tx_ready)(struct uart_port *);
+	void		(*put_char)(struct uart_port *, unsigned char ch);
+	void		(*tx_done)(struct uart_port *);
 	void		(*set_mctrl)(struct uart_port *, unsigned int mctrl);
 	unsigned int	(*get_mctrl)(struct uart_port *);
 	void		(*stop_tx)(struct uart_port *);
@@ -321,6 +324,12 @@ struct uart_driver {
 };
 
 void uart_write_wakeup(struct uart_port *port);
+unsigned int uart_port_tx_limit(struct uart_port *port, unsigned int count);
+
+static inline unsigned int uart_port_tx(struct uart_port *port)
+{
+	return uart_port_tx_limit(port, ~0U);
+}
 
 /*
  * Baud rate helpers.
-- 
2.35.1


  reply	other threads:[~2022-04-11 10:54 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-04-11 10:54 [PATCH 0/3] tty: TX helpers Jiri Slaby
2022-04-11 10:54 ` Jiri Slaby [this message]
2022-04-14 16:32   ` [PATCH 1/3] tty: serial: introduce uart_port_tx{,_limit}() helpers Greg KH
2022-04-15  7:47     ` Jiri Slaby
2022-04-15  8:42       ` Greg KH
2022-04-11 10:54 ` [PATCH 2/3] tty: serial: use uart_port_tx() helper Jiri Slaby
2022-04-11 10:54 ` [PATCH 3/3] tty: serial: use uart_port_tx_limit() helper Jiri Slaby
2022-04-11 11:51   ` Pali Rohár
2022-08-29  7:27     ` Jiri Slaby
2022-08-29  8:50       ` Pali Rohár

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=20220411105405.9519-2-jslaby@suse.cz \
    --to=jslaby@suse.cz \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.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;
as well as URLs for NNTP newsgroup(s).