/* USB communication methods * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "board.h" #include "llist_irqsafe.h" #include "usb_buf.h" #include "utils.h" #include #include #include /*********************************************************************** * USBD Integration API ***********************************************************************/ /* call-back after (successful?) transfer of a write buffer on IN EP */ static void usb_write_cb(uint8_t *arg, uint8_t status, uint32_t transferred, uint32_t remaining) { struct msgb *msg = (struct msgb *) arg; struct usb_buffered_ep *bep = msg->dst; uint16_t ep_size = USBD_GetEndpointSize(bep->ep); unsigned long x; TRACE_DEBUG("%s (EP=0x%02x)\r\n", __func__, bep->ep); if (((msgb_length(msg) % ep_size) == 0) && (transferred == ep_size)) { /* terminate with ZLP; pass in 'msg' again as 'arg' so we get * called the second time and proceed with usb_buf_free below */ USBD_Write(bep->ep, 0, 0, (TransferCallback) &usb_write_cb, msg); return; } local_irq_save(x); bep->in_progress--; local_irq_restore(x); TRACE_DEBUG("%u: in_progress=%lu\r\n", bep->ep, bep->in_progress); if (status != USBD_STATUS_SUCCESS) TRACE_ERROR("%s error, status=%d\r\n", __func__, status); usb_buf_free(msg); } /* check if the spcified IN endpoint is idle and submit the next buffer from queue */ int usb_refill_to_host(uint8_t ep) { struct usb_buffered_ep *bep = usb_get_buf_ep(ep); struct msgb *msg; unsigned long x; int rc; #if 0 if (bep->out_from_host) { TRACE_ERROR("EP 0x%02x is not IN\r\n", bep->ep); return -EINVAL; } #endif local_irq_save(x); if (bep->in_progress) { local_irq_restore(x); return 0; } if (llist_empty(&bep->queue)) { local_irq_restore(x); return 0; } bep->in_progress++; msg = msgb_dequeue_count(&bep->queue, &bep->queue_len); local_irq_restore(x); TRACE_DEBUG("%s (EP=0x%02x), in_progress=%lu\r\n", __func__, ep, bep->in_progress); msg->dst = bep; rc = USBD_Write(ep, msgb_data(msg), msgb_length(msg), (TransferCallback) &usb_write_cb, msg); if (rc != USBD_STATUS_SUCCESS) { TRACE_ERROR("%s error %x\r\n", __func__, rc); /* re-insert to head of queue */ llist_add_irqsafe(&msg->list, &bep->queue); local_irq_save(x); bep->in_progress--; local_irq_restore(x); TRACE_DEBUG("%02x: in_progress=%lu\r\n", bep->ep, bep->in_progress); return 0; } return 1; } /* call-back after (successful?) read transfer of a buffer on OUT EP */ static void usb_read_cb(uint8_t *arg, uint8_t status, uint32_t transferred, uint32_t remaining) { struct msgb *msg = (struct msgb *) arg; struct usb_buffered_ep *bep = msg->dst; TRACE_DEBUG("%s (EP=%u, len=%lu, q=%p)\r\n", __func__, bep->ep, transferred, &bep->queue); bep->in_progress = 0; if (status != USBD_STATUS_SUCCESS) { TRACE_ERROR("%s error, status=%d\r\n", __func__, status); usb_buf_free(msg); return; } msgb_put(msg, transferred); llist_add_tail_irqsafe(&msg->list, &bep->queue); } /* refill the read queue for data received from host PC on OUT EP, if needed */ int usb_refill_from_host(uint8_t ep) { struct usb_buffered_ep *bep = usb_get_buf_ep(ep); struct msgb *msg; unsigned long x; int rc; #if 0 if (!bep->out_from_host) { TRACE_ERROR("EP 0x%02x is not OUT\r\n", bep->ep); return -EINVAL; } #endif if (bep->in_progress) return 0; TRACE_DEBUG("%s (EP=0x%02x)\r\n", __func__, bep->ep); msg = usb_buf_alloc(bep->ep); if (!msg) return -ENOMEM; msg->dst = bep; msg->l1h = msg->head; bep->in_progress = 1; rc = USBD_Read(ep, msg->head, msgb_tailroom(msg), (TransferCallback) &usb_read_cb, msg); if (rc != USBD_STATUS_SUCCESS) { TRACE_ERROR("%s error %d\r\n", __func__, rc); usb_buf_free(msg); bep->in_progress = 0; } return 1; } /* drain any buffers from the queue of the endpoint and release their memory */ int usb_drain_queue(uint8_t ep) { struct usb_buffered_ep *bep = usb_get_buf_ep(ep); struct msgb *msg; unsigned long x; int ret = 0; /* wait until no transfers are in progress anymore and block * further interrupts */ while (1) { local_irq_save(x); if (!bep->in_progress) { break; } local_irq_restore(x); /* retry */ } /* free all queued msgbs */ while ((msg = msgb_dequeue_count(&bep->queue, &bep->queue_len))) { usb_buf_free(msg); ret++; } /* re-enable interrupts and return number of free'd msgbs */ local_irq_restore(x); return ret; }