]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
xhci: dbgtty: Fix data corruption when transmitting data form DbC to host
authorMathias Nyman <mathias.nyman@linux.intel.com>
Fri, 7 Nov 2025 16:28:17 +0000 (18:28 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 6 Dec 2025 21:12:44 +0000 (06:12 +0900)
commit f6bb3b67be9af0cfb90075c60850b6af5338a508 upstream.

Data read from a DbC device may be corrupted due to a race between
ongoing write and write request completion handler both queuing new
transfer blocks (TRBs) if there are remining data in the kfifo.

TRBs may be in incorrct order compared to the data in the kfifo.

Driver fails to keep lock between reading data from kfifo into a
dbc request buffer, and queuing the request to the transfer ring.

This allows completed request to re-queue itself in the middle of
an ongoing transfer loop, forcing itself between a kfifo read and
request TRB write of another request

cpu0 cpu1 (re-queue completed req2)

lock(port_lock)
dbc_start_tx()
kfifo_out(fifo, req1->buffer)
unlock(port_lock)
lock(port_lock)
dbc_write_complete(req2)
dbc_start_tx()
       kfifo_out(fifo, req2->buffer)
unlock(port_lock)
lock(port_lock)
req2->trb = ring->enqueue;
ring->enqueue++
unlock(port_lock)
lock(port_lock)
req1->trb = ring->enqueue;
ring->enqueue++
unlock(port_lock)

In the above scenario a kfifo containing "12345678" would read "1234" to
req1 and "5678" to req2, but req2 is queued before req1 leading to
data being transmitted as "56781234"

Solve this by adding a flag that prevents starting a new tx if we
are already mid dbc_start_tx() during the unlocked part.

The already running dbc_do_start_tx() will make sure the newly completed
request gets re-queued as it is added to the request write_pool while
holding the lock.

Cc: stable@vger.kernel.org
Fixes: dfba2174dc42 ("usb: xhci: Add DbC support in xHCI driver")
Tested-by: Ɓukasz Bartosik <ukaszb@chromium.org>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20251107162819.1362579-3-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-dbgcap.h
drivers/usb/host/xhci-dbgtty.c

index 1fab3eb9c831d37847e50a33c807ad423ae62f70..e6aab11ed6f25ce15965f92cd3f8f4fc7b6312de 100644 (file)
@@ -113,6 +113,7 @@ struct dbc_port {
        unsigned int                    tx_boundary;
 
        bool                            registered;
+       bool                            tx_running;
 };
 
 struct dbc_driver {
index daf021daaa7e0049204f4a7b93eac7268cdf5d80..81432638b678ebfc19cf2eb04c6679452167fdc3 100644 (file)
@@ -47,7 +47,7 @@ dbc_kfifo_to_req(struct dbc_port *port, char *packet)
        return len;
 }
 
-static int dbc_start_tx(struct dbc_port *port)
+static int dbc_do_start_tx(struct dbc_port *port)
        __releases(&port->port_lock)
        __acquires(&port->port_lock)
 {
@@ -57,6 +57,8 @@ static int dbc_start_tx(struct dbc_port *port)
        bool                    do_tty_wake = false;
        struct list_head        *pool = &port->write_pool;
 
+       port->tx_running = true;
+
        while (!list_empty(pool)) {
                req = list_entry(pool->next, struct dbc_request, list_pool);
                len = dbc_kfifo_to_req(port, req->buf);
@@ -77,12 +79,25 @@ static int dbc_start_tx(struct dbc_port *port)
                }
        }
 
+       port->tx_running = false;
+
        if (do_tty_wake && port->port.tty)
                tty_wakeup(port->port.tty);
 
        return status;
 }
 
+/* must be called with port->port_lock held */
+static int dbc_start_tx(struct dbc_port *port)
+{
+       lockdep_assert_held(&port->port_lock);
+
+       if (port->tx_running)
+               return -EBUSY;
+
+       return dbc_do_start_tx(port);
+}
+
 static void dbc_start_rx(struct dbc_port *port)
        __releases(&port->port_lock)
        __acquires(&port->port_lock)