/* * Copyright (c) 2015 Brian Swetland * Copyright (c) 2008 Google, Inc. * * Use of this source code is governed by a MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT */ #include #include #include #include #include #include #include #include #include #include static_assert(sizeof(usb_dqh_t) == 64, ""); static_assert(sizeof(usb_dtd_t) == 32, ""); #include #include "udc-common.h" #define F_LL_INIT 1 #define F_UDC_INIT 2 // NOTE: I cheat a bit with the locking because this is a UP Cortex-M // NOTE: device. I use spinlocks for code that might be called from // NOTE: userspace or irq context, but for the irq-only code I don't // NOTE: bother with locking because it's impossible for it to execute // NOTE: while the lock is held from userspace. typedef struct { u32 base; spin_lock_t lock; usb_dqh_t *qh; usb_dtd_t *dtd_freelist; udc_endpoint_t *ep0in; udc_endpoint_t *ep0out; udc_request_t *ep0req; u8 txd[8]; u8 rxd[8]; udc_endpoint_t *ept_list; uint8_t online; uint8_t highspeed; uint8_t config_value; uint8_t flags; udc_device_t *device; udc_gadget_t *gadget; uint32_t ept_alloc_table; } usb_t; static usb_t USB; typedef struct usb_request { udc_request_t req; struct usb_request *next; usb_dtd_t *dtd; } usb_request_t; struct udc_endpoint { udc_endpoint_t *next; usb_dqh_t *head; usb_request_t *req; usb_request_t *last; usb_t *usb; uint32_t bit; uint16_t maxpkt; uint8_t num; uint8_t in; }; // ---- endpoint management #if 1 #define DBG(x...) do {} while(0) #else #define DBG(x...) dprintf(INFO, x) #endif static udc_endpoint_t *_udc_endpoint_alloc(usb_t *usb, unsigned num, unsigned in, unsigned max_pkt) { udc_endpoint_t *ept; unsigned cfg; ept = malloc(sizeof(*ept)); ept->maxpkt = max_pkt; ept->num = num; ept->in = !!in; ept->req = 0; ept->last = 0; ept->usb = usb; cfg = DQH_CFG_MAXPKT(max_pkt) | DQH_CFG_ZLT; if (ept->in) { ept->bit = EPT_TX(ept->num); } else { ept->bit = EPT_RX(ept->num); if (num == 0) { cfg |= DQH_CFG_IOS; } } ept->head = usb->qh + (num * 2) + (ept->in); ept->head->config = cfg; ept->next = usb->ept_list; usb->ept_list = ept; DBG("ept%d %s @%p/%p max=%d bit=%x\n", num, in ? "in":"out", ept, ept->head, max_pkt, ept->bit); return ept; } udc_endpoint_t *udc_endpoint_alloc(unsigned type, unsigned maxpkt) { udc_endpoint_t *ept; unsigned n; unsigned in = !!(type & 0x80); if (!(USB.flags & F_UDC_INIT)) { panic("udc_init() must be called before udc_endpoint_alloc()\n"); } for (n = 1; n < 6; n++) { unsigned bit = in ? EPT_TX(n) : EPT_RX(n); if (USB.ept_alloc_table & bit) { continue; } if ((ept = _udc_endpoint_alloc(&USB, n, in, maxpkt))) { USB.ept_alloc_table |= bit; } return ept; } return 0; } void udc_endpoint_free(struct udc_endpoint *ept) { // todo } static void handle_ept_complete(struct udc_endpoint *ept); static void endpoint_flush(usb_t *usb, udc_endpoint_t *ept) { if (ept->req) { // flush outstanding transfers writel(ept->bit, usb->base + USB_ENDPTFLUSH); while (readl(usb->base + USB_ENDPTFLUSH)) ; while (ept->req) { handle_ept_complete(ept); } } } static void endpoint_reset(usb_t *usb, udc_endpoint_t *ept) { unsigned n = readl(usb->base + USB_ENDPTCTRL(ept->num)); n |= ept->in ? EPCTRL_TXR : EPCTRL_RXR; writel(n, usb->base + USB_ENDPTCTRL(ept->num)); } static void endpoint_enable(usb_t *usb, udc_endpoint_t *ept, unsigned yes) { unsigned n = readl(usb->base + USB_ENDPTCTRL(ept->num)); if (yes) { if (ept->in) { n |= (EPCTRL_TXE | EPCTRL_TXR | EPCTRL_TX_BULK); } else { n |= (EPCTRL_RXE | EPCTRL_RXR | EPCTRL_RX_BULK); } if (ept->num != 0) { // todo: support non-max-sized packet sizes if (usb->highspeed) { ept->head->config = DQH_CFG_MAXPKT(512) | DQH_CFG_ZLT; } else { ept->head->config = DQH_CFG_MAXPKT(64) | DQH_CFG_ZLT; } } } writel(n, usb->base + USB_ENDPTCTRL(ept->num)); } // ---- request management udc_request_t *udc_request_alloc(void) { spin_lock_saved_state_t state; usb_request_t *req; if ((req = malloc(sizeof(*req))) == NULL) { return NULL; } spin_lock_irqsave(&USB.lock, state); if (USB.dtd_freelist == NULL) { spin_unlock_irqrestore(&USB.lock, state); free(req); return NULL; } else { req->dtd = USB.dtd_freelist; USB.dtd_freelist = req->dtd->next; spin_unlock_irqrestore(&USB.lock, state); req->req.buffer = 0; req->req.length = 0; return &req->req; } } void udc_request_free(struct udc_request *req) { // todo: check if active? free(req); } int udc_request_queue(udc_endpoint_t *ept, struct udc_request *_req) { spin_lock_saved_state_t state; usb_request_t *req = (usb_request_t *) _req; usb_dtd_t *dtd = req->dtd; unsigned phys = (unsigned) req->req.buffer; int ret = 0; dtd->next_dtd = 1; // terminate bit dtd->config = DTD_LEN(req->req.length) | DTD_IOC | DTD_ACTIVE; dtd->bptr0 = phys; phys &= 0xfffff000; dtd->bptr1 = phys + 0x1000; dtd->bptr2 = phys + 0x2000; dtd->bptr3 = phys + 0x3000; dtd->bptr4 = phys + 0x4000; req->next = 0; spin_lock_irqsave(&ept->usb->lock, state); if (!USB.online && ept->num) { ret = -1; } else if (ept->req) { // already a transfer in flight, add us to the list // we'll get queue'd by the irq handler when it's our turn ept->last->next = req; } else { ept->head->next_dtd = (unsigned) dtd; ept->head->dtd_config = 0; DSB; writel(ept->bit, ept->usb->base + USB_ENDPTPRIME); ept->req = req; } ept->last = req; spin_unlock_irqrestore(&ept->usb->lock, state); DBG("ept%d %s queue req=%p\n", ept->num, ept->in ? "in" : "out", req); return ret; } static void handle_ept_complete(struct udc_endpoint *ept) { usb_request_t *req; usb_dtd_t *dtd; unsigned actual; int status; DBG("ept%d %s complete req=%p\n", ept->num, ept->in ? "in" : "out", ept->req); if ((req = ept->req)) { if (req->next) { // queue next req to hw ept->head->next_dtd = (unsigned) req->next->dtd; ept->head->dtd_config = 0; DSB; writel(ept->bit, ept->usb->base + USB_ENDPTPRIME); ept->req = req->next; } else { ept->req = 0; ept->last = 0; } dtd = req->dtd; if (dtd->config & 0xff) { actual = 0; status = -1; dprintf(INFO, "EP%d/%s FAIL nfo=%x pg0=%x\n", ept->num, ept->in ? "in" : "out", dtd->config, dtd->bptr0); } else { actual = req->req.length - ((dtd->config >> 16) & 0x7fff); status = 0; } if (req->req.complete) { req->req.complete(&req->req, actual, status); } } } static void setup_ack(usb_t *usb) { usb->ep0req->complete = 0; usb->ep0req->length = 0; udc_request_queue(usb->ep0in, usb->ep0req); } static void ep0in_complete(struct udc_request *req, unsigned actual, int status) { usb_t *usb = (usb_t *) req->context; DBG("ep0in_complete %p %d %d\n", req, actual, status); if (status == 0) { req->length = 0; req->complete = 0; udc_request_queue(usb->ep0out, req); } } static void setup_tx(usb_t *usb, void *buf, unsigned len) { DBG("setup_tx %p %d\n", buf, len); usb->ep0req->buffer = buf; usb->ep0req->complete = ep0in_complete; usb->ep0req->length = len; udc_request_queue(usb->ep0in, usb->ep0req); } static void notify_gadgets(udc_gadget_t *gadget, unsigned event) { while (gadget) { if (gadget->notify) { gadget->notify(gadget, event); } gadget = gadget->next; } } #define SETUP(type,request) (((type) << 8) | (request)) static void handle_setup(usb_t *usb) { union setup_packet s; // setup procedure, per databook // a. clear setup status by writing and waiting for 0 (1-2uS) writel(1, usb->base + USB_ENDPTSETUPSTAT); while (readl(usb->base + USB_ENDPTSETUPSTAT) & 1) ; do { // b. write 1 to tripwire writel(CMD_RUN | CMD_SUTW, usb->base + USB_CMD); // c. extract setup data s.w0 = usb->qh[0].setup0; s.w1 = usb->qh[0].setup1; // d. if tripwire clear, retry } while ((readl(usb->base + USB_CMD) & CMD_SUTW) == 0); // e. clear tripwire writel(CMD_RUN, usb->base + USB_CMD); // flush any pending io from previous setup transactions usb->ep0in->req = 0; usb->ep0out->req = 0; // f. process packet // g. ensure setup status is 0 DBG("setup 0x%02x 0x%02x %d %d %d\n", s.type, s.request, s.value, s.index, s.length); switch (SETUP(s.type,s.request)) { case SETUP(DEVICE_READ, GET_STATUS): { static unsigned zero = 0; if (s.length == 2) { setup_tx(usb, &zero, 2); return; } break; } case SETUP(DEVICE_READ, GET_DESCRIPTOR): { struct udc_descriptor *desc = udc_descriptor_find(s.value); if (desc) { unsigned len = desc->len; if (len > s.length) len = s.length; setup_tx(usb, desc->data, len); return; } break; } case SETUP(DEVICE_READ, GET_CONFIGURATION): if ((s.value == 0) && (s.index == 0) && (s.length == 1)) { setup_tx(usb, &usb->config_value, 1); return; } break; case SETUP(DEVICE_WRITE, SET_CONFIGURATION): if (s.value == 1) { struct udc_endpoint *ept; /* enable endpoints */ for (ept = usb->ept_list; ept; ept = ept->next) { if (ept->num != 0) { endpoint_enable(usb, ept, 1); } } usb->config_value = 1; notify_gadgets(usb->gadget, UDC_EVENT_ONLINE); } else { writel(0, usb->base + USB_ENDPTCTRL(1)); usb->config_value = 0; notify_gadgets(usb->gadget, UDC_EVENT_OFFLINE); } setup_ack(usb); usb->online = s.value ? 1 : 0; return; case SETUP(DEVICE_WRITE, SET_ADDRESS): // write address delayed (will take effect after the next IN txn) writel(((s.value & 0x7F) << 25) | (1 << 24), usb->base + USB_DEVICEADDR); setup_ack(usb); return; case SETUP(INTERFACE_WRITE, SET_INTERFACE): goto stall; case SETUP(ENDPOINT_WRITE, CLEAR_FEATURE): { udc_endpoint_t *ept; unsigned num = s.index & 15; unsigned in = !!(s.index & 0x80); if ((s.value != 0) || (s.length != 0)) { break; } DBG("clr feat %d %d\n", num, in); for (ept = usb->ept_list; ept; ept = ept->next) { if ((ept->num == num) && (ept->in == in)) { endpoint_flush(usb, ept); // todo: if callback requeues this could be ugly... endpoint_reset(usb, ept); setup_ack(usb); return; } } break; } } dprintf(INFO, "udc: stall %02x %02x %04x %04x %04x\n", s.type, s.request, s.value, s.index, s.length); stall: writel(EPCTRL_RXS | EPCTRL_TXS, usb->base + USB_ENDPTCTRL(0)); } int lpc43xx_usb_init(u32 dmabase, size_t dmasize) { usb_t *usb = &USB; printf("usb_init()\n"); if ((dmabase & 0x7FF) || (dmasize < 1024)) { return -1; } usb->qh = (void *) dmabase; usb->dtd_freelist = NULL; memset(usb->qh, 0, dmasize); usb->base = USB0_BASE; dmabase += 768; dmasize -= 768; while (dmasize > sizeof(usb_dtd_t)) { usb_dtd_t *dtd = (void *) dmabase; dtd->next = usb->dtd_freelist; usb->dtd_freelist = dtd; dmabase += sizeof(usb_dtd_t); dmasize -= sizeof(usb_dtd_t); } writel(CMD_RST, usb->base + USB_CMD); while (readl(usb->base + USB_CMD) & CMD_RST) ; printf("usb_init(): reset ok\n"); thread_sleep(250); // enable USB0 PHY via CREG0 writel(readl(0x40043004) & (~0x20), 0x40043004); writel(MODE_DEVICE | MODE_SLOM, usb->base + USB_MODE); // enable termination in OTG control (required for device mode) writel(OTG_OT, usb->base + USB_OTGSC); writel((u32) usb->qh, usb->base + USB_ENDPOINTLISTADDR); usb->flags |= F_LL_INIT; return 0; } static void usb_enable(usb_t *usb, int yes) { if (yes) { writel(INTR_UE | INTR_UEE | INTR_PCE | INTR_SEE | INTR_URE, usb->base + USB_INTR); writel(CMD_RUN, usb->base + USB_CMD); NVIC_EnableIRQ(USB0_IRQn); } else { NVIC_DisableIRQ(USB0_IRQn); writel(CMD_STOP, usb->base + USB_CMD); } } void lpc43xx_USB0_IRQ(void) { udc_endpoint_t *ept; usb_t *usb = &USB; int ret = 0; unsigned n; arm_cm_irq_entry(); n = readl(usb->base + USB_STS); writel(n, usb->base + USB_STS); if (n & STS_URI) { // reset procedure, per databook // 1. clear setup token semaphores writel(readl(usb->base + USB_ENDPTSETUPSTAT), usb->base + USB_ENDPTSETUPSTAT); // 2. clear completion status bits writel(readl(usb->base + USB_ENDPTCOMPLETE), usb->base + USB_ENDPTCOMPLETE); // 3. cancel primed transfers while (readl(usb->base + USB_ENDPTPRIME)) ; writel(0xFFFFFFFF, usb->base + USB_ENDPTFLUSH); // 4. ensure we finished while reset still active if (!(readl(usb->base + USB_PORTSC1) & PORTSC1_RC)) { printf("usb: failed to reset in time\n"); } // 5. free active DTDs usb->online = 0; usb->config_value = 0; notify_gadgets(usb->gadget, UDC_EVENT_OFFLINE); for (ept = usb->ept_list; ept; ept = ept->next) { if (ept->req) { ept->req->dtd->config = DTD_HALTED; handle_ept_complete(ept); } } } if (n & STS_PCI) { unsigned x = readl(usb->base + USB_PORTSC1); usb->highspeed = (x & PORTSC1_HSP) ? 1 : 0; } if (n & (STS_UI | STS_UEI)) { if (readl(usb->base + USB_ENDPTSETUPSTAT) & 1) { handle_setup(usb); } n = readl(usb->base + USB_ENDPTCOMPLETE); writel(n, usb->base + USB_ENDPTCOMPLETE); for (ept = usb->ept_list; ept; ept = ept->next) { if (n & ept->bit) { handle_ept_complete(ept); ret = INT_RESCHEDULE; } } } if (n & STS_SEI) { panic(""); } arm_cm_irq_exit(ret); } // ---- UDC API int udc_init(struct udc_device *dev) { USB.device = dev; USB.ep0out = _udc_endpoint_alloc(&USB, 0, 0, 64); USB.ep0in = _udc_endpoint_alloc(&USB, 0, 1, 64); USB.ep0req = udc_request_alloc(); USB.ep0req->context = &USB; USB.flags |= F_UDC_INIT; return 0; } int udc_register_gadget(udc_gadget_t *gadget) { if (USB.gadget) { udc_gadget_t *last = USB.gadget; while (last->next) { last = last->next; } last->next = gadget; } else { USB.gadget = gadget; } gadget->next = NULL; return 0; } void udc_ept_desc_fill(udc_endpoint_t *ept, unsigned char *data) { data[0] = 7; data[1] = TYPE_ENDPOINT; data[2] = ept->num | (ept->in ? 0x80 : 0x00); data[3] = 0x02; // bulk -- the only kind we support data[4] = ept->maxpkt; data[5] = ept->maxpkt >> 8; data[6] = ept->in ? 0x00 : 0x01; } int udc_start(void) { usb_t *usb = &USB; dprintf(INFO, "udc_start()\n"); if (!(usb->flags & F_LL_INIT)) { panic("udc cannot start before hw init\n"); } if (!usb->device) { panic("udc cannot start before init\n"); } if (!usb->gadget) { panic("udc has no gadget registered\n"); } udc_create_descriptors(usb->device, usb->gadget); usb_enable(usb, 1); return 0; } int udc_stop(void) { usb_enable(&USB, 0); thread_sleep(10); return 0; }