/* * usb_tap.c * * Copyright (C) 2022 Sylvain Munaut * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include "console.h" #include "usb_desc_ids.h" #include "utils.h" #include "led.h" #include "usb_tap_proto.h" struct wb_tap { struct { uint32_t csr; uint32_t _rsvd[3]; } cap; #define TAP_CAP_CSR_BUSY (1 << 1) #define TAP_CAP_CSR_ACTIVE (1 << 0) uint32_t _rsvd[4]; struct { uint32_t csr; uint32_t _rsvd; uint32_t bdi; uint32_t bdo; } wr; #define TAP_WR_CSR_DF_OVFL (1 << 31) #define TAP_WR_CSR_DF_FULL (1 << 29) #define TAP_WR_CSR_DF_EMPTY (1 << 28) #define TAP_WR_CSR_GET_LEVEL(x) (((x) >> 16) & 0xfff) #define TAP_WR_CSR_DIF_OVFL (1 << 15) #define TAP_WR_CSR_DIF_UNFL (1 << 14) #define TAP_WR_CSR_DIF_FULL (1 << 13) #define TAP_WR_CSR_DIF_EMPTY (1 << 12) #define TAP_WR_CSR_DOF_OVFL (1 << 11) #define TAP_WR_CSR_DOF_FULL (1 << 9) #define TAP_WR_CSR_DOF_EMPTY (1 << 8) #define TAP_WR_CSR_DIF_FLUSH (1 << 3) #define TAP_WR_CSR_HALT_DF_OVFL (1 << 2) #define TAP_WR_CSR_FLUSH (1 << 1) #define TAP_WR_CSR_ACTIVE (1 << 0) #define TAP_WR_DESC_LEN(l) (((l)-1) << 24) #define TAP_WR_DESC_GET_LEN(x) (((x) >> 24) + 1) #define TAP_WR_DESC_ADDR(a) ((a) & 0xffffff) #define TAP_WR_DESC_GET_ADDR(x) ((x) & 0xffffff) #define TAP_WR_DESC_INVALID (1 << 31) struct { uint32_t csr; uint32_t _rsvd; uint32_t cmd_lo; uint32_t cmd_hi; } rd; #define TAP_RD_CSR_BUSY (1 << 0) #define TAP_RD_CMD_LO_LEN(l) (((((l)+3) >> 2) - 1) << 16) #define TAP_RD_CMD_LO_USB_ADDR(a) (((a) >> 2) & 0xffff) #define TAP_RD_CMD_HI_MEM_ADDR(a) ((a) & 0xffffff) } __attribute__((packed,aligned(4))); static volatile struct wb_tap * const tap_regs = (void*)(TAP_BASE); struct { /* Current core status */ enum { OFF = 0, RUN = 1, FLUSH_INT = 2, /* Flush internal FIFOs */ FLUSH_EXT = 3, /* Flush external RAM */ } state; /* Pointers in memory */ struct { uint32_t rd; /* Read */ uint32_t wrd; /* Write Done */ uint32_t wrs; /* Write Submitted */ } ptr; #define PTR_MASK 0x7fffff /* 8 MBytes */ /* In Flight descriptors */ int ifd; /* USB Buffer Descriptor index */ int bdi; /* Pending flags */ uint32_t flags; } g_state; #define DMA_WR_SIZE 128 #define USB_MAX_PKT_SIZE 64 static int _efifo_free(void) { return (g_state.ptr.rd - g_state.ptr.wrs - DMA_WR_SIZE) & PTR_MASK; } static int _efifo_filled(void) { return (g_state.ptr.wrd - g_state.ptr.rd) & PTR_MASK; } void usb_tap_reset(void) { /* State */ g_state.state = OFF; /* Reset all pointers */ g_state.ptr.rd = 0; g_state.ptr.wrd = 0; g_state.ptr.wrs = 0; /* Disable HW */ tap_regs->wr.csr = 0; /* Clear all conditions */ tap_regs->wr.csr = TAP_WR_CSR_DF_OVFL | TAP_WR_CSR_DIF_OVFL | TAP_WR_CSR_DIF_UNFL | TAP_WR_CSR_DOF_OVFL; /* Flush any input descriptor */ tap_regs->wr.csr = TAP_WR_CSR_DIF_FLUSH; g_state.ifd = 0; } int usb_tap_start(void) { /* If not stopped, nothing we can do */ if (g_state.state != OFF) return -EINVAL; /* Preload descriptors */ while (!(tap_regs->wr.csr & TAP_WR_CSR_DIF_FULL)) { tap_regs->wr.bdi = TAP_WR_DESC_LEN(DMA_WR_SIZE) | TAP_WR_DESC_ADDR(g_state.ptr.wrs); g_state.ptr.wrs += DMA_WR_SIZE; g_state.ifd++; } /* Enable the DMA write engine */ tap_regs->wr.csr = TAP_WR_CSR_HALT_DF_OVFL | TAP_WR_CSR_ACTIVE; /* Enable the capture engine */ tap_regs->cap.csr = TAP_CAP_CSR_ACTIVE; /* Clear pending overflow flag */ g_state.flags &= ~IUT_CAP_FLAGS_OVERFLOW; /* Run */ g_state.state = RUN; return 0; } int usb_tap_stop(void) { /* If we're not running, nothing we can do */ if (g_state.state != RUN) return -EINVAL; /* Disable capture engine */ tap_regs->cap.csr = 0; /* Wait for it to stop */ /* Busy can actually rise a tiny bit after we disable * the capture engine, so check several times */ for (int check=0; check<10; check++) while (tap_regs->cap.csr & TAP_CAP_CSR_BUSY); /* Enable flush mode */ tap_regs->wr.csr = TAP_WR_CSR_FLUSH | TAP_WR_CSR_ACTIVE; /* Go to flush state */ g_state.state = FLUSH_INT; return 0; } int usb_tap_flush(void) { volatile struct usb_ep *ep_regs; /* If we're stopped, we're done already */ if (g_state.state == OFF) return 0; /* If we're not in FLUSH_EXT, nothing we can do */ if (g_state.state != FLUSH_EXT) return -EINVAL; /* Clear pending USB packets */ ep_regs = &usb_ep_regs[USB_EP_TAP_IN & 0x1f].in; while (1) { /* Previous BDI */ g_state.bdi ^= 1; /* Stop if not ready */ if ((ep_regs->bd[g_state.bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) break; /* Clear */ ep_regs->bd[g_state.bdi].csr = 0; } /* Reset (will clear FIFO state & HW state) */ usb_tap_reset(); /* Clear pending overflow flag */ g_state.flags &= ~IUT_CAP_FLAGS_OVERFLOW; /* Update state */ g_state.state = OFF; return 0; } void usb_tap_debug(void) { printf("SW: state=%d, ptr=%08x/%08x/%08x (%d filled, %d free)\n", g_state.state, g_state.ptr.rd, g_state.ptr.wrd, g_state.ptr.wrs, _efifo_filled(), _efifo_free()); printf("CAP: csr=%08x\n", tap_regs->cap.csr); printf("WR : csr=%08x\n", tap_regs->wr.csr); printf("RD : csr=%08x\n", tap_regs->rd.csr); } static void _tap_fill_status(struct iut_capture_status *status) { const uint8_t map[] = { [OFF] = IUT_CAP_STATE_STOPPED, [RUN] = IUT_CAP_STATE_RUNNING, [FLUSH_INT] = IUT_CAP_STATE_FLUSHING, [FLUSH_EXT] = IUT_CAP_STATE_FLUSHING, }; status->state = map[g_state.state]; status->flags = g_state.flags; status->level = _efifo_filled(); } static void _tap_led_update(void) { enum led_state { LED_OFF = 0, LED_BLINK, LED_BREATHE, LED_ON, }; static enum led_state led_prev_state = LED_OFF; static uint32_t tick_last = 0; uint32_t tick_now; int level; enum led_state led_next_state; uint8_t r, g, b; /* Only do this every 250 ms */ tick_now = usb_get_tick(); if ((tick_now - tick_last) < 250) return; /* Schedule next update */ tick_last = tick_now; /* If we have an overflow flag, fast red blinking */ if (g_state.flags & IUT_CAP_FLAGS_OVERFLOW) { led_next_state = LED_BLINK; r = 64; g = b = 0; } /* If we're off, so is the led */ else if (g_state.state == OFF) { led_next_state = LED_OFF; r = g = b = 0; } /* We're in an active state */ else { /* Set some defaults */ led_next_state = LED_ON; g = 64; b = 0; /* Set the Red level based on buffer level */ level = _efifo_filled(); if (level) { int x = (level >> 19); r = 4 + ((x * x) >> 1); } else { r = 0; } g -= (r >> 2); /* If we're in a flush state, enable breathing */ if (g_state.state != RUN) { led_next_state = LED_BREATHE; } } /* Apply the next requested state */ /* (We can't re-write the regs each time since for periodic * effects like blink/breathe, it resets them ...) */ led_color(r, g, b); if (led_prev_state != led_next_state) { /* Save it */ led_prev_state = led_next_state; /* Set it */ switch (led_next_state) { case LED_OFF: led_state(false); break; case LED_BLINK: led_blink(true, 125, 125); led_breathe(false, 0, 0); led_state(true); break; case LED_BREATHE: led_blink(true, 200, 1000); led_breathe(true, 100, 200); led_state(true); break; case LED_ON: led_blink(false, 0, 0); led_breathe(false, 0, 0); led_state(true); break; } } } void usb_tap_poll(void) { volatile struct usb_ep *ep_regs; uint32_t v; int min_free; bool usb_sent = false; /* Refresh status indicator */ _tap_led_update(); /* Anything to do ? */ if (g_state.state == OFF) return; /* Test for error conditions */ v = tap_regs->wr.csr; if (v & TAP_WR_CSR_DF_OVFL) { // Shouldn't happen, the capture should have stopped gracefully // before this occurs panic("DMA Write input FIFO overflow\n"); } if (v & TAP_WR_CSR_DIF_OVFL) { // Really shouldn't happen since we fill that manually from // software ... panic("DMA Write DescIn FIFO overflow\n"); } if (v & TAP_WR_CSR_DIF_UNFL) { // Not great, shouldn't happen, but if it recovers in-time // and doesn't trigger TAP_WR_CSR_DF_OVFL, it's inconsequential. #if 0 printf("DMA Write DescIn FIFO underflow\n"); #endif } if (v & TAP_WR_CSR_DOF_OVFL) { // Really shouldn't happen since we empty that here before // we submit new ones in the software panic("DMA Write DescOut FIFO overflow\n"); } /* Ack all reported conditions */ tap_regs->wr.csr = v; /* Recover all done DMA writer descriptors */ do { /* Grab descriptor */ v = tap_regs->wr.bdo; /* If not valid, we're done */ if (v & TAP_WR_DESC_INVALID) break; /* Consistency check */ if (TAP_WR_DESC_GET_ADDR(v) != g_state.ptr.wrd) { panic("DMA Write DescOut pointer invalid: %08x %08x\n", TAP_WR_DESC_GET_ADDR(v), g_state.ptr.wrd); } /* Record data as filled in */ g_state.ptr.wrd = (g_state.ptr.wrd + TAP_WR_DESC_GET_LEN(v)) & PTR_MASK; /* One less in-flight descriptor */ g_state.ifd--; if (g_state.ifd < 0) { panic("DMA Write DescOut retired non-submitted descriptor\n"); } } while (1); /* Fill DMA writer descriptors */ /* We always leave some space for FLUSH_INT to happen when needed */ min_free = (g_state.state == FLUSH_INT) ? DMA_WR_SIZE : (16 * 1024); /* Fill while there is space */ while (!(tap_regs->wr.csr & TAP_WR_CSR_DIF_FULL) && (g_state.ifd < 2)) { /* Does the external FIFO have space ? */ if (_efifo_free() < min_free) { /* If we run out of space in RUN mode, we will most likely * have a HW overflow soon. To avoid that, we pre-emptively * stop now and report overflow */ if (g_state.state == RUN) { /* Report as overflow */ g_state.flags |= IUT_CAP_FLAGS_OVERFLOW; /* Stop procedure */ usb_tap_stop(); /* Debug */ printf("eFIFO Full: do graceful shutdown\n"); } /* Done */ break; } /* Queue descriptor */ tap_regs->wr.bdi = TAP_WR_DESC_LEN(DMA_WR_SIZE) | TAP_WR_DESC_ADDR(g_state.ptr.wrs); g_state.ptr.wrs = (g_state.ptr.wrs + DMA_WR_SIZE) & PTR_MASK; g_state.ifd++; } /* Fill USB data buffers */ ep_regs = &usb_ep_regs[USB_EP_TAP_IN & 0x1f].in; while ((ep_regs->bd[g_state.bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) { /* Anything to send ? */ int l = _efifo_filled(); if (!l && (g_state.state != FLUSH_EXT)) break; if (l >= USB_MAX_PKT_SIZE) l = USB_MAX_PKT_SIZE; else usb_sent = true; /* DMA */ if (l) { tap_regs->rd.cmd_hi = TAP_RD_CMD_HI_MEM_ADDR(g_state.ptr.rd); tap_regs->rd.cmd_lo = TAP_RD_CMD_LO_LEN(l) | TAP_RD_CMD_LO_USB_ADDR(ep_regs->bd[g_state.bdi].ptr); while (tap_regs->rd.csr & TAP_RD_CSR_BUSY); g_state.ptr.rd = (g_state.ptr.rd + l) & PTR_MASK; } /* Submit */ ep_regs->bd[g_state.bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(l); /* Next BDI */ g_state.bdi ^= 1; } /* Fill USB intr buffer */ ep_regs = &usb_ep_regs[USB_EP_TAP_INTR & 0x1f].in; if ((ep_regs->bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) { struct iut_capture_status status; _tap_fill_status(&status); usb_data_write(ep_regs->bd[0].ptr, &status, sizeof(status)); ep_regs->bd[g_state.bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(sizeof(status)); } /* Handle the flushing process */ if (g_state.state == FLUSH_INT) { if (tap_regs->wr.csr & TAP_WR_CSR_DF_EMPTY) { /* Input FIFO is flushed, stop HW and move on * to external flush */ tap_regs->wr.csr = 0; g_state.state = FLUSH_EXT; } } if (g_state.state == FLUSH_EXT) { if (usb_sent && (_efifo_filled() == 0)) { /* All data has been sent to host */ usb_tap_reset(); } } } static enum usb_fnd_resp _tap_set_conf(const struct usb_conf_desc *conf) { const struct usb_intf_desc *intf; g_state.bdi = 0; if (!conf) return USB_FND_SUCCESS; intf = usb_desc_find_intf(conf, USB_INTF_TAP, 0, NULL); if (!intf) return USB_FND_ERROR; usb_ep_boot(intf, USB_EP_TAP_IN, true); usb_ep_boot(intf, USB_EP_TAP_INTR, false); return USB_FND_SUCCESS; } static enum usb_fnd_resp _tap_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer) { /* Check if it's for the tap interface */ if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_VENDOR | USB_REQ_RCPT_INTF)) return USB_FND_CONTINUE; if (req->wIndex != USB_INTF_TAP) return USB_FND_CONTINUE; /* Process request */ switch (req->bRequest) { case IUT_INTF_CAPTURE_STATUS: { struct iut_capture_status status; _tap_fill_status(&status); xfer->len = sizeof(struct iut_capture_status); memcpy(xfer->data, &status, xfer->len); break; } case IUT_INTF_CAPTURE_START: { if (usb_tap_start()) return USB_FND_ERROR; break; } case IUT_INTF_CAPTURE_STOP: { if (usb_tap_stop()) return USB_FND_ERROR; break; } case IUT_INTF_BUFFER_GET_LEVEL: { uint32_t level = _efifo_filled(); xfer->len = 4; memcpy(xfer->data, &level, xfer->len); break; } case IUT_INTF_BUFFER_FLUSH: { if (usb_tap_flush()) return USB_FND_ERROR; break; } default: return USB_FND_ERROR; } return USB_FND_SUCCESS; } static struct usb_fn_drv _tap_drv = { .set_conf = _tap_set_conf, .ctrl_req = _tap_ctrl_req, }; void usb_tap_init(void) { /* USB Init */ usb_register_function_driver(&_tap_drv); /* Reset */ usb_tap_reset(); }