/* * Implementation of generic HCD */ #include /* memcpy */ #include /* errno with sign */ #include #include #include #include #include /*===========================================================================* * Local declarations * *===========================================================================*/ /* Thread to handle device logic */ static void hcd_device_thread(void *); /* Procedure that locks device thread forever in case of error/completion */ static void hcd_device_finish(hcd_device_state *, const char *); /* Procedure that finds device, waiting for given EP interrupt */ static hcd_device_state * hcd_get_child_for_ep(hcd_device_state *, hcd_reg1); /* For HCD level, hub handling */ static void hcd_add_child(hcd_device_state *, hcd_reg1, hcd_speed); static void hcd_delete_child(hcd_device_state *, hcd_reg1); static void hcd_disconnect_tree(hcd_device_state *); static void hcd_dump_tree(hcd_device_state *, hcd_reg1); /* Typical USD device communication procedures */ static int hcd_enumerate(hcd_device_state *); static int hcd_get_device_descriptor(hcd_device_state *); static int hcd_set_address(hcd_device_state *); static int hcd_get_descriptor_tree(hcd_device_state *); static int hcd_set_configuration(hcd_device_state *, hcd_reg1); static void hcd_handle_urb(hcd_device_state *); static void hcd_complete_urb(hcd_device_state *); static int hcd_control_urb(hcd_device_state *, hcd_urb *); static int hcd_non_control_urb(hcd_device_state *, hcd_urb *); /* For internal use by more general methods */ static int hcd_setup_packet(hcd_device_state *, hcd_ctrlrequest *, hcd_reg1); static int hcd_finish_setup(hcd_device_state *, void *); static int hcd_data_transfer(hcd_device_state *, hcd_datarequest *); /* TODO: This is not meant to be explicitly visible outside DDEKit library * but there is no other way to set thread priority for now */ extern void _ddekit_thread_set_myprio(int); /*===========================================================================* * Local definitions * *===========================================================================*/ /* TODO: This was added for compatibility with DDELinux drivers that * allow receiving less data than expected in URB, without error */ #define HCD_ANY_LENGTH 0xFFFFFFFFu /* This doesn't seem to be specified in standard but abnormal values * are unlikely so check for this was added below */ #define HCD_SANE_DESCRIPTOR_LENGTH 2048 /*===========================================================================* * hcd_handle_event * *===========================================================================*/ void hcd_handle_event(hcd_device_state * device, hcd_event event, hcd_reg1 val) { DEBUG_DUMP; /* Invalid device may be supplied */ if (EXIT_SUCCESS != hcd_check_device(device)) { USB_MSG("No device available for event: 0x%02X, value: 0x%02X", event, val); return; } #ifdef HCD_DUMP_DEVICE_TREE /* This can be unlocked to dump current USB device tree on event */ { /* Go to the base of USB device tree and * print the current state of it */ hcd_device_state * base; base = device; while (NULL != base->parent) base = base->parent; USB_MSG("Current state of USB device tree:"); hcd_dump_tree(base, 0); } #endif /* Handle event and forward control to device thread when required */ switch (event) { case HCD_EVENT_CONNECTED: USB_ASSERT((HCD_STATE_DISCONNECTED == device->state), "Device not marked as 'disconnected' " "for 'connection' event"); /* Try creating new thread for device */ if (hcd_connect_device(device, hcd_device_thread)) USB_MSG("Device creation failed, nothing more " "will happen until disconnected"); break; case HCD_EVENT_DISCONNECTED: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Device is marked as 'disconnected' " "for 'disconnection' event"); /* Make this device and all attached children * disconnect recursively */ hcd_disconnect_tree(device); break; case HCD_EVENT_PORT_LS_CONNECTED: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Device is marked as 'disconnected' " "for 'hub port LS attach' event"); USB_MSG("Low speed device connected at " "hub 0x%08X, port %u", device, val); hcd_add_child(device, val, HCD_SPEED_LOW); break; case HCD_EVENT_PORT_FS_CONNECTED: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Device is marked as 'disconnected' " "for 'hub port FS attach' event"); USB_MSG("Full speed device connected at " "hub 0x%08X, port %u", device, val); hcd_add_child(device, val, HCD_SPEED_FULL); break; case HCD_EVENT_PORT_HS_CONNECTED: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Device is marked as 'disconnected' " "for 'hub port HS attach' event"); USB_MSG("High speed device connected at " "hub 0x%08X, port %u", device, val); hcd_add_child(device, val, HCD_SPEED_HIGH); break; case HCD_EVENT_PORT_DISCONNECTED: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Device is marked as 'disconnected' " "for 'hub port detach' event"); hcd_delete_child(device, val); USB_MSG("Device disconnected from " "hub 0x%08X, port %u", device, val); break; case HCD_EVENT_ENDPOINT: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Parent device is marked as 'disconnected' " "for 'endpoint' event"); /* Alters 'device' when endpoint is allocated to * child rather than parent (hub), which allows * proper thread to continue */ device = hcd_get_child_for_ep(device, val); /* Check if anything at all, waits for such endpoint */ if (device) /* Allow device thread, waiting for endpoint * event, to continue with its logic */ hcd_device_continue(device, event, val); else USB_MSG("No device waits for endpoint %u", val); break; case HCD_EVENT_URB: USB_ASSERT((HCD_STATE_DISCONNECTED != device->state), "Device is marked as 'disconnected' " "for 'URB' event"); /* Allow device thread to continue with it's logic */ hcd_device_continue(device, event, val); break; default: USB_ASSERT(0, "Illegal HCD event"); } } /*===========================================================================* * hcd_update_port * *===========================================================================*/ void hcd_update_port(hcd_driver_state * driver, hcd_event event) { DEBUG_DUMP; switch (event) { case HCD_EVENT_CONNECTED: /* Check if already assigned */ USB_ASSERT(NULL == driver->port_device, "Device was already connected before " "receiving 'connection' event"); /* Assign new blank device */ driver->port_device = hcd_new_device(); /* Associate this device with driver */ driver->port_device->driver = driver; break; case HCD_EVENT_DISCONNECTED: /* Check if already released */ USB_ASSERT(NULL != driver->port_device, "Device was already disconnected before " "receiving 'disconnection' event"); /* Release device */ hcd_delete_device(driver->port_device); /* Clear port device pointer */ driver->port_device = NULL; break; default: USB_ASSERT(0, "Illegal port update event"); } } /*===========================================================================* * hcd_device_thread * *===========================================================================*/ static void hcd_device_thread(void * thread_args) { hcd_device_state * this_device; DEBUG_DUMP; /* Set device thread priority higher so it * won't change context unless explicitly locked */ _ddekit_thread_set_myprio(2); /* Retrieve structures from generic data */ this_device = (hcd_device_state *)thread_args; /* Enumeration sequence */ if (EXIT_SUCCESS != hcd_enumerate(this_device)) hcd_device_finish(this_device, "USB device enumeration failed"); /* Tell everyone that device was connected */ hcd_connect_cb(this_device); /* Fully configured */ this_device->state = HCD_STATE_CONNECTED; USB_DBG("Waiting for URBs"); /* Start handling URB's */ for(;;) { /* Block and wait for something like 'submit URB' */ hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); hcd_handle_urb(this_device); } /* Finish device handling to avoid leaving thread */ hcd_device_finish(this_device, "USB device handling completed"); } /*===========================================================================* * hcd_device_finish * *===========================================================================*/ static void hcd_device_finish(hcd_device_state * this_device, const char * finish_msg) { DEBUG_DUMP; USB_MSG("USB device handling finished with message: '%s'", finish_msg); /* Lock forever */ for (;;) { hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); USB_MSG("Failed attempt to continue finished thread"); } } /*===========================================================================* * hcd_get_child_for_ep * *===========================================================================*/ static hcd_device_state * hcd_get_child_for_ep(hcd_device_state * device, hcd_reg1 ep) { hcd_device_state * child_found; hcd_device_state * final_found; hcd_device_state * child; hcd_reg1 child_num; DEBUG_DUMP; /* Nothing yet */ final_found = NULL; /* Check if any children (and their children) wait for EP event */ /* Every device in tree is checked every time so errors can be found */ for (child_num = 0; child_num < HCD_CHILDREN; child_num++) { /* Device, to be checked for EP event recursively... */ child = device->child[child_num]; /* ...but only if attached */ if (NULL != child) { /* Look deeper first */ child_found = hcd_get_child_for_ep(child, ep); if (NULL != child_found) { /* Only one device can wait for EP event */ USB_ASSERT((NULL == final_found), "More than one device waits for EP"); /* Remember what was found */ final_found = child_found; } } } /* Check this device last */ if ((HCD_EVENT_ENDPOINT == device->wait_event) && (ep == device->wait_ep)) { /* Only one device can wait for EP event */ USB_ASSERT((NULL == final_found), "More than one device waits for EP"); /* Remember what was found */ final_found = device; } return final_found; } /*===========================================================================* * hcd_add_child * *===========================================================================*/ static void hcd_add_child(hcd_device_state * parent, hcd_reg1 port, hcd_speed speed) { DEBUG_DUMP; USB_ASSERT(port < HCD_CHILDREN, "Port number too high"); USB_ASSERT(NULL == parent->child[port], "Child device already exists"); /* Basic addition */ parent->child[port] = hcd_new_device(); parent->child[port]->parent = parent; /* Inherit parent's driver */ parent->child[port]->driver = parent->driver; /* Remember speed, determined by hub driver */ parent->child[port]->speed = speed; /* Try creating new thread for device */ if (hcd_connect_device(parent->child[port], hcd_device_thread)) USB_MSG("Device creation failed, nothing more " "will happen until disconnected"); } /*===========================================================================* * hcd_delete_child * *===========================================================================*/ static void hcd_delete_child(hcd_device_state * parent, hcd_reg1 port) { hcd_device_state * child; DEBUG_DUMP; USB_ASSERT(port < HCD_CHILDREN, "Port number too high"); child = parent->child[port]; /* Child to be detached */ USB_ASSERT(NULL != child, "Child device does not exist"); /* Make this child device and all its attached children * disconnect recursively */ hcd_disconnect_tree(child); /* Delete to release device itself */ hcd_delete_device(child); /* Mark as released */ parent->child[port] = NULL; } /*===========================================================================* * hcd_disconnect_tree * *===========================================================================*/ static void hcd_disconnect_tree(hcd_device_state * device) { hcd_reg1 child_num; DEBUG_DUMP; /* Generate disconnect event for all children */ for (child_num = 0; child_num < HCD_CHILDREN; child_num++) { if (NULL != device->child[child_num]) hcd_handle_event(device, HCD_EVENT_PORT_DISCONNECTED, child_num); } /* If this device was detached during URB handling, some steps must be * taken to ensure that no process/thread is waiting for completion */ if (NULL != device->urb) { USB_MSG("Unplugged device had unhandled URB"); /* Tell device driver that device was detached */ /* TODO: ENODEV selected for that */ device->urb->inout_status = ENODEV; hcd_complete_urb(device); } /* If connect callback was used before, call * it's equivalent to signal disconnection */ if (HCD_STATE_CONNECTED == device->state) hcd_disconnect_cb(device); /* Handle device disconnection (freeing memory etc.) */ hcd_disconnect_device(device); } /*===========================================================================* * hcd_dump_tree * *===========================================================================*/ static void hcd_dump_tree(hcd_device_state * device, hcd_reg1 level) { hcd_reg1 child_num; /* DEBUG_DUMP; */ /* Let's keep tree output cleaner */ USB_MSG("Device on level %03u: 0x%08X", level, device); /* Traverse device tree recursively */ for (child_num = 0; child_num < HCD_CHILDREN; child_num++) { if (NULL != device->child[child_num]) hcd_dump_tree(device->child[child_num], level + 1); } } /*===========================================================================* * hcd_enumerate * *===========================================================================*/ static int hcd_enumerate(hcd_device_state * this_device) { hcd_driver_state * d; DEBUG_DUMP; d = this_device->driver; /* Having a parent device also means being reseted by it * so only reset devices that have no parents */ if (NULL == this_device->parent) { /* First let driver reset device */ if (EXIT_SUCCESS != d->reset_device(d->private_data, &(this_device->speed))) { USB_MSG("Failed to reset device"); return EXIT_FAILURE; } } /* Default MaxPacketSize, based on speed */ if (HCD_SPEED_HIGH == this_device->speed) this_device->max_packet_size = HCD_HS_MAXPACKETSIZE; else this_device->max_packet_size = HCD_LS_MAXPACKETSIZE; /* Get device descriptor */ if (EXIT_SUCCESS != hcd_get_device_descriptor(this_device)) { USB_MSG("Failed to get device descriptor"); return EXIT_FAILURE; } /* Remember max packet size from device descriptor */ this_device->max_packet_size = this_device->device_desc.bMaxPacketSize; /* Dump device descriptor in debug mode */ #ifdef DEBUG { hcd_device_descriptor * d; d = &(this_device->device_desc); USB_DBG("<>"); USB_DBG("bLength %02X", d->bLength); USB_DBG("bDescriptorType %02X", d->bDescriptorType); USB_DBG("bcdUSB %04X", UGETW(d->bcdUSB)); USB_DBG("bDeviceClass %02X", d->bDeviceClass); USB_DBG("bDeviceSubClass %02X", d->bDeviceSubClass); USB_DBG("bDeviceProtocol %02X", d->bDeviceProtocol); USB_DBG("bMaxPacketSize %02X", d->bMaxPacketSize); USB_DBG("idVendor %04X", UGETW(d->idVendor)); USB_DBG("idProduct %04X", UGETW(d->idProduct)); USB_DBG("bcdDevice %04X", UGETW(d->bcdDevice)); USB_DBG("iManufacturer %02X", d->iManufacturer); USB_DBG("iProduct %02X", d->iProduct); USB_DBG("iSerialNumber %02X", d->iSerialNumber); USB_DBG("bNumConfigurations %02X", d->bNumConfigurations); } #endif /* Set reserved address */ if (EXIT_SUCCESS != hcd_set_address(this_device)) { USB_MSG("Failed to set device address"); return EXIT_FAILURE; } /* Sleep 5msec to allow addressing */ hcd_os_nanosleep(HCD_NANOSLEEP_MSEC(5)); /* Remember what was assigned in hardware */ this_device->current_address = this_device->reserved_address; /* Get other descriptors */ if (EXIT_SUCCESS != hcd_get_descriptor_tree(this_device)) { USB_MSG("Failed to get configuration descriptor tree"); return EXIT_FAILURE; } /* TODO: Always use first configuration, as there is no support for * multiple configurations in DDEKit/devman and devices rarely have * more than one anyway */ /* Set configuration */ if (EXIT_SUCCESS != hcd_set_configuration(this_device, HCD_SET_CONFIG_NUM(HCD_DEFAULT_CONFIG))) { USB_MSG("Failed to set configuration"); return EXIT_FAILURE; } USB_DBG("Enumeration completed"); return EXIT_SUCCESS; } /*===========================================================================* * hcd_get_device_descriptor * *===========================================================================*/ static int hcd_get_device_descriptor(hcd_device_state * this_device) { hcd_ctrlrequest setup; hcd_urb urb; DEBUG_DUMP; /* TODO: magic numbers, no header for these */ /* Format setup packet */ setup.bRequestType = 0x80; /* IN */ setup.bRequest = 0x06; /* Get descriptor */ setup.wValue = 0x0100; /* Device */ setup.wIndex = 0x0000; setup.wLength = sizeof(this_device->device_desc); /* Prepare self-URB */ memset(&urb, 0, sizeof(urb)); urb.direction = HCD_DIRECTION_IN; urb.endpoint = HCD_DEFAULT_EP; urb.in_setup = &setup; urb.inout_data = (hcd_reg1 *)(&(this_device->device_desc)); urb.target_device = this_device; urb.type = HCD_TRANSFER_CONTROL; /* Put it to be scheduled and wait for control to get back */ hcd_schedule_internal_urb(&urb); hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); hcd_handle_urb(this_device); /* Check if URB submission completed successfully */ if (urb.inout_status) { USB_MSG("URB submission failed"); return EXIT_FAILURE; } /* Check if expected size was received */ if (urb.out_size != setup.wLength) { USB_MSG("URB submission returned invalid amount of data"); return EXIT_FAILURE; } return EXIT_SUCCESS; } /*===========================================================================* * hcd_set_address * *===========================================================================*/ static int hcd_set_address(hcd_device_state * this_device) { hcd_ctrlrequest setup; hcd_urb urb; DEBUG_DUMP; /* Check for legal USB device address (must be non-zero as well) */ USB_ASSERT((this_device->reserved_address > HCD_DEFAULT_ADDR) && (this_device->reserved_address <= HCD_LAST_ADDR), "Illegal device address supplied"); /* TODO: magic numbers, no header for these */ setup.bRequestType = 0x00; /* OUT */ setup.bRequest = 0x05; /* Set address */ setup.wValue = this_device->reserved_address; setup.wIndex = 0x0000; setup.wLength = 0x0000; /* Prepare self-URB */ memset(&urb, 0, sizeof(urb)); urb.direction = HCD_DIRECTION_OUT; urb.endpoint = HCD_DEFAULT_EP; urb.in_setup = &setup; urb.inout_data = NULL; urb.target_device = this_device; urb.type = HCD_TRANSFER_CONTROL; /* Put it to be scheduled and wait for control to get back */ hcd_schedule_internal_urb(&urb); hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); hcd_handle_urb(this_device); /* Check if URB submission completed successfully */ if (urb.inout_status) { USB_MSG("URB submission failed"); return EXIT_FAILURE; } /* Check if expected size was received */ if (urb.out_size != setup.wLength) { USB_MSG("URB submission returned invalid amount of data"); return EXIT_FAILURE; } return EXIT_SUCCESS; } /*===========================================================================* * hcd_get_descriptor_tree * *===========================================================================*/ static int hcd_get_descriptor_tree(hcd_device_state * this_device) { hcd_config_descriptor temp_config_descriptor; hcd_ctrlrequest setup; hcd_urb urb; /* To receive data */ hcd_reg4 expected_length; hcd_reg1 * expected_buffer; int retval; DEBUG_DUMP; /* Initially */ retval = EXIT_FAILURE; expected_buffer = NULL; /* First part gets only configuration to find out total length */ { /* TODO: Default configuration is hard-coded * but others are rarely used anyway */ /* TODO: magic numbers, no header for these */ setup.bRequestType = 0x80; /* IN */ setup.bRequest = 0x06; /* Get descriptor */ setup.wValue = 0x0200 | HCD_DEFAULT_CONFIG; setup.wIndex = 0x0000; setup.wLength = sizeof(temp_config_descriptor); /* Prepare self-URB */ memset(&urb, 0, sizeof(urb)); urb.direction = HCD_DIRECTION_IN; urb.endpoint = HCD_DEFAULT_EP; urb.in_setup = &setup; urb.inout_data = (hcd_reg1 *)(&temp_config_descriptor); urb.target_device = this_device; urb.type = HCD_TRANSFER_CONTROL; /* Put it to be scheduled and wait for control to get back */ hcd_schedule_internal_urb(&urb); hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); hcd_handle_urb(this_device); /* Check if URB submission completed successfully */ if (urb.inout_status) { USB_MSG("URB submission failed"); goto FINISH; } /* Check if expected size was received */ if (urb.out_size != setup.wLength) { USB_MSG("URB submission returned " "invalid amount of data"); goto FINISH; } } /* Get total expected length */ expected_length = UGETW(temp_config_descriptor.wTotalLength); /* Check for abnormal value */ if (expected_length > HCD_SANE_DESCRIPTOR_LENGTH) { USB_MSG("Total descriptor length declared is too high"); goto FINISH; } /* Get descriptor buffer to hold everything expected */ if (NULL == (expected_buffer = malloc(expected_length))) { USB_MSG("Descriptor allocation failed"); goto FINISH; } /* Second part gets all available descriptors */ { /* TODO: Default configuration is hard-coded * but others are rarely used anyway */ /* TODO: magic numbers, no header for these */ setup.bRequestType = 0x80; /* IN */ setup.bRequest = 0x06; /* Get descriptor */ setup.wValue = 0x0200 | HCD_DEFAULT_CONFIG; setup.wIndex = 0x0000; setup.wLength = expected_length; /* Prepare self-URB */ memset(&urb, 0, sizeof(urb)); urb.direction = HCD_DIRECTION_IN; urb.endpoint = HCD_DEFAULT_EP; urb.in_setup = &setup; urb.inout_data = expected_buffer; urb.target_device = this_device; urb.type = HCD_TRANSFER_CONTROL; /* Put it to be scheduled and wait for control to get back */ hcd_schedule_internal_urb(&urb); hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); hcd_handle_urb(this_device); /* Check if URB submission completed successfully */ if (urb.inout_status) { USB_MSG("URB submission failed"); goto FINISH; } /* Check if expected size was received */ if (urb.out_size != setup.wLength) { USB_MSG("URB submission returned " "invalid amount of data"); goto FINISH; } } if (EXIT_SUCCESS != hcd_buffer_to_tree(expected_buffer, (int)expected_length, &(this_device->config_tree))) { USB_MSG("Broken descriptor data"); goto FINISH; } /* No errors occurred */ retval = EXIT_SUCCESS; FINISH: /* Release allocated buffer */ if (expected_buffer) free(expected_buffer); return retval; } /*===========================================================================* * hcd_set_configuration * *===========================================================================*/ static int hcd_set_configuration(hcd_device_state * this_device, hcd_reg1 configuration) { hcd_ctrlrequest setup; hcd_urb urb; DEBUG_DUMP; /* TODO: magic numbers, no header for these */ setup.bRequestType = 0x00; /* OUT */ setup.bRequest = 0x09; /* Set configuration */ setup.wValue = configuration; setup.wIndex = 0x0000; setup.wLength = 0x0000; /* Prepare self-URB */ memset(&urb, 0, sizeof(urb)); urb.direction = HCD_DIRECTION_OUT; urb.endpoint = HCD_DEFAULT_EP; urb.in_setup = &setup; urb.inout_data = NULL; urb.target_device = this_device; urb.type = HCD_TRANSFER_CONTROL; /* Put it to be scheduled and wait for control to get back */ hcd_schedule_internal_urb(&urb); hcd_device_wait(this_device, HCD_EVENT_URB, HCD_UNUSED_VAL); hcd_handle_urb(this_device); return urb.inout_status; } /*===========================================================================* * hcd_handle_urb * *===========================================================================*/ static void hcd_handle_urb(hcd_device_state * this_device) { hcd_urb * urb; int transfer_status; DEBUG_DUMP; /* Retrieve URB */ urb = this_device->urb; USB_ASSERT(NULL != urb, "No URB supplied"); USB_ASSERT(this_device == urb->target_device, "Unknown device for URB"); /* Only if URB parsing was completed... */ if (EXIT_SUCCESS == urb->inout_status) { transfer_status = EXIT_FAILURE; /* ...check for URB to handle */ switch (urb->type) { case HCD_TRANSFER_CONTROL: transfer_status = hcd_control_urb( this_device, urb); break; case HCD_TRANSFER_BULK: case HCD_TRANSFER_INTERRUPT: transfer_status = hcd_non_control_urb( this_device, urb); break; default: USB_MSG("Unsupported transfer type 0x%02X", (int)urb->type); break; } /* In case of error, only dump message */ if (EXIT_SUCCESS != transfer_status) USB_MSG("USB transfer failed"); } else USB_MSG("Invalid URB supplied"); /* Perform completion routine */ hcd_complete_urb(this_device); } /*===========================================================================* * hcd_complete_urb * *===========================================================================*/ static void hcd_complete_urb(hcd_device_state * this_device) { DEBUG_DUMP; /* Signal scheduler that URB was handled */ this_device->urb->handled(this_device->urb); /* Use this callback in case it is an external URB */ hcd_completion_cb(this_device->urb); /* Make device forget about this URB */ this_device->urb = NULL; } /*===========================================================================* * hcd_control_urb * *===========================================================================*/ static int hcd_control_urb(hcd_device_state * this_device, hcd_urb * urb) { DEBUG_DUMP; /* Assume bad values unless something different occurs later */ urb->inout_status = EINVAL; /* Must have setup packet for control transfer */ if (NULL == urb->in_setup) { USB_MSG("No setup packet in URB, for control transfer"); return EXIT_FAILURE; } /* TODO: Only EP0 can have control transfer */ if (HCD_DEFAULT_EP != urb->endpoint) { USB_MSG("Control transfer for non zero EP"); return EXIT_FAILURE; } /* Setup and URB directions should match */ if (((urb->in_setup->bRequestType >> 7) & 0x01) != urb->direction) { USB_MSG("URB Direction mismatch"); return EXIT_FAILURE; } /* Send setup packet */ if (EXIT_SUCCESS != hcd_setup_packet(this_device, urb->in_setup, urb->endpoint)) { USB_MSG("Sending URB setup packet, failed"); urb->inout_status = EPIPE; return EXIT_FAILURE; } /* Put what was read back into URB */ if (EXIT_SUCCESS != hcd_finish_setup(this_device, urb->inout_data)) return EXIT_FAILURE; /* Write transfer output info to URB */ urb->out_size = (hcd_reg4)this_device->control_len; urb->inout_status = EXIT_SUCCESS; return EXIT_SUCCESS; } /*===========================================================================* * hcd_non_control_urb * *===========================================================================*/ static int hcd_non_control_urb(hcd_device_state * this_device, hcd_urb * urb) { hcd_endpoint * e; hcd_datarequest request; DEBUG_DUMP; /* Assume bad values unless something different occurs later */ urb->inout_status = EINVAL; /* Must have data buffer to send/receive */ if (NULL == urb->inout_data) { USB_MSG("No data packet in URB"); return EXIT_FAILURE; } if (HCD_DEFAULT_EP == urb->endpoint) { USB_MSG("Non-control transfer for EP0"); return EXIT_FAILURE; } /* Check if EP number is valid within remembered descriptor tree */ e = hcd_tree_find_ep(&(this_device->config_tree), urb->endpoint); if (NULL == e) { USB_MSG("Invalid EP number for this device"); return EXIT_FAILURE; } /* Check if remembered descriptor direction, matches the one in URB */ if (((e->descriptor.bEndpointAddress >> 7) & 0x01) != urb->direction) { USB_MSG("EP direction mismatch"); return EXIT_FAILURE; } /* Check if remembered type matches */ if (UE_GET_XFERTYPE(e->descriptor.bmAttributes) != urb->type) { USB_MSG("EP type mismatch"); return EXIT_FAILURE; } /* Check if remembered interval matches */ if ((hcd_reg1)e->descriptor.bInterval != urb->interval) { USB_MSG("EP interval mismatch"); return EXIT_FAILURE; } /* Assign URB values to data request structure */ request.type = urb->type; request.endpoint = urb->endpoint; request.direction = urb->direction; request.data_left = (int)urb->in_size; request.data = urb->inout_data; /* TODO: This was changed to allow software scheduler to work correctly * by switching URBs when they NAK, rather than waiting forever if URB * which requires such waiting, was issued */ #if 0 request.interval = urb->interval; #else request.interval = HCD_DEFAULT_NAKLIMIT; #endif /* Assign to let know how much data can be transfered at a time */ request.max_packet_size = UGETW(e->descriptor.wMaxPacketSize); /* Let know how to configure EP for speed */ request.speed = this_device->speed; /* Start sending data */ if (EXIT_SUCCESS != hcd_data_transfer(this_device, &request)) { USB_MSG("URB non-control transfer, failed"); urb->inout_status = EPIPE; return EXIT_FAILURE; } /* Transfer successfully completed update URB */ USB_ASSERT(request.data_left >= 0, "Negative amount of transfer data remains"); urb->out_size = urb->in_size - (hcd_reg4)request.data_left; urb->inout_status = EXIT_SUCCESS; return EXIT_SUCCESS; } /*===========================================================================* * hcd_setup_packet * *===========================================================================*/ static int hcd_setup_packet(hcd_device_state * this_device, hcd_ctrlrequest * setup, hcd_reg1 ep) { hcd_driver_state * d; hcd_reg1 * current_byte; int rx_len; DEBUG_DUMP; /* Should have been set at enumeration or with default values */ USB_ASSERT(this_device->max_packet_size >= HCD_LS_MAXPACKETSIZE, "Illegal MaxPacketSize"); USB_ASSERT(ep <= HCD_LAST_EP, "Invalid EP number"); USB_ASSERT(this_device->current_address <= HCD_LAST_ADDR, "Invalid device address"); /* Initially... */ d = this_device->driver; current_byte = this_device->control_data;/* Start reading into this */ this_device->control_len = 0; /* Nothing read yet */ /* Set parameters for further communication */ d->setup_device(d->private_data, ep, this_device->current_address, NULL, NULL); /* Send setup packet */ d->setup_stage(d->private_data, setup); /* Wait for response */ hcd_device_wait(this_device, HCD_EVENT_ENDPOINT, ep); /* Check response */ if (EXIT_SUCCESS != d->check_error(d->private_data, HCD_TRANSFER_CONTROL, ep, HCD_DIRECTION_UNUSED)) return EXIT_FAILURE; /* For data packets... */ if (setup->wLength > 0) { /* TODO: magic number */ /* ...IN data packets */ if (setup->bRequestType & 0x80) { for(;;) { /* Try getting data */ d->in_data_stage(d->private_data); /* Wait for response */ hcd_device_wait(this_device, HCD_EVENT_ENDPOINT, ep); /* Check response */ if (EXIT_SUCCESS != d->check_error( d->private_data, HCD_TRANSFER_CONTROL, ep, HCD_DIRECTION_UNUSED)) return EXIT_FAILURE; /* Read data received as response */ rx_len = d->read_data(d->private_data, current_byte, ep); /* Increment */ current_byte += rx_len; this_device->control_len += rx_len; /* If max sized packet was read (or more)... */ if (rx_len >= (int)this_device->max_packet_size) /* ...try reading next packet even if * zero bytes may be received */ continue; /* If less than max data was read... */ if (rx_len < (int)this_device->max_packet_size) /* ...it must have been * the last packet */ break; /* Unreachable during normal operation */ USB_MSG("rx_len: %d; max_packet_size: %d", rx_len, this_device->max_packet_size); USB_ASSERT(0, "Illegal state of data " "receive operation"); } } else { /* TODO: Unimplemented OUT DATA stage */ d->out_data_stage(d->private_data); return EXIT_FAILURE; } } /* Status stages */ if (setup->bRequestType & 0x80) { /* Try confirming data receive */ d->out_status_stage(d->private_data); /* Wait for response */ hcd_device_wait(this_device, HCD_EVENT_ENDPOINT, ep); /* Check response */ if (EXIT_SUCCESS != d->check_error(d->private_data, HCD_TRANSFER_CONTROL, ep, HCD_DIRECTION_UNUSED)) return EXIT_FAILURE; } else { /* Try getting status confirmation */ d->in_status_stage(d->private_data); /* Wait for response */ hcd_device_wait(this_device, HCD_EVENT_ENDPOINT, ep); /* Check response */ if (EXIT_SUCCESS != d->check_error(d->private_data, HCD_TRANSFER_CONTROL, ep, HCD_DIRECTION_UNUSED)) return EXIT_FAILURE; /* Read zero data from response to clear registers */ if (0 != d->read_data(d->private_data, NULL, ep)) return EXIT_FAILURE; } return EXIT_SUCCESS; } /*===========================================================================* * hcd_finish_setup * *===========================================================================*/ static int hcd_finish_setup(hcd_device_state * this_device, void * output) { DEBUG_DUMP; /* Validate setup transfer output length */ if (this_device->control_len < 0) { USB_MSG("Negative control transfer output length"); return EXIT_FAILURE; } /* Length is valid but output not supplied */ if (NULL == output) return EXIT_SUCCESS; /* Finally, copy when needed */ memcpy(output, this_device->control_data, this_device->control_len); return EXIT_SUCCESS; } /*===========================================================================* * hcd_data_transfer * *===========================================================================*/ static int hcd_data_transfer(hcd_device_state * this_device, hcd_datarequest * request) { hcd_driver_state * d; hcd_datarequest temp_req; int transfer_len; DEBUG_DUMP; USB_ASSERT((request->endpoint <= HCD_LAST_EP) && (request->endpoint > HCD_DEFAULT_EP), "Invalid EP number"); USB_ASSERT((this_device->current_address <= HCD_LAST_ADDR) && (this_device->current_address > HCD_DEFAULT_ADDR), "Invalid device address"); /* Initially... */ d = this_device->driver; /* Set parameters for further communication */ d->setup_device(d->private_data, request->endpoint, this_device->current_address, &(this_device->ep_tx_tog[request->endpoint]), &(this_device->ep_rx_tog[request->endpoint])); /* Check transfer direction first */ if (HCD_DIRECTION_IN == request->direction) { do { /* Start actual data transfer */ d->rx_stage(d->private_data, request); /* Wait for response */ hcd_device_wait(this_device, HCD_EVENT_ENDPOINT, request->endpoint); /* Check response */ if (EXIT_SUCCESS != d->check_error(d->private_data, request->type, request->endpoint, HCD_DIRECTION_IN)) return EXIT_FAILURE; /* Read data received as response */ transfer_len = d->read_data(d->private_data, request->data, request->endpoint); request->data_left -= transfer_len; request->data += transfer_len; /* Total length shall not become negative */ if (request->data_left < 0) { USB_MSG("Invalid amount of data received"); return EXIT_FAILURE; } } while (0 != request->data_left); } else if (HCD_DIRECTION_OUT == request->direction) { do { temp_req = *request; /* Decide temporary transfer size */ if (temp_req.data_left > (int)temp_req.max_packet_size) temp_req.data_left = (int)temp_req.max_packet_size; /* Alter actual transfer size */ request->data += temp_req.data_left; request->data_left -= temp_req.data_left; /* Total length shall not become negative */ USB_ASSERT(request->data_left >= 0, "Invalid amount of transfer data calculated"); /* Start actual data transfer */ d->tx_stage(d->private_data, &temp_req); /* Wait for response */ hcd_device_wait(this_device, HCD_EVENT_ENDPOINT, request->endpoint); /* Check response */ if (EXIT_SUCCESS != d->check_error(d->private_data, request->type, request->endpoint, HCD_DIRECTION_OUT)) return EXIT_FAILURE; } while (0 != request->data_left); } else USB_ASSERT(0, "Invalid transfer direction"); return EXIT_SUCCESS; }