usb_queue: Introduce queue management
This commit is contained in:
@ -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 \
|
||||
|
227
firmware/hackrf_usb/usb_queue.c
Normal file
227
firmware/hackrf_usb/usb_queue.c
Normal file
@ -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 <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libopencm3/cm3/cortex.h>
|
||||
#include <libopencm3/cm3/sync.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
90
firmware/hackrf_usb/usb_queue.h
Normal file
90
firmware/hackrf_usb/usb_queue.h
Normal file
@ -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 <libopencm3/lpc43xx/usb.h>
|
||||
|
||||
#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__
|
Reference in New Issue
Block a user