diff --git a/firmware/hackrf_usb/Makefile b/firmware/hackrf_usb/Makefile index 3d2b4d53..20a97295 100644 --- a/firmware/hackrf_usb/Makefile +++ b/firmware/hackrf_usb/Makefile @@ -27,6 +27,7 @@ SRC = $(BINARY).c \ usb_request.c \ usb_standard_request.c \ usb_descriptor.c \ + usb_queue.c \ ../common/fault_handler.c \ ../common/hackrf_core.c \ ../common/sgpio.c \ diff --git a/firmware/hackrf_usb/usb_queue.c b/firmware/hackrf_usb/usb_queue.c new file mode 100644 index 00000000..1b79e829 --- /dev/null +++ b/firmware/hackrf_usb/usb_queue.c @@ -0,0 +1,227 @@ +/* + * Copyright 2012 Jared Boone + * Copyright 2013 Ben Gamari + * + * This file is part of HackRF. + * + * 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, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include +#include + +#include "usb.h" +#include "usb_queue.h" + +usb_queue_t* endpoint_queues[12] = {}; + +#define USB_ENDPOINT_INDEX(endpoint_address) (((endpoint_address & 0xF) * 2) + ((endpoint_address >> 7) & 1)) + +static usb_queue_t* endpoint_queue( + const usb_endpoint_t* const endpoint +) { + uint32_t index = USB_ENDPOINT_INDEX(endpoint->address); + if (endpoint_queues[index] == NULL) while (1); + return endpoint_queues[index]; +} + +void usb_queue_init( + usb_queue_t* const queue +) { + uint32_t index = USB_ENDPOINT_INDEX(queue->endpoint->address); + if (endpoint_queues[index] != NULL) while (1); + endpoint_queues[index] = queue; + + usb_transfer_t* t = queue->free_transfers; + for (unsigned int i=0; i < queue->pool_size - 1; i++, t++) { + t->next = t+1; + t->queue = queue; + } + t->next = NULL; + t->queue = queue; +} + +/* Allocate a transfer */ +static usb_transfer_t* allocate_transfer( + usb_queue_t* const queue +) { + bool aborted; + usb_transfer_t* transfer; + if (queue->free_transfers == NULL) + return NULL; + + do { + transfer = (void *) __ldrex((uint32_t *) &queue->free_transfers); + aborted = __strex((uint32_t) transfer->next, (uint32_t *) &queue->free_transfers); + } while (aborted); + transfer->next = NULL; + return transfer; +} + +/* Place a transfer in the free list */ +static void free_transfer(usb_transfer_t* const transfer) +{ + usb_queue_t* const queue = transfer->queue; + bool aborted; + do { + transfer->next = (void *) __ldrex((uint32_t *) &queue->free_transfers); + aborted = __strex((uint32_t) transfer, (uint32_t *) &queue->free_transfers); + } while (aborted); +} + +/* Add a transfer to the end of an endpoint's queue. Returns the old + * tail or NULL is the queue was empty + */ +static usb_transfer_t* endpoint_queue_transfer( + usb_transfer_t* const transfer +) { + usb_queue_t* const queue = transfer->queue; + transfer->next = NULL; + if (queue->active != NULL) { + usb_transfer_t* t = queue->active; + while (t->next != NULL) t = t->next; + t->next = transfer; + return t; + } else { + queue->active = transfer; + return NULL; + } +} + +static void usb_queue_flush_queue(usb_queue_t* const queue) +{ + cm_disable_interrupts(); + while (queue->active) { + usb_transfer_t* transfer = queue->active; + queue->active = transfer->next; + free_transfer(transfer); + } + cm_enable_interrupts(); +} + +void usb_queue_flush_endpoint(const usb_endpoint_t* const endpoint) +{ + usb_queue_flush_queue(endpoint_queue(endpoint)); +} + +int usb_transfer_schedule( + const usb_endpoint_t* const endpoint, + void* const data, + const uint32_t maximum_length, + const transfer_completion_cb completion_cb, + void* const user_data +) { + usb_queue_t* const queue = endpoint_queue(endpoint); + usb_transfer_t* const transfer = allocate_transfer(queue); + if (transfer == NULL) return -1; + usb_transfer_descriptor_t* const td = &transfer->td; + + // Configure the transfer descriptor + td->next_dtd_pointer = USB_TD_NEXT_DTD_POINTER_TERMINATE; + td->total_bytes = + USB_TD_DTD_TOKEN_TOTAL_BYTES(maximum_length) + | USB_TD_DTD_TOKEN_IOC + | USB_TD_DTD_TOKEN_MULTO(0) + | USB_TD_DTD_TOKEN_STATUS_ACTIVE + ; + td->buffer_pointer_page[0] = (uint32_t)data; + td->buffer_pointer_page[1] = ((uint32_t)data + 0x1000) & 0xfffff000; + td->buffer_pointer_page[2] = ((uint32_t)data + 0x2000) & 0xfffff000; + td->buffer_pointer_page[3] = ((uint32_t)data + 0x3000) & 0xfffff000; + td->buffer_pointer_page[4] = ((uint32_t)data + 0x4000) & 0xfffff000; + + // Fill in transfer fields + transfer->maximum_length = maximum_length; + transfer->completion_cb = completion_cb; + transfer->user_data = user_data; + + cm_disable_interrupts(); + usb_transfer_t* tail = endpoint_queue_transfer(transfer); + if (tail == NULL) { + // The queue is currently empty, we need to re-prime + usb_endpoint_schedule_wait(queue->endpoint, &transfer->td); + } else { + // The queue is currently running, try to append + usb_endpoint_schedule_append(queue->endpoint, &tail->td, &transfer->td); + } + cm_enable_interrupts(); + return 0; +} + +int usb_transfer_schedule_block( + const usb_endpoint_t* const endpoint, + void* const data, + const uint32_t maximum_length, + const transfer_completion_cb completion_cb, + void* const user_data +) { + int ret; + do { + ret = usb_transfer_schedule(endpoint, data, maximum_length, + completion_cb, user_data); + } while (ret == -1); + return 0; +} + +int usb_transfer_schedule_ack( + const usb_endpoint_t* const endpoint +) { + return usb_transfer_schedule_block(endpoint, 0, 0, NULL, NULL); +} + +/* Called when an endpoint might have completed a transfer */ +void usb_queue_transfer_complete(usb_endpoint_t* const endpoint) +{ + usb_queue_t* const queue = endpoint_queue(endpoint); + if (queue == NULL) while(1); // Uh oh + usb_transfer_t* transfer = queue->active; + + while (transfer != NULL) { + uint8_t status = transfer->td.total_bytes; + + // Check for failures + if ( status & USB_TD_DTD_TOKEN_STATUS_HALTED + || status & USB_TD_DTD_TOKEN_STATUS_BUFFER_ERROR + || status & USB_TD_DTD_TOKEN_STATUS_TRANSACTION_ERROR) { + // TODO: Uh oh, do something useful here + while (1); + } + + // Still not finished + if (status & USB_TD_DTD_TOKEN_STATUS_ACTIVE) + break; + + // Advance the head. We need to do this before invoking the completion + // callback as it might attempt to schedule a new transfer + queue->active = transfer->next; + usb_transfer_t* next = transfer->next; + + // Invoke completion callback + unsigned int total_bytes = (transfer->td.total_bytes & USB_TD_DTD_TOKEN_TOTAL_BYTES_MASK) >> USB_TD_DTD_TOKEN_TOTAL_BYTES_SHIFT; + unsigned int transferred = transfer->maximum_length - total_bytes; + if (transfer->completion_cb) + transfer->completion_cb(transfer->user_data, transferred); + + // Advance head and free transfer + free_transfer(transfer); + transfer = next; + } +} diff --git a/firmware/hackrf_usb/usb_queue.h b/firmware/hackrf_usb/usb_queue.h new file mode 100644 index 00000000..2332022c --- /dev/null +++ b/firmware/hackrf_usb/usb_queue.h @@ -0,0 +1,90 @@ +/* + * Copyright 2012 Jared Boone + * Copyright 2013 Ben Gamari + * + * This file is part of HackRF. + * + * 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, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __USB_QUEUE_H__ +#define __USB_QUEUE_H__ + +#include + +#include "usb_type.h" + +typedef struct _usb_transfer_t usb_transfer_t; +typedef struct _usb_queue_t usb_queue_t; +typedef void (*transfer_completion_cb)(void*, unsigned int); + +// This is an opaque datatype. Thou shall not touch these members. +struct _usb_transfer_t { + struct _usb_transfer_t* next; + usb_transfer_descriptor_t td ATTR_ALIGNED(64); + unsigned int maximum_length; + struct _usb_queue_t* queue; + transfer_completion_cb completion_cb; + void* user_data; +}; + +// This is an opaque datatype. Thou shall not touch these members. +struct _usb_queue_t { + struct usb_endpoint_t* endpoint; + const unsigned int pool_size; + usb_transfer_t* volatile free_transfers; + usb_transfer_t* volatile active; +}; + +#define USB_DEFINE_QUEUE(endpoint_name, _pool_size) \ + struct _usb_transfer_t endpoint_name##_transfers[_pool_size]; \ + struct _usb_queue_t endpoint_name##_queue = { \ + .endpoint = &endpoint_name, \ + .free_transfers = endpoint_name##_transfers, \ + .pool_size = _pool_size \ + }; + +void usb_queue_flush_endpoint(const usb_endpoint_t* const endpoint); + +int usb_transfer_schedule( + const usb_endpoint_t* const endpoint, + void* const data, + const uint32_t maximum_length, + const transfer_completion_cb completion_cb, + void* const user_data +); + +int usb_transfer_schedule_block( + const usb_endpoint_t* const endpoint, + void* const data, + const uint32_t maximum_length, + const transfer_completion_cb completion_cb, + void* const user_data +); + +int usb_transfer_schedule_ack( + const usb_endpoint_t* const endpoint +); + +void usb_queue_init( + usb_queue_t* const queue +); + +void usb_queue_transfer_complete( + usb_endpoint_t* const endpoint +); + +#endif//__USB_QUEUE_H__