Satellite Observers Workbench. NOT yet complete, just published for forum posters to \"cherry pick\" pieces of code as requiered as an example.
Diff: usbeh/usbeh_controller.cpp
- Revision:
- 0:0a841b89d614
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usbeh/usbeh_controller.cpp Mon Oct 11 10:34:55 2010 +0000 @@ -0,0 +1,602 @@ +/**************************************************************************** + * Copyright 2010 Andy Kirkham, Stellar Technologies Ltd + * + * This file is part of the Satellite Observers Workbench (SOWB). + * + * SOWB 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 3 of the License, or + * (at your option) any later version. + * + * SOWB 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 SOWB. If not, see <http://www.gnu.org/licenses/>. + * + * $Id: main.cpp 5 2010-07-12 20:51:11Z ajk $ + * + ***************************************************************************/ + +#include "sowb.h" +#include "usbeh_endpoint.h" +#include "usbeh_device.h" +#include "usbeh_controller.h" +#include "usbeh_api.h" + +#include "main.h" +#include "debug.h" + +void USBEH_Controller::process(void) { + USBEH_Endpoint *endpoint; + USBEH_U16 elapsed = hcca.frameNumber - (USBEH_U16)this->frameNumber; + this->frameNumber += elapsed; + + while (this->callbacksPending) { + for (int i = 0; i < USBEH_MAX_ENDPOINTS_TOTAL; i++) { + endpoint = this->endpoints + i; + if (endpoint->currentState == USBEH_Endpoint::callbackPending) { + this->callbacksPending--; + endpoint->currentState = USBEH_Endpoint::idle; + endpoint->callback( + endpoint->device(), + endpoint->address(), + endpoint->status(), + (USBEH_U08 *)endpoint->userData, + endpoint->length, + endpoint->userData); + } + } + } + + if (this->rootHubStatusChange) { + USBEH_U32 status = USBEH_HcRhPortStatus1; + this->rootHubStatusChange = 0; + if (status >> 16) { + hubStatusChange(0, 1, status); + USBEH_HcRhPortStatus1 = status & 0xFFFF0000; + } + } + + if (this->connectCountdown) { + if (elapsed >= this->connectCountdown) { + this->connectCountdown = 0; + connect(this->connectHub, this->connectPort & 0x7F, this->connectPort & 0x80); + } + else { + this->connectCountdown -= elapsed; + } + } +} + +void USBEH_Controller::init(void) { + memset(this, 0, sizeof(USBEH_Controller)); + endpointZero.currentState = USBEH_Endpoint::notQueued; + initHW(&hcca); + delayMS(10); +} + +void USBEH_Controller::hubInterrupt(int device) { + USBEH_Device *dev = &devices[device - 1]; + + for (int i = 0; i < dev->hubPortCount; i++) { + int port = i + 1; + if (dev->hubInterruptData * (1 << port)) { + USBEH_U32 status = 0; + usbeh_api_get_port_status(device, port, &status); + if (status >> 16) { + if (connectPending && (status & USBEH_CONNECT_STATUS_CHANGE)) { + hubStatusChange(device, port, status); + if (status & USBEH_CONNECT_STATUS_CHANGE) { + usbeh_api_clear_port_feature(device, USBEH_C_PORT_CONNECTION, port); + } + if (status & USBEH_PORT_RESET_STATUS_CAHNGE) { + usbeh_api_clear_port_feature(device, USBEH_C_PORT_RESET, port); + } + } + } + } + } +} + +void USBEH_Controller::hubInterruptCallback(int device, int endpoint, int status, USBEH_U08 *data, int length, void *userData) { + USBEH_Controller *controller = (USBEH_Controller *)userData; + if (status == 0) { + controller->hubInterrupt(device); + } + usbeh_api_interrupt_transfer(device, endpoint, data, 1, hubInterruptCallback, userData); +} + +int USBEH_Controller::initHub(int device) { + USBEH_U08 buffer[16]; + int r = usbeh_api_control_transfer(device, USBEH_DEVICE_TO_HOST | USBEH_REQUEST_TYPE_CLASS | USBEH_RECIPIENT_DEVICE, USBEH_GET_DESCRIPTOR, (USBEH_DESCRIPTOR_TYPE_HUB << 8), 0, buffer, sizeof(buffer), 0, 0); + if (r < 0) return -1; + + USBEH_Device *dev = &this->devices[device - 1]; + + int ports = buffer[2]; + + for (int i = 0; i < ports; i++) { + usbeh_api_set_port_power(device, i + 1); + } + + return usbeh_api_interrupt_transfer(device, 0x81, &dev->hubInterruptData,1, hubInterruptCallback, this); +} + +int USBEH_Controller::transfer(USBEH_Endpoint *endpoint, int token, USBEH_U08 *data, int len, int state) { + + int toggle = 0; + + if (endpoint->address() == 0) { + toggle = (token == USBEH_TOKEN_SETUP) ? USBEH_TD_TOGGLE_0 : USBEH_TD_TOGGLE_1; + } + + if (token != USBEH_TOKEN_SETUP) { + token = (token == USBEH_TOKEN_IN ? USBEH_TD_IN : USBEH_TD_OUT); + } + + USBEH_HCTD *head = &endpoint->tdHead; + USBEH_HCTD *tail = &this->commonTail; + + head->control = USBEH_TD_ROUNDING | token | USBEH_TD_DELAY_INT(0) | toggle | USBEH_TD_CC; + head->currentBufferPointer = (USBEH_U32)data; + head->bufferEnd = (USBEH_U32)(data + len - 1); + head->next = (USBEH_U32)tail; + + USBEH_HCED *ed = &endpoint->endpointDescriptor; + ed->headTd = (USBEH_U32)head | (ed->headTd & 0x00000002); + ed->tailTd = (USBEH_U32)tail; + + switch (endpoint->flags & 3) { + case USBEH_ENDPOINT_CONTROL: + USBEH_HcControlHeadED = endpoint->queue(USBEH_HcControlHeadED); + endpoint->currentState = state; + USBEH_HcCommandStatus = USBEH_HcCommandStatus | USBEH_CONTROL_LIST_FILLED; + USBEH_HcControl |= USBEH_CONTROL_LIST_ENABLE; + break; + case USBEH_ENDPOINT_BULK: + USBEH_HcBulkHeadED = endpoint->queue(USBEH_HcBulkHeadED); + endpoint->currentState = state; + USBEH_HcCommandStatus = USBEH_HcCommandStatus | USBEH_BULK_LIST_FILLED; + USBEH_HcControl |= USBEH_BULK_LIST_ENABLE; + break; + case USBEH_ENDPOINT_INTERRUPT: + hcca.interruptTable[0] = endpoint->queue(hcca.interruptTable[0]); + endpoint->currentState = state; + USBEH_HcControl |= USBEH_PERIODIC_LIST_ENABLE; + break; + } + + return 0; +} + +bool USBEH_Controller::remove(USBEH_HCED *ed, volatile USBEH_HCED **queue) { + if (*queue == 0) return false; + if (*queue == (volatile USBEH_HCED *)ed) { + *queue = (volatile USBEH_HCED *)ed->next; + return true; + } + + volatile USBEH_HCED *head = *queue; + while (head) { + if (head->next == (USBEH_U32)ed) { + head->next = ed->next; + return true; + } + head = (volatile USBEH_HCED *)head->next; + } + + return false; +} + +void USBEH_Controller::release(USBEH_Endpoint *endpoint) { + if (endpoint->currentState != USBEH_Endpoint::notQueued) { + USBEH_HCED *ed = (USBEH_HCED *)endpoint; + ed->control |= 0x4000; + switch (endpoint->flags & 0x3) { + case USBEH_ENDPOINT_CONTROL: + remove(ed, (volatile USBEH_HCED **)&USBEH_HcControlHeadED); + break; + case USBEH_ENDPOINT_BULK: + remove(ed, (volatile USBEH_HCED **)&USBEH_HcBulkHeadED); + break; + case USBEH_ENDPOINT_INTERRUPT: + for (int i = 0; i < 32; i++) { + remove(ed, (volatile USBEH_HCED **)&hcca.interruptTable[i]); + } + break; + } + + USBEH_U16 fn = hcca.frameNumber; + while (fn == hcca.frameNumber) ; + } + + memset(endpoint, 0, sizeof(USBEH_Endpoint)); +} + +int USBEH_Controller::addEndpoint(int device, USBEH_endpointDescriptor *endpoint) { + return addEndpoint(device, endpoint->bEndpointAddress, endpoint->bmAttributes, endpoint->wMaxPacketSize, endpoint->bInterval); +} + +int USBEH_Controller::addEndpoint(int device, int endpoint, int attributes, int maxPacketSize, int interval) { + USBEH_Device *dev = &this->devices[device - 1]; + USBEH_Endpoint *ep = allocateEndpoint(device, endpoint, attributes, maxPacketSize); + if (!ep) { + return -1; + } + dev->setEndpointIndex(endpoint, ep - this->endpoints); + ep->endpointDescriptor.control |= dev->flags; + return 0; + +} + +USBEH_Endpoint * USBEH_Controller::allocateEndpoint(int device, int endpointAddress, int type, int maxPacketSize) { + for (int i = 0; i < USBEH_MAX_ENDPOINTS_TOTAL; i++) { + USBEH_Endpoint *ep = &this->endpoints[i]; + if (ep->currentState == 0) { + ep->flags = (endpointAddress & 0x80) | (type & 0x3); + ep->currentState = USBEH_Endpoint::notQueued; + ep->endpointDescriptor.control = (maxPacketSize << 16) | ((endpointAddress & 0x7F) << 7) | device; + return ep; + } + } + return 0; +} + +int USBEH_Controller::addDevice(int hub, int port, bool isLowSpeed) { + int device = addDeviceCore(hub, port, isLowSpeed); + if (device < 0) { + disconnect(hub, port); + resetPort(hub, port); + return -1; + } + return device; +} + +int USBEH_Controller::addDeviceCore(int hub, int port, bool isLowSpeed) { + + int lowSpeed = isLowSpeed ? 0x2000 : 0; + + USBEH_deviceDescriptor desc; + + endpointZero.endpointDescriptor.control = (8 << 16) | lowSpeed; + + int result = usbeh_api_get_descriptor(0, USBEH_DESCRIPTOR_TYPE_DEVICE, 0, (USBEH_U08 *)&desc, 8); + + if (result < 0) { + #ifdef DEBUG_USB_DRIVER + debug_printf("usbeh_api_get_descriptor() failed with %d (0x%x) at line %d\r\n", result, result, __LINE__); + #endif + return result; + } + + endpointZero.endpointDescriptor.control = (desc.bMaxPacketSize << 16) | lowSpeed; + + result = usbeh_api_get_descriptor(0, USBEH_DESCRIPTOR_TYPE_DEVICE, 0, (USBEH_U08 *)&desc, sizeof(desc)); + if (result < 0) { + #ifdef DEBUG_USB_DRIVER + debug_printf("usbeh_api_get_descriptor() failed with %d at line %d\r\n", result, __LINE__); + #endif + return result; + } + + int device = 0; + for (int i = 0; i < USBEH_MAX_DEVICES; i++) { + if (devices[i].port == 0) { + device = i + 1; + break; + } + } + + if (!device) { + return -1; + } + + result = usbeh_api_set_address(0, device); + if (result) { + return result; + } + + delayMS(2); + + USBEH_Device *dev = &devices[device - 1]; + dev->init(&desc, hub, port, device, lowSpeed); + addEndpoint(device, 0, USBEH_ENDPOINT_CONTROL, desc.bMaxPacketSize, 0); + this->connectPending = 0; + + if ((result = usbeh_api_get_descriptor(device, USBEH_DESCRIPTOR_TYPE_DEVICE, 0, (USBEH_U08 *)&desc, sizeof(desc))) < 0) { + #ifdef DEBUG_USB_DRIVER + debug_printf("usbeh_api_get_descriptor() failed with %d at line %d\r\n", result, __LINE__); + #endif + return result; + } + + result = setConfigurationAndInterface(device, 1, -1, &desc); + + if (desc.bDeviceClass == CLASS_HUB) { + initHub(device); + } + + return device; +} + +int USBEH_Controller::setConfigurationAndInterface(int device, int configuration, int interfaceNumber, USBEH_deviceDescriptor *desc) { + USBEH_U08 buffer[255]; + USBEH_interfaceDescriptor *found[16]; + USBEH_endpointDescriptor *ed; + + for (int i = 0; i < 16; i++) { + found[i] = (USBEH_interfaceDescriptor *)NULL; + } + + int err = usbeh_api_get_descriptor(device, USBEH_DESCRIPTOR_TYPE_CONFIGURATION, 0, buffer, sizeof(buffer)); + if (err < 0) { + #ifdef DEBUG_USB_DRIVER + debug_printf("GET_DESCRIPTOR failed at line %d\r\n", __LINE__); + #endif + return err; + } + + err = usbeh_api_set_configuration(device, configuration); + if (err < 0) { + return err; + } + + int interfaceCounter = 0; + int len = buffer[2] | (buffer[3] << 8); + USBEH_U08 *d = buffer; + USBEH_U08 *end = d + len; + while (d < end) { + //printf("Testing descriptor type %02x\n\r", d[1]); + if (d[1] == USBEH_DESCRIPTOR_TYPE_INTERFACE) { + //printf(" Found interface descriptor type %02x\n\r", d[1]); + USBEH_interfaceDescriptor *id = (USBEH_interfaceDescriptor *)d; + if (interfaceNumber == -1 || id->bInterfaceNumber == interfaceNumber) { + found[interfaceCounter++] = id; + d += d[0]; + while (d < end && d[1] != USBEH_DESCRIPTOR_TYPE_INTERFACE) { + switch (d[1]) { + case USBEH_DESCRIPTOR_TYPE_ENDPOINT: + ed = (USBEH_endpointDescriptor *)d; + //printf(" Adding endpoint 0x%02x for interface %d\r\n", ed->bEndpointAddress, id->bInterfaceNumber); + addEndpoint(device, ed); + break; + default: + // Skip unknown descriptor. + //printf(" Unknown descriptor type: %02x\r\n", d[1]); + break; + } + d += d[0]; + } + } + } + else { + d += d[0]; + } + } + + if (interfaceCounter == 0) { + return USBEH_ERR_INTERFACE_NOT_FOUND; + } + + usbeh_api_on_load_device(device, desc, found); + + return 0; +} + +void USBEH_Controller::processDoneQueue(USBEH_U32 tdList) { + USBEH_Endpoint *endpoint; + USBEH_HCTD *list = reverse((USBEH_HCTD *)tdList); + while (list) { + endpoint = (USBEH_Endpoint *)(list - 1); + list = (USBEH_HCTD *)list->next; + int ep = endpoint->address(); + bool in = endpoint->flags & 0x80; + int status = (endpoint->tdHead.control >> 28) & 0xF; + + if (status != 0) { + endpoint->currentState = USBEH_Endpoint::idle; + } + else { + switch (endpoint->currentState) { + case USBEH_Endpoint::setupQueued: + if (endpoint->length == 0) { + transfer(endpoint, in ? USBEH_TOKEN_OUT : USBEH_TOKEN_IN, 0, 0, USBEH_Endpoint::statusQueued); + } + else { + transfer(endpoint, in ? USBEH_TOKEN_IN : USBEH_TOKEN_OUT, (USBEH_U08 *)endpoint->data, endpoint->length, USBEH_Endpoint::dataQueued); + } + break; + + case USBEH_Endpoint::dataQueued: + if (endpoint->tdHead.currentBufferPointer) { + endpoint->length = endpoint->tdHead.currentBufferPointer - (USBEH_U32)endpoint->data; + } + + if (ep == 0) { + transfer(endpoint, in ? USBEH_TOKEN_OUT : USBEH_TOKEN_IN, 0, 0, USBEH_Endpoint::statusQueued); + } + else { + endpoint->currentState = USBEH_Endpoint::idle; + } + break; + + case USBEH_Endpoint::statusQueued: + endpoint->currentState = USBEH_Endpoint::idle; + break; + } + } + + if (endpoint->callback && endpoint->currentState == USBEH_Endpoint::idle) { + //if (endpoint->address() != 0x81) printf("\r\nCallback pending for 0x%02X\r\n", endpoint->address()); + endpoint->currentState = USBEH_Endpoint::callbackPending; + this->callbacksPending++; + } + } +} + +void USBEH_Controller::resetPort(int hub, int port) { + this->connectPending++; + if (hub == 0) { + USBEH_HcRhPortStatus1 = USBEH_PORT_RESET_STATUS; + } + else { + usbeh_api_set_port_reset(hub, port); + } +} + +void USBEH_Controller::connect(int hub, int port, bool lowSpeed) { + #ifdef DEBUG_USB_DRIVER + debug_printf("%s called at line %d\r\n", __FUNCTION__, __LINE__); + #endif + addDevice(hub, port, lowSpeed); +} + +void USBEH_Controller::disconnect(int hub, int port) { + for (int i = 0; i < USBEH_MAX_DEVICES; i++) { + USBEH_Device *dev = this->devices + i; + if (dev->port == port && dev->hub == hub) { + for (int p = 0; p < dev->hubPortCount; p++) { + disconnect(i + 1, p + 1); + } + for (int j = 1; j < USBEH_MAX_ENDPOINTS_PER_DEVICE * 2; j += 2) { + USBEH_U08 endpointIndex = dev->endpoints[j]; + if (endpointIndex != 0xFF) { + release(this->endpoints + endpointIndex); + } + dev->port = 0; + dev->flags = 0; + return; + } + } + } +} + +void USBEH_Controller::hubStatusChange(int hub, int port, USBEH_U32 status) { + if (status & USBEH_CONNECT_STATUS_CHANGE) { + if (status & USBEH_CURRENT_CONNECT_STATUS) { + resetPort(hub, port); + } + else { + disconnect(hub, port); + } + } + + if (status & USBEH_PORT_RESET_STATUS_CHANGE) { + if (!(status & USBEH_PORT_RESET_STATUS)) { + this->connectCountdown = 200; + if (status & USBEH_LOW_SPEED_DEVICE) { + port |= 0x80; + } + this->connectHub = hub; + this->connectPort = port; + } + } +} + +void USBEH_Controller::delayMS(int ms) { + USBEH_U16 f = ms + hcca.frameNumber; + while (f != hcca.frameNumber) ; +} + +void USBEH_Controller::initHW(USBEH_HCCA *cca) { + NVIC_DisableIRQ(USB_IRQn); + + LPC_SC->PCONP |= (1UL << 31); + LPC_USB->USBClkCtrl |= USBEH_CLOCK_MASK; + while ((LPC_USB->USBClkSt & USBEH_CLOCK_MASK) != USBEH_CLOCK_MASK); + + USBEH_OTGStCtrl |= 1; + USBEH_USBClkCtrl &= ~USBEH_PORTSEL_CLK_EN; + + LPC_PINCON->PINSEL1 &= ~( (3<<26) | (3<<28) ); + LPC_PINCON->PINSEL1 |= ( (1<<26) | (1<<28)); + + USBEH_HcControl = 0; + USBEH_HcControlHeadED = 0; + USBEH_HcBulkHeadED = 0; + USBEH_HcCommandStatus = USBEH_HOST_CONTROLLER_RESET; + USBEH_HcFmInterval = USBEH_DEFAULT_FMINTERVAL; + USBEH_HcPeriodicStart = USBEH_FRAMEINTERVAL * 90 / 100; + + USBEH_HcControl = (USBEH_HcControl & (~USBEH_HOST_CONTROLLER_FUNCTIONAL_STATE)) | USBEH_OPERATIONAL_MASK; + USBEH_HcRhStatus = USBEH_SET_GLOBAL_POWER; + + USBEH_HcHCCA = (USBEH_U32)cca; + USBEH_HcInterruptStatus |= USBEH_HcInterruptStatus; + USBEH_HcInterruptEnable |= USBEH_MASTER_IRQ_ENABLE | USBEH_WRITEBACK_DONE_HEAD | USBEH_ROOT_HUB_STATUS_CHANGE | USBEH_FRAME_NUMBER_OVERFLOW | USBEH_START_OF_FRAME; + + NVIC_EnableIRQ(USB_IRQn); + while (cca->frameNumber < 10); +} + +USBEH_HCTD * USBEH_Controller::reverse(USBEH_HCTD *current) { + USBEH_HCTD *result = NULL, *temp; + while (current) { + temp = (USBEH_HCTD *)current->next; + current->next = (USBEH_U32)result; + result = current; + current = temp; + } + return result; +} + +USBEH_Endpoint * USBEH_Controller::getEndpoint(int device, int ep) { + if (device == 0) { + return &endpointZero; + } + if (device > USBEH_MAX_DEVICES) { + return 0; + } + int i = devices[device - 1].getEndpointIndex(ep); + if (i == -1) { + return 0; + } + return endpoints + i; +} + +/* The controller is defined withinn the API section. */ +extern USBEH_Controller sys_usb_controller; + +USBEH_SOF_COUNTER *sof_counter_head = NULL; + +/* The USB interrupt handler. */ +extern "C" void USB_IRQHandler (void) __irq { + + USBEH_U32 int_status = USBEH_HcInterruptStatus; + + if (int_status & USBEH_ROOT_HUB_STATUS_CHANGE) { + sys_usb_controller.rootHubStatusChange++; + } + + USBEH_U32 head = 0; + + if (int_status & USBEH_WRITEBACK_DONE_HEAD) { + head = sys_usb_controller.hcca.doneHead; + sys_usb_controller.hcca.doneHead = 0; + } + + if (int_status & USBEH_START_OF_FRAME) { + for (USBEH_SOF_COUNTER *list = sof_counter_head; list != NULL; list = list->next) { + if (list->mode & USBEH_SOF_COUNTER_DEC && list->flag == 0) { + if (list->counter > 0) list->counter--; + if (list->counter == 0) { + list->flag = 1; + list->counter = list->reload; + if (list->callback != NULL) { + (list->callback)((struct _sof_counter *)list); + } + } + } + } + } + + USBEH_HcInterruptStatus = int_status; + + if (head) { + sys_usb_controller.processDoneQueue(head); + } +} +