/* 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ #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 buffer */ 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; unsigned long x; TRACE_DEBUG("%s (EP=0x%02x)\r\n", __func__, bep->ep); local_irq_save(x); bep->in_progress--; local_irq_restore(x); TRACE_DEBUG("%u: in_progress=%d\n", bep->ep, bep->in_progress); if (status != USBD_STATUS_SUCCESS) TRACE_ERROR("%s error, status=%d\n", __func__, status); usb_buf_free(msg); } 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(&bep->queue); local_irq_restore(x); TRACE_DEBUG("%s (EP=0x%02x), in_progress=%d\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\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=%d\n", bep->ep, bep->in_progress); return 0; } return 1; } /* call-back after (successful?) transfer of a buffer */ 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=%u, 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\n", __func__, status); usb_buf_free(msg); return; } msgb_put(msg, transferred); llist_add_tail_irqsafe(&msg->list, &bep->queue); } 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 %s\n", __func__, rc); usb_buf_free(msg); bep->in_progress = 0; } return 1; } 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(&bep->queue))) { usb_buf_free(msg); ret++; } /* re-enable interrupts and return number of free'd msgbs */ local_irq_restore(x); return ret; }