[PATCH v3 4/6] USB: serial: add generic write control callbacks

Crescent Hsieh <[email protected]>
Newsgroups org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Some USB serial devices use device-specific flow control to determine
whether write submission may proceed. The generic write path currently
provides no way to pause submission based on driver-specific state. It
also provides no way for a driver to restore its state if URB submission
fails.

Add an optional write_ready() callback to struct usb_serial_driver. Call
it with port->lock held after confirming that data is queued and a write
URB is available. If the callback returns false, stop write processing
and leave the data in the FIFO. The driver must call
usb_serial_generic_write_start() when writes may proceed again.

Add an optional write_rollback() callback for restoring driver-specific
state after usb_submit_urb() fails. Call it with port->lock held after
restoring the generic transmit accounting, and pass it the length of the
prepared transfer.

Drivers which do not provide these callbacks retain the existing generic
write behaviour.

Signed-off-by: Crescent Hsieh <[email protected]>
---
 drivers/usb/serial/generic.c | 12 ++++++++----
 include/linux/usb/serial.h   |  9 ++++++++-
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c
index 6eaf74930aa3..bb042b97893b 100644
--- a/drivers/usb/serial/generic.c
+++ b/drivers/usb/serial/generic.c
@@ -152,6 +152,7 @@ int usb_serial_generic_prepare_write_buffer(struct usb_serial_port *port,
 int usb_serial_generic_write_start(struct usb_serial_port *port,
 							gfp_t mem_flags)
 {
+	struct usb_serial_driver *type = port->serial->type;
 	struct urb *urb;
 	int count, result;
 	unsigned long flags;
@@ -161,7 +162,8 @@ int usb_serial_generic_write_start(struct usb_serial_port *port,
 		return 0;
 retry:
 	spin_lock_irqsave(&port->lock, flags);
-	if (!port->write_urbs_free || !kfifo_len(&port->write_fifo)) {
+	if (!port->write_urbs_free || !kfifo_len(&port->write_fifo) ||
+	    (type->write_ready && !type->write_ready(port))) {
 		clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
 		spin_unlock_irqrestore(&port->lock, flags);
 		return 0;
@@ -171,9 +173,7 @@ int usb_serial_generic_write_start(struct usb_serial_port *port,
 	spin_unlock_irqrestore(&port->lock, flags);
 
 	urb = port->write_urbs[i];
-	count = port->serial->type->prepare_write_buffer(port,
-						urb->transfer_buffer,
-						port->bulk_out_size);
+	count = type->prepare_write_buffer(port, urb->transfer_buffer, port->bulk_out_size);
 	urb->transfer_buffer_length = count;
 	usb_serial_debug_data(&port->dev, __func__, count, urb->transfer_buffer);
 	spin_lock_irqsave(&port->lock, flags);
@@ -188,6 +188,10 @@ int usb_serial_generic_write_start(struct usb_serial_port *port,
 		set_bit(i, &port->write_urbs_free);
 		spin_lock_irqsave(&port->lock, flags);
 		port->tx_bytes -= count;
+
+		if (type->write_rollback)
+			type->write_rollback(port, count);
+
 		spin_unlock_irqrestore(&port->lock, flags);
 
 		clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h
index 534e6650e2aa..d44ae5a9bf5d 100644
--- a/include/linux/usb/serial.h
+++ b/include/linux/usb/serial.h
@@ -305,9 +305,17 @@ struct usb_serial_driver {
 	void (*write_bulk_callback)(struct urb *urb);
 	/* Called by the generic read bulk callback */
 	void (*process_read_urb)(struct urb *urb);
+	/*
+	 * Called with port->lock held before preparing another write URB.
+	 * Drivers returning false must restart the generic write path when
+	 * writes may proceed again.
+	 */
+	bool (*write_ready)(struct usb_serial_port *port);
 	/* Called by the generic write implementation */
 	int (*prepare_write_buffer)(struct usb_serial_port *port,
 						void *dest, size_t size);
+	/* Called with port->lock held to roll back a failed write submission */
+	void (*write_rollback)(struct usb_serial_port *port, int count);
 };
 #define to_usb_serial_driver(d) \
 	container_of(d, struct usb_serial_driver, driver)
@@ -436,4 +444,3 @@ module_exit(usb_serial_module_exit);
 	usb_serial_module_driver(KBUILD_MODNAME, __serial_drivers, __ids)
 
 #endif /* __LINUX_USB_SERIAL_H */
-
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.