Re: [PATCH v2 3/4] USB: serial: mxuport: handle SEND_NEXT transmit flow control
Johan Hovold <[email protected]> Mon, 3 Aug 2026 10:35:12 +0200
| Newsgroups | org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On Mon, Jul 27, 2026 at 06:28:09PM +0800, Crescent Hsieh wrote:
> On Tue, Jul 21, 2026 at 05:51:58PM +0200, Johan Hovold wrote:
> > On Tue, Jun 23, 2026 at 04:01:38PM +0800, Crescent Hsieh wrote:
> > > @@ -276,22 +280,148 @@ MODULE_DEVICE_TABLE(usb, mxuport_idtable);
> > > static int mxuport_prepare_write_buffer(struct usb_serial_port *port,
> > > void *dest, size_t size)
> > > {
> > > + struct mxuport_port *mxport = usb_get_serial_port_data(port);
> > > u8 *buf = dest;
> > > + unsigned long flags;
> > > + bool request_send_next;
> > > int count;
> > >
> > > - count = kfifo_out_locked(&port->write_fifo, buf + HEADER_SIZE,
> > > - size - HEADER_SIZE,
> > > - &port->lock);
> > > + spin_lock_irqsave(&port->lock, flags);
> > > + count = kfifo_out(&port->write_fifo, buf + HEADER_SIZE,
> > > + size - HEADER_SIZE);
> > > + mxport->sent_payload += count;
> > > + request_send_next = mxport->sent_payload >= port->bulk_out_size;
> >
> > How big are the per-port buffers?
>
> The size depends on the device. The G2 and Platform UART firmware have
> a 4096-byte buffer per port, while the G1 per-port buffer ranges from
> 32 KiB to 256 KiB depending on the number of ports.
>
> The bulk_out_size threshold is not derived from the firmware buffer
> size. It is used as a simple and conservative pacing interval that
> works across the device families without requiring family-specific
> buffer sizes in the driver. This results in more frequent
> SEND_NEXT waits and may reduce throughput.
Thanks for the details. So there may be some room for optimisation later
here by taking the buffer sizes into account. Not sure if it's worth it
though (and draining 32k at low line speeds will take quite some time).
> > > +static int mxuport_write_start(struct usb_serial_port *port, gfp_t mem_flags)
> > > +{
> > > + struct mxuport_port *mxport = usb_get_serial_port_data(port);
> > > + struct urb *urb;
> > > + unsigned long flags;
> > > + int i;
> > > + int count;
> > > + int result;
> > > +
> > > + if (test_and_set_bit_lock(USB_SERIAL_WRITE_BUSY, &port->flags))
> > > + return 0;
> > > +retry:
> > > + spin_lock_irqsave(&port->lock, flags);
> > > + if ((mxport->hold_reason & MX_WAIT_FOR_SEND_NEXT) ||
> > > + !port->write_urbs_free || !kfifo_len(&port->write_fifo)) {
> > > + clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
> > > + spin_unlock_irqrestore(&port->lock, flags);
> > > + return 0;
> > > + }
> > > +
> > > + i = (int)find_first_bit(&port->write_urbs_free,
> > > + ARRAY_SIZE(port->write_urbs));
> > > + spin_unlock_irqrestore(&port->lock, flags);
> > > +
> > > + urb = port->write_urbs[i];
> > > + count = mxuport_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);
> > > + port->tx_bytes += count;
> > > + spin_unlock_irqrestore(&port->lock, flags);
> > > +
> > > + clear_bit(i, &port->write_urbs_free);
> > > + result = usb_submit_urb(urb, mem_flags);
> > > + if (result) {
> > > + dev_err_console(port, "%s - error submitting urb: %d\n",
> > > + __func__, result);
> > > + set_bit(i, &port->write_urbs_free);
> > > + spin_lock_irqsave(&port->lock, flags);
> > > + port->tx_bytes -= count;
> >
> > > + if (mxport->hold_reason & MX_WAIT_FOR_SEND_NEXT) {
> > > + mxport->hold_reason &= ~MX_WAIT_FOR_SEND_NEXT;
> > > + mxport->sent_payload = 0;
> > > + }
> >
> > Shouldn't you undo the effects of prepare_write_buffer() and subtract
> > count from sent_payload (and clear the flag) unconditionally?
> >
> > > + spin_unlock_irqrestore(&port->lock, flags);
> > > +
> > > + clear_bit_unlock(USB_SERIAL_WRITE_BUSY, &port->flags);
> > > + return result;
> > > + }
> > > +
> > > + goto retry;
> > > +}
> >
> > This is a more or less verbatim copy of the generic write
> > implementation. If we go this way you should at least mention that you
> > copied it in the commit message, but perhaps we should try to find a way
> > to generalise it instead.
> >
> > Also, if you really need a custom implementation to throttle writes,
> > then shouldn't using one URB be enough? The other one is essentially
> > there to allow for higher throughput which we need to give up for
> > correctness here anyway.
>
> Agreed, the custom write path is largely based on the generic write
> implementation. In v1, I attempted to implement the throttling in
> prepare_write_buffer() to avoid changing the generic path, but
> returning zero caused the generic implementation to submit a
> zero-length URB. In v2, I therefore added a custom write path so that
> submission could be stopped before preparing another URB.
Yeah, we probably want a custom implementation for this. Making the
generic implementation handle drivers returning 0 from
prepare_write_buffer() is straightforward, but then you also need to
notify them if URB submission failed (e.g. to prevent stalled writes).
But then again, if that's all that's needed it doesn't sound too bad.
> I will investigate whether this can instead be generalised through a
> small driver callback that allows the generic write path to check
> whether another URB may be submitted. This would avoid duplicating the
> generic implementation while preserving the behaviour of other
> USB-to-serial drivers.
I think we may have other drivers that could benefit from this too.
> I will also describe the SEND_NEXT behaviour in more detail in the v3
> commit message.
Sounds good, thanks.
Johan