This patch adds interfaces for bulk out and bulk in ops. These
interfaces could be used to implement early printk bootconsole
or hook to various system debuggers.

Signed-off-by: Lu Baolu <baolu...@linux.intel.com>
---
 drivers/usb/early/xhci-dbc.c | 373 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/xhci-dbc.h |  30 ++++
 2 files changed, 403 insertions(+)

diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c
index b36a527..f51daa4 100644
--- a/drivers/usb/early/xhci-dbc.c
+++ b/drivers/usb/early/xhci-dbc.c
@@ -219,11 +219,21 @@ static void xdbc_dbg_dump_data(char *str)
        xdbc_dbg_dump_string("String Descriptor:");
 }
 
+static void xdbc_dbg_dump_trb(struct xdbc_trb *trb, char *str)
+{
+       xdbc_trace("DBC trb: %s\n", str);
+       xdbc_trace("@%016llx %08x %08x %08x %08x\n", (u64)__pa(trb),
+                               le32_to_cpu(trb->field[0]),
+                               le32_to_cpu(trb->field[1]),
+                               le32_to_cpu(trb->field[2]),
+                               le32_to_cpu(trb->field[3]));
+}
 #else
 static inline void xdbc_trace(const char *fmt, ...) { }
 static inline void xdbc_dump_debug_buffer(void) { }
 static inline void xdbc_dbg_dump_regs(char *str) { }
 static inline void xdbc_dbg_dump_data(char *str) { }
+static inline void xdbc_dbg_dump_trb(struct xdbc_trb *trb, char *str) { }
 #endif /* DBC_DEBUG */
 
 /*
@@ -334,6 +344,7 @@ static void *xdbc_get_page(dma_addr_t *dma_addr,
        static char in_ring_page[PAGE_SIZE] __aligned(PAGE_SIZE);
        static char out_ring_page[PAGE_SIZE] __aligned(PAGE_SIZE);
        static char table_page[PAGE_SIZE] __aligned(PAGE_SIZE);
+       static char bulk_buf_page[PAGE_SIZE] __aligned(PAGE_SIZE);
 
        switch (type) {
        case XDBC_PAGE_EVENT:
@@ -348,6 +359,9 @@ static void *xdbc_get_page(dma_addr_t *dma_addr,
        case XDBC_PAGE_TABLE:
                virt = (void *)table_page;
                break;
+       case XDBC_PAGE_BUFFER:
+               virt = (void *)bulk_buf_page;
+               break;
        default:
                return NULL;
        }
@@ -707,6 +721,12 @@ static int xdbc_mem_init(void)
        dev_info = cpu_to_le32((XDBC_DEVICE_REV << 16) | XDBC_PRODUCT_ID);
        writel(dev_info, &xdbcp->xdbc_reg->devinfo2);
 
+       /* get and store the transfer buffer */
+       xdbcp->out_buf = xdbc_get_page(&xdbcp->out_dma,
+                       XDBC_PAGE_BUFFER);
+       xdbcp->in_buf = xdbcp->out_buf + XDBC_MAX_PACKET;
+       xdbcp->in_dma = xdbcp->out_dma + XDBC_MAX_PACKET;
+
        return 0;
 }
 
@@ -802,6 +822,9 @@ static int xdbc_start(void)
 
        xdbc_trace("root hub port number %d\n", DCST_DPN(status));
 
+       xdbcp->in_ep_state = EP_RUNNING;
+       xdbcp->out_ep_state = EP_RUNNING;
+
        xdbc_trace("DbC is running now, control 0x%08x\n",
                        readl(&xdbcp->xdbc_reg->control));
 
@@ -895,3 +918,353 @@ int __init early_xdbc_init(char *s)
 
        return 0;
 }
+
+static void xdbc_queue_trb(struct xdbc_ring *ring,
+               u32 field1, u32 field2, u32 field3, u32 field4)
+{
+       struct xdbc_trb *trb, *link_trb;
+
+       trb = ring->enqueue;
+       trb->field[0] = cpu_to_le32(field1);
+       trb->field[1] = cpu_to_le32(field2);
+       trb->field[2] = cpu_to_le32(field3);
+       trb->field[3] = cpu_to_le32(field4);
+
+       xdbc_dbg_dump_trb(trb, "enqueue trb");
+
+       ++(ring->enqueue);
+       if (ring->enqueue >= &ring->segment->trbs[TRBS_PER_SEGMENT - 1]) {
+               link_trb = ring->enqueue;
+               if (ring->cycle_state)
+                       link_trb->field[3] |= cpu_to_le32(TRB_CYCLE);
+               else
+                       link_trb->field[3] &= cpu_to_le32(~TRB_CYCLE);
+
+               ring->enqueue = ring->segment->trbs;
+               ring->cycle_state ^= 1;
+       }
+}
+
+static void xdbc_ring_doorbell(int target)
+{
+       writel(DOOR_BELL_TARGET(target), &xdbcp->xdbc_reg->doorbell);
+}
+
+static void xdbc_handle_port_status(struct xdbc_trb *evt_trb)
+{
+       u32 port_reg;
+
+       port_reg = readl(&xdbcp->xdbc_reg->portsc);
+
+       if (port_reg & PORTSC_CSC) {
+               xdbc_trace("%s: connect status change event\n", __func__);
+               writel(port_reg | PORTSC_CSC, &xdbcp->xdbc_reg->portsc);
+               port_reg = readl(&xdbcp->xdbc_reg->portsc);
+       }
+
+       if (port_reg & PORTSC_PRC) {
+               xdbc_trace("%s: port reset change event\n", __func__);
+               writel(port_reg | PORTSC_PRC, &xdbcp->xdbc_reg->portsc);
+               port_reg = readl(&xdbcp->xdbc_reg->portsc);
+       }
+
+       if (port_reg & PORTSC_PLC) {
+               xdbc_trace("%s: port link status change event\n", __func__);
+               writel(port_reg | PORTSC_PLC, &xdbcp->xdbc_reg->portsc);
+               port_reg = readl(&xdbcp->xdbc_reg->portsc);
+       }
+
+       if (port_reg & PORTSC_CEC) {
+               xdbc_trace("%s: config error change\n", __func__);
+               writel(port_reg | PORTSC_CEC, &xdbcp->xdbc_reg->portsc);
+               port_reg = readl(&xdbcp->xdbc_reg->portsc);
+       }
+}
+
+static void xdbc_handle_tx_event(struct xdbc_trb *evt_trb)
+{
+       u32 comp_code;
+       u32 tx_dma_high, tx_dma_low;
+       u64 in_dma, out_dma;
+       size_t remain_length;
+       int ep_id;
+
+       tx_dma_low = le32_to_cpu(evt_trb->field[0]);
+       tx_dma_high = le32_to_cpu(evt_trb->field[1]);
+       comp_code = GET_COMP_CODE(le32_to_cpu(evt_trb->field[2]));
+       remain_length = EVENT_TRB_LEN(le32_to_cpu(evt_trb->field[2]));
+       ep_id = TRB_TO_EP_ID(le32_to_cpu(evt_trb->field[3]));
+       in_dma = __pa(xdbcp->in_pending);
+       out_dma = __pa(xdbcp->out_pending);
+
+       /*
+        * Possible Completion Codes for DbC Transfer Event are Success,
+        * Stall Error, USB Transaction Error, Babble Detected Error,
+        * TRB Error, Short Packet, Undefined Error, Event Ring Full Error,
+        * and Vendor Defined Error. TRB error, undefined error and vendor
+        * defined error will result in HOT/HIT set and be handled the same
+        * way as Stall error.
+        */
+       switch (comp_code) {
+       case COMP_SUCCESS:
+               remain_length = 0;
+       case COMP_SHORT_TX:
+               xdbc_trace("%s: endpoint %d remains %d bytes\n", __func__,
+                       ep_id, remain_length);
+               break;
+       case COMP_TRB_ERR:
+       case COMP_BABBLE:
+       case COMP_TX_ERR:
+       case COMP_STALL:
+       default:
+               xdbc_trace("%s: endpoint %d halted\n", __func__, ep_id);
+               if (ep_id == XDBC_EPID_OUT)
+                       xdbcp->out_ep_state = EP_HALTED;
+               if (ep_id == XDBC_EPID_IN)
+                       xdbcp->in_ep_state = EP_HALTED;
+
+               break;
+       }
+
+       if (lower_32_bits(in_dma) == tx_dma_low &&
+                       upper_32_bits(in_dma) == tx_dma_high) {
+               xdbcp->in_complete = comp_code;
+               xdbcp->in_complete_length =
+                               (remain_length > xdbcp->in_length) ?
+                               0 : xdbcp->in_length - remain_length;
+       }
+
+       if (lower_32_bits(out_dma) == tx_dma_low &&
+                       upper_32_bits(out_dma) == tx_dma_high) {
+               xdbcp->out_complete = comp_code;
+               xdbcp->out_complete_length =
+                               (remain_length > xdbcp->out_length) ?
+                               0 : xdbcp->out_length - remain_length;
+       }
+}
+
+static void xdbc_handle_events(void)
+{
+       struct xdbc_trb *evt_trb;
+       bool update_erdp = false;
+
+       evt_trb = xdbcp->evt_ring.dequeue;
+       while ((le32_to_cpu(evt_trb->field[3]) & TRB_CYCLE) ==
+                       xdbcp->evt_ring.cycle_state) {
+               /*
+                * Memory barrier to ensure software sees the trbs
+                * enqueued by hardware.
+                */
+               rmb();
+
+               xdbc_dbg_dump_trb(evt_trb, "event trb");
+
+               /* FIXME: Handle more event types. */
+               switch ((le32_to_cpu(evt_trb->field[3]) & TRB_TYPE_BITMASK)) {
+               case TRB_TYPE(TRB_PORT_STATUS):
+                       xdbc_handle_port_status(evt_trb);
+                       break;
+               case TRB_TYPE(TRB_TRANSFER):
+                       xdbc_handle_tx_event(evt_trb);
+                       break;
+               default:
+                       break;
+               }
+
+               /* advance to the next trb */
+               ++(xdbcp->evt_ring.dequeue);
+               if (xdbcp->evt_ring.dequeue ==
+                               &xdbcp->evt_seg.trbs[TRBS_PER_SEGMENT]) {
+                       xdbcp->evt_ring.dequeue = xdbcp->evt_seg.trbs;
+                       xdbcp->evt_ring.cycle_state ^= 1;
+               }
+
+               evt_trb = xdbcp->evt_ring.dequeue;
+               update_erdp = true;
+       }
+
+       /* update event ring dequeue pointer */
+       if (update_erdp)
+               xdbc_write64(__pa(xdbcp->evt_ring.dequeue),
+                               &xdbcp->xdbc_reg->erdp);
+}
+
+/*
+ * Check and dispatch events in event ring. It also checks status
+ * of hardware. This function will be called from multiple threads.
+ * An atomic lock is applied to protect the access of event ring.
+ */
+static int xdbc_check_event(void)
+{
+       /* event ring is under checking by other thread? */
+       if (!test_bit(XDBC_ATOMIC_EVENT, &xdbcp->atomic_flags) &&
+                       !test_and_set_bit(XDBC_ATOMIC_EVENT,
+                       &xdbcp->atomic_flags))
+               return 0;
+
+       xdbc_handle_events();
+
+       test_and_clear_bit(XDBC_ATOMIC_EVENT, &xdbcp->atomic_flags);
+
+       return 0;
+}
+
+#define        BULK_IN_COMPLETED(p)    ((xdbcp->in_pending == (p)) && \
+                                xdbcp->in_complete)
+#define        BULK_OUT_COMPLETED(p)   ((xdbcp->out_pending == (p)) && \
+                                xdbcp->out_complete)
+
+/*
+ * Wait for a bulk-in or bulk-out transfer completion or timed out.
+ * Return count of the actually transferred bytes or error.
+ */
+static int xdbc_wait_until_bulk_done(struct xdbc_trb *trb, int loops)
+{
+       int timeout = 0;
+       bool read;
+
+       if (trb != xdbcp->in_pending &&
+                       trb != xdbcp->out_pending)
+               return -EINVAL;
+
+       read = (trb == xdbcp->in_pending);
+
+       do {
+               if (xdbc_check_event() < 0)
+                       break;
+
+               if (read && BULK_IN_COMPLETED(trb)) {
+                       if (xdbcp->in_ep_state == EP_HALTED)
+                               return -EAGAIN;
+                       else
+                               return xdbcp->in_complete_length;
+               }
+
+               if (!read && BULK_OUT_COMPLETED(trb)) {
+                       if (xdbcp->out_ep_state == EP_HALTED)
+                               return -EAGAIN;
+                       else
+                               return xdbcp->out_complete_length;
+               }
+
+               xdbc_udelay(10);
+       } while ((timeout++ < loops) || !loops);
+
+       return -EIO;
+}
+
+static int xdbc_bulk_transfer(void *data, int size, int loops, bool read)
+{
+       u64 addr;
+       u32 length, control;
+       struct xdbc_trb *trb;
+       struct xdbc_ring *ring;
+       u32 cycle;
+       int ret;
+
+       if (size > XDBC_MAX_PACKET) {
+               xdbc_trace("%s: bad parameter, size %d", __func__, size);
+               return -EINVAL;
+       }
+
+       ring = (read ? &xdbcp->in_ring : &xdbcp->out_ring);
+       trb = ring->enqueue;
+       cycle = ring->cycle_state;
+
+       length = TRB_LEN(size);
+       control = TRB_TYPE(TRB_NORMAL) | TRB_IOC;
+
+       if (cycle)
+               control &= cpu_to_le32(~TRB_CYCLE);
+       else
+               control |= cpu_to_le32(TRB_CYCLE);
+
+       if (read) {
+               memset(xdbcp->in_buf, 0, XDBC_MAX_PACKET);
+               addr = xdbcp->in_dma;
+
+               xdbcp->in_pending = trb;
+               xdbcp->in_length = size;
+               xdbcp->in_complete = 0;
+               xdbcp->in_complete_length = 0;
+       } else {
+               memcpy(xdbcp->out_buf, data, size);
+               addr = xdbcp->out_dma;
+
+               xdbcp->out_pending = trb;
+               xdbcp->out_length = size;
+               xdbcp->out_complete = 0;
+               xdbcp->out_complete_length = 0;
+       }
+
+       xdbc_queue_trb(ring, lower_32_bits(addr),
+                       upper_32_bits(addr),
+                       length, control);
+
+       /*
+        * Memory barrier to ensure hardware sees the trbs
+        * enqueued above.
+        */
+       wmb();
+       if (cycle)
+               trb->field[3] |= cpu_to_le32(cycle);
+       else
+               trb->field[3] &= cpu_to_le32(~TRB_CYCLE);
+
+       xdbc_ring_doorbell(read ? IN_EP_DOORBELL : OUT_EP_DOORBELL);
+
+       ret = xdbc_wait_until_bulk_done(trb, loops);
+
+       if (read)
+               xdbcp->in_pending = NULL;
+       else
+               xdbcp->out_pending = NULL;
+
+       if (ret > 0) {
+               if (read)
+                       memcpy(data, xdbcp->in_buf, size);
+               else
+                       memset(xdbcp->out_buf, 0, XDBC_MAX_PACKET);
+       } else {
+               xdbc_trace("%s: bulk %s transfer results in error %d\n",
+                               __func__, read ? "in" : "out", ret);
+       }
+
+       return ret;
+}
+
+int xdbc_bulk_read(void *data, int size, int loops)
+{
+       int ret;
+
+       do {
+               if (!test_bit(XDBC_ATOMIC_BULKIN, &xdbcp->atomic_flags) &&
+                               !test_and_set_bit(XDBC_ATOMIC_BULKIN,
+                               &xdbcp->atomic_flags))
+                       break;
+       } while (1);
+
+       ret = xdbc_bulk_transfer(data, size, loops, true);
+
+       test_and_clear_bit(XDBC_ATOMIC_BULKIN, &xdbcp->atomic_flags);
+
+       return ret;
+}
+
+int xdbc_bulk_write(const char *bytes, int size)
+{
+       int ret;
+
+       do {
+               if (!test_bit(XDBC_ATOMIC_BULKOUT, &xdbcp->atomic_flags) &&
+                               !test_and_set_bit(XDBC_ATOMIC_BULKOUT,
+                               &xdbcp->atomic_flags))
+                       break;
+       } while (1);
+
+       ret = xdbc_bulk_transfer((void *)bytes, size, XDBC_LOOPS, false);
+
+       test_and_clear_bit(XDBC_ATOMIC_BULKOUT, &xdbcp->atomic_flags);
+
+       return ret;
+}
diff --git a/include/linux/usb/xhci-dbc.h b/include/linux/usb/xhci-dbc.h
index fc0ef9a..289ba58 100644
--- a/include/linux/usb/xhci-dbc.h
+++ b/include/linux/usb/xhci-dbc.h
@@ -120,8 +120,17 @@ enum xdbc_page_type {
        XDBC_PAGE_TXIN,
        XDBC_PAGE_TXOUT,
        XDBC_PAGE_TABLE,
+       XDBC_PAGE_BUFFER,
 };
 
+enum xdbc_ep_state {
+       EP_DISABLED,
+       EP_RUNNING,
+       EP_HALTED,
+};
+#define        XDBC_EPID_OUT   2
+#define        XDBC_EPID_IN    1
+
 struct xdbc_state {
        /* pci device info*/
        u32             bus;
@@ -170,13 +179,34 @@ struct xdbc_state {
        /* bulk OUT endpoint */
        struct xdbc_ring        out_ring;
        struct xdbc_segment     out_seg;
+       void                    *out_buf;
+       dma_addr_t              out_dma;
+       struct xdbc_trb         *out_pending;           /* IN */
+       size_t                  out_length;             /* IN */
+       u32                     out_complete;           /* OUT */
+       size_t                  out_complete_length;    /* OUT */
+       enum xdbc_ep_state      out_ep_state;
 
        /* bulk IN endpoint */
        struct xdbc_ring        in_ring;
        struct xdbc_segment     in_seg;
+       void                    *in_buf;
+       dma_addr_t              in_dma;
+       struct xdbc_trb         *in_pending;            /* IN */
+       size_t                  in_length;              /* IN */
+       u32                     in_complete;            /* OUT */
+       size_t                  in_complete_length;     /* OUT */
+       enum xdbc_ep_state      in_ep_state;
+
+       /* atomic flags */
+       unsigned long           atomic_flags;
+#define        XDBC_ATOMIC_BULKOUT     0
+#define        XDBC_ATOMIC_BULKIN      1
+#define        XDBC_ATOMIC_EVENT       2
 };
 
 #define        XDBC_MAX_PACKET         1024
+#define        XDBC_LOOPS              1000
 
 /* door bell target */
 #define        OUT_EP_DOORBELL         0
-- 
2.1.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to