@@ -736,8 +736,8 @@ over the PCI busses sequentially) or by PCI device (must be on segment 0).
Specify the USB controller to use, either by instance number (when going
over the PCI busses sequentially) or by PCI device (must be on segment 0).
-Output only console. XHCI driver will wait indefinitely for the debug host to
-connect - make sure the cable is connected.
+XHCI driver will wait indefinitely for the debug host to connect - make sure
+the cable is connected.
### debug_stack_lines
> `= <integer>`
@@ -111,6 +111,7 @@ enum {
enum {
XHCI_TRB_CC_SUCCESS = 1,
XHCI_TRB_CC_TRB_ERR = 5,
+ XHCI_TRB_CC_SHORT_PACKET = 13,
};
/* DbC endpoint types */
@@ -239,6 +240,7 @@ struct dbc {
struct xhci_trb_ring dbc_oring;
struct xhci_trb_ring dbc_iring;
struct dbc_work_ring dbc_owork;
+ struct dbc_work_ring dbc_iwork;
struct xhci_string_descriptor *dbc_str;
pci_sbdf_t sbdf;
@@ -443,6 +445,16 @@ static void xhci_trb_norm_set_ioc(struct xhci_trb *trb)
trb->ctrl |= 0x20;
}
+static uint64_t xhci_trb_norm_buf(const struct xhci_trb *trb)
+{
+ return trb->params;
+}
+
+static uint32_t xhci_trb_norm_len(const struct xhci_trb *trb)
+{
+ return trb->status & 0x1FFFF;
+}
+
/**
* Fields for Transfer Event TRBs (see section 6.4.2.1). Note that event
* TRBs are read-only from software
@@ -452,6 +464,17 @@ static uint64_t xhci_trb_tfre_ptr(const struct xhci_trb *trb)
return trb->params;
}
+static uint32_t xhci_trb_tfre_cc(const struct xhci_trb *trb)
+{
+ return trb->status >> 24;
+}
+
+/* Amount of data _not_ transferred */
+static uint32_t xhci_trb_tfre_len(const struct xhci_trb *trb)
+{
+ return trb->status & 0x1FFFF;
+}
+
/* Fields for link TRBs (section 6.4.4.1) */
static void xhci_trb_link_set_rsp(struct xhci_trb *trb, uint64_t rsp)
{
@@ -493,6 +516,14 @@ static bool xhci_trb_ring_full(const struct xhci_trb_ring *ring)
return ((ring->enq + 1) & (DBC_TRB_RING_CAP - 1)) == ring->deq;
}
+static unsigned int xhci_trb_ring_size(const struct xhci_trb_ring *ring)
+{
+ if ( ring->enq >= ring->deq )
+ return ring->enq - ring->deq;
+
+ return DBC_TRB_RING_CAP - ring->deq + ring->enq;
+}
+
static bool dbc_work_ring_full(const struct dbc_work_ring *ring)
{
return ((ring->enq + 1) & (DBC_WORK_RING_CAP - 1)) == ring->deq;
@@ -506,6 +537,14 @@ static unsigned int dbc_work_ring_size(const struct dbc_work_ring *ring)
return DBC_WORK_RING_CAP - ring->deq + ring->enq;
}
+static unsigned int dbc_work_ring_space_to_end(const struct dbc_work_ring *ring)
+{
+ if ( ring->enq >= ring->deq )
+ return DBC_WORK_RING_CAP - ring->enq;
+
+ return ring->deq - ring->enq;
+}
+
static void dbc_push_trb(struct dbc *dbc, struct xhci_trb_ring *ring,
uint64_t dma, uint64_t len)
{
@@ -566,6 +605,31 @@ static unsigned int dbc_push_work(struct dbc *dbc, struct dbc_work_ring *ring,
return i;
}
+static void dbc_rx_trb(struct dbc *dbc, struct xhci_trb *trb,
+ uint64_t not_transferred)
+{
+ struct dbc_work_ring *ring = &dbc->dbc_iwork;
+ unsigned int rx_len;
+ unsigned int end, start = ring->enq;
+
+ if ( xhci_trb_type(trb) != XHCI_TRB_NORM )
+ /* Can be Link TRB for example. */
+ return;
+
+ ASSERT(xhci_trb_norm_buf(trb) == ring->dma + ring->enq);
+ ASSERT(xhci_trb_norm_len(trb) >= not_transferred);
+ rx_len = xhci_trb_norm_len(trb) - not_transferred;
+
+ /* It can hit the ring end, but should not wrap around. */
+ ASSERT(ring->enq + rx_len <= DBC_WORK_RING_CAP);
+ ring->enq = (ring->enq + rx_len) & (DBC_WORK_RING_CAP - 1);
+
+ end = ring->enq;
+
+ if ( end > start )
+ cache_flush(&ring->buf[start], end - start);
+}
+
/*
* Note that if IN transfer support is added, then this
* will need to be changed; it assumes an OUT transfer ring only
@@ -575,6 +639,7 @@ static void dbc_pop_events(struct dbc *dbc)
struct dbc_reg *reg = dbc->dbc_reg;
struct xhci_trb_ring *er = &dbc->dbc_ering;
struct xhci_trb_ring *tr = &dbc->dbc_oring;
+ struct xhci_trb_ring *ir = &dbc->dbc_iring;
struct xhci_trb *event = &er->trb[er->deq];
uint64_t erdp = readq(®->erdp);
uint32_t portsc;
@@ -600,6 +665,14 @@ static void dbc_pop_events(struct dbc *dbc)
trb_idx = (event_ptr - tr->dma) >> XHCI_TRB_SHIFT;
tr->deq = (trb_idx + 1) & (DBC_TRB_RING_CAP - 1);
}
+ else if ( event_ptr - ir->dma < DBC_TRB_RING_BYTES )
+ {
+ trb_idx = (event_ptr - ir->dma) >> XHCI_TRB_SHIFT;
+ if ( xhci_trb_tfre_cc(event) == XHCI_TRB_CC_SUCCESS ||
+ xhci_trb_tfre_cc(event) == XHCI_TRB_CC_SHORT_PACKET )
+ dbc_rx_trb(dbc, &ir->trb[trb_idx], xhci_trb_tfre_len(event));
+ ir->deq = (trb_idx + 1) & (DBC_TRB_RING_CAP - 1);
+ }
else
dbc_alert("event: TRB 0x%lx not found in any ring\n",
event_ptr);
@@ -870,6 +943,7 @@ static bool __init dbc_open(struct dbc *dbc)
return false;
dbc_init_work_ring(dbc, &dbc->dbc_owork);
+ dbc_init_work_ring(dbc, &dbc->dbc_iwork);
dbc_enable_dbc(dbc);
dbc->open = true;
@@ -946,6 +1020,33 @@ static void dbc_flush(struct dbc *dbc, struct xhci_trb_ring *trb,
}
/**
+ * Ensure DbC has a pending transfer TRB to receive data into.
+ *
+ * @param dbc the dbc to flush
+ * @param trb the ring for the TRBs to transfer
+ * @param wrk the work ring to receive data into
+ */
+static void dbc_enqueue_in(struct dbc *dbc, struct xhci_trb_ring *trb,
+ struct dbc_work_ring *wrk)
+{
+ struct dbc_reg *reg = dbc->dbc_reg;
+ uint32_t db = (readl(®->db) & 0xFFFF00FF) | (trb->db << 8);
+
+ /* Check if there is already queued TRB */
+ if ( xhci_trb_ring_size(trb) >= 1 )
+ return;
+
+ if ( dbc_work_ring_full(wrk) )
+ return;
+
+ dbc_push_trb(dbc, trb, wrk->dma + wrk->enq,
+ dbc_work_ring_space_to_end(wrk));
+
+ wmb();
+ writel(db, ®->db);
+}
+
+/**
* Queue a single character to the DbC. A transfer TRB will be created
* if the character is a newline and the DbC will be notified that data is
* available for writing to the debug host.
@@ -968,6 +1069,19 @@ static int64_t dbc_putc(struct dbc *dbc, char c)
return 1;
}
+static int dbc_getc(struct dbc *dbc, char *c)
+{
+ struct dbc_work_ring *wrk = &dbc->dbc_iwork;
+
+ if ( dbc_work_ring_size(wrk) == 0 )
+ return 0;
+
+ *c = wrk->buf[wrk->deq];
+ wrk->deq = (wrk->deq + 1) & (DBC_WORK_RING_CAP - 1);
+
+ return 1;
+}
+
struct dbc_uart {
struct dbc dbc;
struct timer timer;
@@ -986,10 +1100,16 @@ static void cf_check dbc_uart_poll(void *data)
if ( spin_trylock_irqsave(&port->tx_lock, flags) )
{
if ( dbc_ensure_running(dbc) )
+ {
dbc_flush(dbc, &dbc->dbc_oring, &dbc->dbc_owork);
+ dbc_enqueue_in(dbc, &dbc->dbc_iring, &dbc->dbc_iwork);
+ }
spin_unlock_irqrestore(&port->tx_lock, flags);
}
+ while ( dbc_work_ring_size(&dbc->dbc_iwork) )
+ serial_rx_interrupt(port, guest_cpu_user_regs());
+
serial_tx_interrupt(port, guest_cpu_user_regs());
set_timer(&uart->timer, NOW() + MICROSECS(DBC_POLL_INTERVAL));
}
@@ -1028,6 +1148,12 @@ static void cf_check dbc_uart_putc(struct serial_port *port, char c)
dbc_putc(&uart->dbc, c);
}
+static int cf_check dbc_uart_getc(struct serial_port *port, char *c)
+{
+ struct dbc_uart *uart = port->uart;
+ return dbc_getc(&uart->dbc, c);
+}
+
static void cf_check dbc_uart_flush(struct serial_port *port)
{
s_time_t goal;
@@ -1047,6 +1173,7 @@ static struct uart_driver dbc_uart_driver = {
.init_postirq = dbc_uart_init_postirq,
.tx_ready = dbc_uart_tx_ready,
.putc = dbc_uart_putc,
+ .getc = dbc_uart_getc,
.flush = dbc_uart_flush,
};
@@ -1056,6 +1183,7 @@ struct dbc_dma_bufs {
struct xhci_trb out_trb[DBC_TRB_RING_CAP];
struct xhci_trb in_trb[DBC_TRB_RING_CAP];
uint8_t out_wrk_buf[DBC_WORK_RING_CAP];
+ uint8_t in_wrk_buf[DBC_WORK_RING_CAP];
struct xhci_erst_segment erst __aligned(16);
struct xhci_dbc_ctx ctx __aligned(16);
struct xhci_string_descriptor str_buf[DBC_STRINGS_COUNT];
@@ -1108,6 +1236,7 @@ void __init xhci_dbc_uart_init(void)
dbc->dbc_oring.trb = dbc_dma_bufs.out_trb;
dbc->dbc_iring.trb = dbc_dma_bufs.in_trb;
dbc->dbc_owork.buf = dbc_dma_bufs.out_wrk_buf;
+ dbc->dbc_iwork.buf = dbc_dma_bufs.in_wrk_buf;
dbc->dbc_str = dbc_dma_bufs.str_buf;
if ( dbc_open(dbc) )