/** * Load hub-specific information into hub_dev structure and process if needed * * Read port count and initialize structures holding per port information. * If there are any non-removable devices, start initializing them. * This function is hub-specific and should be run only after the hub is * configured using usb_set_first_configuration function. * @param hub_dev hub representation * @return error code */ static int usb_hub_process_hub_specific_info(usb_hub_dev_t *hub_dev) { assert(hub_dev); /* Get hub descriptor. */ usb_log_debug("Retrieving descriptor\n"); usb_pipe_t *control_pipe = &hub_dev->usb_device->ctrl_pipe; usb_hub_descriptor_header_t descriptor; size_t received_size; int opResult = usb_request_get_descriptor(control_pipe, USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_HUB, 0, 0, &descriptor, sizeof(usb_hub_descriptor_header_t), &received_size); if (opResult != EOK) { usb_log_error("Failed to receive hub descriptor: %s.\n", str_error(opResult)); return opResult; } usb_log_debug("Setting port count to %d.\n", descriptor.port_count); hub_dev->port_count = descriptor.port_count; hub_dev->ports = calloc(hub_dev->port_count, sizeof(usb_hub_port_t)); if (!hub_dev->ports) { return ENOMEM; } for (size_t port = 0; port < hub_dev->port_count; ++port) { usb_hub_port_init( &hub_dev->ports[port], port + 1, control_pipe); } hub_dev->power_switched = !(descriptor.characteristics & HUB_CHAR_NO_POWER_SWITCH_FLAG); hub_dev->per_port_power = descriptor.characteristics & HUB_CHAR_POWER_PER_PORT_FLAG; if (!hub_dev->power_switched) { usb_log_info( "Power switching not supported, ports always powered.\n"); return EOK; } usb_log_info("Hub port power switching enabled.\n"); for (size_t port = 0; port < hub_dev->port_count; ++port) { usb_log_debug("Powering port %zu.\n", port); const int ret = usb_hub_port_set_feature( &hub_dev->ports[port], USB_HUB_FEATURE_PORT_POWER); if (ret != EOK) { usb_log_error("Cannot power on port %zu: %s.\n", hub_dev->ports[port].port_number, str_error(ret)); } else { if (!hub_dev->per_port_power) { usb_log_debug("Ganged power switching, " "one port is enough.\n"); break; } } } return EOK; }
/** Process endpoint descriptor. * * @param mapping Endpoint mapping list. * @param mapping_count Number of endpoint mappings in @p mapping. * @param interface Interface descriptor under which belongs the @p endpoint. * @param endpoint Endpoint descriptor. * @return Error code. */ static int process_endpoint( usb_endpoint_mapping_t *mapping, size_t mapping_count, usb_standard_interface_descriptor_t *interface, usb_standard_endpoint_descriptor_t *endpoint_desc, usb_dev_session_t *bus_session) { /* * Get endpoint characteristics. */ /* Actual endpoint number is in bits 0..3 */ const usb_endpoint_t ep_no = endpoint_desc->endpoint_address & 0x0F; const usb_endpoint_description_t description = { /* Endpoint direction is set by bit 7 */ .direction = (endpoint_desc->endpoint_address & 128) ? USB_DIRECTION_IN : USB_DIRECTION_OUT, /* Transfer type is in bits 0..2 and * the enum values corresponds 1:1 */ .transfer_type = endpoint_desc->attributes & 3, /* Get interface characteristics. */ .interface_class = interface->interface_class, .interface_subclass = interface->interface_subclass, .interface_protocol = interface->interface_protocol, }; /* * Find the most fitting mapping and initialize the pipe. */ usb_endpoint_mapping_t *ep_mapping = find_endpoint_mapping(mapping, mapping_count, &description, interface->interface_number, interface->alternate_setting); if (ep_mapping == NULL) { return ENOENT; } if (ep_mapping->present) { return EEXIST; } int rc = usb_pipe_initialize(&ep_mapping->pipe, ep_no, description.transfer_type, ED_MPS_PACKET_SIZE_GET( uint16_usb2host(endpoint_desc->max_packet_size)), description.direction, ED_MPS_TRANS_OPPORTUNITIES_GET( uint16_usb2host(endpoint_desc->max_packet_size)), bus_session); if (rc != EOK) { return rc; } ep_mapping->present = true; ep_mapping->descriptor = endpoint_desc; ep_mapping->interface = interface; return EOK; } /** Process whole USB interface. * * @param mapping Endpoint mapping list. * @param mapping_count Number of endpoint mappings in @p mapping. * @param parser Descriptor parser. * @param parser_data Descriptor parser data. * @param interface_descriptor Interface descriptor. * @return Error code. */ static int process_interface( usb_endpoint_mapping_t *mapping, size_t mapping_count, const usb_dp_parser_t *parser, const usb_dp_parser_data_t *parser_data, const uint8_t *interface_descriptor, usb_dev_session_t *bus_session) { const uint8_t *descriptor = usb_dp_get_nested_descriptor(parser, parser_data, interface_descriptor); if (descriptor == NULL) { return ENOENT; } do { if (is_endpoint_descriptor(descriptor)) { (void) process_endpoint(mapping, mapping_count, (usb_standard_interface_descriptor_t *) interface_descriptor, (usb_standard_endpoint_descriptor_t *) descriptor, bus_session); } descriptor = usb_dp_get_sibling_descriptor(parser, parser_data, interface_descriptor, descriptor); } while (descriptor != NULL); return EOK; } /** Initialize endpoint pipes from configuration descriptor. * * The mapping array is expected to conform to following rules: * - @c pipe must be uninitialized pipe * - @c description must point to prepared endpoint description * - @c descriptor does not need to be initialized (will be overwritten) * - @c interface does not need to be initialized (will be overwritten) * - @c present does not need to be initialized (will be overwritten) * * After processing the configuration descriptor, the mapping is updated * in the following fashion: * - @c present will be set to @c true when the endpoint was found in the * configuration * - @c descriptor will point inside the configuration descriptor to endpoint * corresponding to given description (or NULL for not found descriptor) * - @c interface will point inside the configuration descriptor to interface * descriptor the endpoint @c descriptor belongs to (or NULL for not found * descriptor) * - @c pipe will be initialized when found, otherwise left untouched * - @c description will be untouched under all circumstances * * @param mapping Endpoint mapping list. * @param mapping_count Number of endpoint mappings in @p mapping. * @param configuration_descriptor Full configuration descriptor (is expected * to be in USB endianness: i.e. as-is after being retrieved from * the device). * @param configuration_descriptor_size Size of @p configuration_descriptor * in bytes. * @param connection Connection backing the endpoint pipes. * @return Error code. */ int usb_pipe_initialize_from_configuration( usb_endpoint_mapping_t *mapping, size_t mapping_count, const uint8_t *config_descriptor, size_t config_descriptor_size, usb_dev_session_t *bus_session) { if (config_descriptor == NULL) return EBADMEM; if (config_descriptor_size < sizeof(usb_standard_configuration_descriptor_t)) { return ERANGE; } /* Go through the mapping and set all endpoints to not present. */ for (size_t i = 0; i < mapping_count; i++) { mapping[i].present = false; mapping[i].descriptor = NULL; mapping[i].interface = NULL; } /* Prepare the descriptor parser. */ const usb_dp_parser_t dp_parser = { .nesting = descriptor_nesting }; const usb_dp_parser_data_t dp_data = { .data = config_descriptor, .size = config_descriptor_size, }; /* * Iterate through all interfaces. */ const uint8_t *interface = usb_dp_get_nested_descriptor(&dp_parser, &dp_data, config_descriptor); if (interface == NULL) { return ENOENT; } do { (void) process_interface(mapping, mapping_count, &dp_parser, &dp_data, interface, bus_session); interface = usb_dp_get_sibling_descriptor(&dp_parser, &dp_data, config_descriptor, interface); } while (interface != NULL); return EOK; } /** Probe default control pipe for max packet size. * * The function tries to get the correct value of max packet size several * time before giving up. * * The session on the pipe shall not be started. * * @param pipe Default control pipe. * @return Error code. */ int usb_pipe_probe_default_control(usb_pipe_t *pipe) { assert(pipe); static_assert(DEV_DESCR_MAX_PACKET_SIZE_OFFSET < CTRL_PIPE_MIN_PACKET_SIZE); if ((pipe->direction != USB_DIRECTION_BOTH) || (pipe->transfer_type != USB_TRANSFER_CONTROL) || (pipe->endpoint_no != 0)) { return EINVAL; } uint8_t dev_descr_start[CTRL_PIPE_MIN_PACKET_SIZE]; size_t transferred_size; int rc; for (size_t attempt_var = 0; attempt_var < 3; ++attempt_var) { rc = usb_request_get_descriptor(pipe, USB_REQUEST_TYPE_STANDARD, USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_DEVICE, 0, 0, dev_descr_start, CTRL_PIPE_MIN_PACKET_SIZE, &transferred_size); if (rc == EOK) { if (transferred_size != CTRL_PIPE_MIN_PACKET_SIZE) { rc = ELIMIT; continue; } break; } } if (rc != EOK) { return rc; } pipe->max_packet_size = dev_descr_start[DEV_DESCR_MAX_PACKET_SIZE_OFFSET]; return EOK; }
/** Generic wrapper for GET requests using standard control request format. * * @see usb_pipe_control_read * * @param pipe Pipe used for the communication. * @param request_type Request type (standard/class/vendor). * @param recipient Request recipient (e.g. device or endpoint). * @param request Actual request (e.g. GET_DESCRIPTOR). * @param value Value of @c wValue field of setup packet * (must be in USB endianness). * @param index Value of @c wIndex field of setup packet * (must be in USB endianness). * @param data Buffer where to store data accepted during the DATA stage. * (they will come in USB endianness). * @param data_size Size of the @p data buffer * (in native endianness). * @param actual_data_size Actual size of transfered data * (in native endianness). * @return Error code. * @retval EBADMEM @p pipe is NULL. * @retval EBADMEM @p data is NULL and @p data_size is not zero. * @retval ERANGE Data buffer too large. */ int usb_control_request_get(usb_pipe_t *pipe, usb_request_type_t request_type, usb_request_recipient_t recipient, uint8_t request, uint16_t value, uint16_t index, void *data, size_t data_size, size_t *actual_data_size) { if (pipe == NULL) { return EBADMEM; } if (data_size > MAX_DATA_LENGTH) { return ERANGE; } if ((data_size > 0) && (data == NULL)) { return EBADMEM; } /* * TODO: check that @p request_type and @p recipient are * within ranges. */ const usb_device_request_setup_packet_t setup_packet = { .request_type = SETUP_REQUEST_TYPE_DEVICE_TO_HOST | (request_type << 5) | recipient, .request = request, .value = uint16_host2usb(value), .index = uint16_host2usb(index), .length = uint16_host2usb(data_size), }; return usb_pipe_control_read(pipe, &setup_packet, sizeof(setup_packet), data, data_size, actual_data_size); } /** Retrieve status of a USB device. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] index Recipient index (in native endianness). * @param[in] recipient Recipient of the GET_STATUS request. * @param[out] status Recipient status (in native endianness). * @return Error code. */ int usb_request_get_status(usb_pipe_t *pipe, usb_request_recipient_t recipient, uint16_t index, uint16_t *status) { if ((recipient == USB_REQUEST_RECIPIENT_DEVICE) && (index != 0)) { return EINVAL; } if (status == NULL) { return EBADMEM; } uint16_t status_usb_endianess; size_t data_transfered_size; int rc = usb_control_request_get(pipe, USB_REQUEST_TYPE_STANDARD, recipient, USB_DEVREQ_GET_STATUS, 0, uint16_host2usb(index), &status_usb_endianess, 2, &data_transfered_size); if (rc != EOK) { return rc; } if (data_transfered_size != 2) { return ELIMIT; } *status = uint16_usb2host(status_usb_endianess); return EOK; } /** Clear or disable specific device feature. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] request_type Request type (standard/class/vendor). * @param[in] recipient Recipient of the CLEAR_FEATURE request. * @param[in] feature_selector Feature selector (in native endianness). * @param[in] index Recipient index (in native endianness). * @return Error code. */ int usb_request_clear_feature(usb_pipe_t *pipe, usb_request_type_t request_type, usb_request_recipient_t recipient, uint16_t feature_selector, uint16_t index) { if (request_type == USB_REQUEST_TYPE_STANDARD) { if ((recipient == USB_REQUEST_RECIPIENT_DEVICE) && (index != 0)) { return EINVAL; } } int rc = usb_control_request_set(pipe, request_type, recipient, USB_DEVREQ_CLEAR_FEATURE, uint16_host2usb(feature_selector), uint16_host2usb(index), NULL, 0); return rc; } /** Set or enable specific device feature. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] request_type Request type (standard/class/vendor). * @param[in] recipient Recipient of the SET_FEATURE request. * @param[in] feature_selector Feature selector (in native endianness). * @param[in] index Recipient index (in native endianness). * @return Error code. */ int usb_request_set_feature(usb_pipe_t *pipe, usb_request_type_t request_type, usb_request_recipient_t recipient, uint16_t feature_selector, uint16_t index) { if (request_type == USB_REQUEST_TYPE_STANDARD) { if ((recipient == USB_REQUEST_RECIPIENT_DEVICE) && (index != 0)) { return EINVAL; } } int rc = usb_control_request_set(pipe, request_type, recipient, USB_DEVREQ_SET_FEATURE, uint16_host2usb(feature_selector), uint16_host2usb(index), NULL, 0); return rc; } /** Retrieve USB descriptor of a USB device. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] request_type Request type (standard/class/vendor). * @param[in] recipient Request recipient (device/interface/endpoint). * @param[in] descriptor_type Descriptor type (device/configuration/HID/...). * @param[in] descriptor_index Descriptor index. * @param[in] language Language index. * @param[out] buffer Buffer where to store the retrieved descriptor. * @param[in] size Size of the @p buffer. * @param[out] actual_size Number of bytes actually transferred. * @return Error code. */ int usb_request_get_descriptor(usb_pipe_t *pipe, usb_request_type_t request_type, usb_request_recipient_t recipient, uint8_t descriptor_type, uint8_t descriptor_index, uint16_t language, void *buffer, size_t size, size_t *actual_size) { if (buffer == NULL) { return EBADMEM; } if (size == 0) { return EINVAL; } const uint16_t wValue = descriptor_index | (descriptor_type << 8); return usb_control_request_get(pipe, request_type, recipient, USB_DEVREQ_GET_DESCRIPTOR, wValue, language, buffer, size, actual_size); } /** Retrieve USB descriptor, allocate space for it. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] request_type Request type (standard/class/vendor). * @param[in] recipient Request recipient (device/interface/endpoint). * @param[in] descriptor_type Descriptor type (device/configuration/HID/...). * @param[in] descriptor_index Descriptor index. * @param[in] language Language index. * @param[out] buffer_ptr Where to store pointer to allocated buffer. * @param[out] buffer_size Where to store the size of the descriptor. * @return */ int usb_request_get_descriptor_alloc(usb_pipe_t * pipe, usb_request_type_t request_type, usb_request_recipient_t recipient, uint8_t descriptor_type, uint8_t descriptor_index, uint16_t language, void **buffer_ptr, size_t *buffer_size) { if (buffer_ptr == NULL) { return EBADMEM; } int rc; /* * Get only first byte to retrieve descriptor length. */ uint8_t tmp_buffer[1]; size_t bytes_transfered; rc = usb_request_get_descriptor(pipe, request_type, recipient, descriptor_type, descriptor_index, language, &tmp_buffer, 1, &bytes_transfered); if (rc != EOK) { return rc; } if (bytes_transfered != 1) { /* FIXME: some better error code? */ return ESTALL; } size_t size = tmp_buffer[0]; if (size == 0) { /* FIXME: some better error code? */ return ESTALL; } /* * Allocate buffer and get the descriptor again. */ void *buffer = malloc(size); if (buffer == NULL) { return ENOMEM; } rc = usb_request_get_descriptor(pipe, request_type, recipient, descriptor_type, descriptor_index, language, buffer, size, &bytes_transfered); if (rc != EOK) { free(buffer); return rc; } if (bytes_transfered != size) { free(buffer); /* FIXME: some better error code? */ return ESTALL; } *buffer_ptr = buffer; if (buffer_size != NULL) { *buffer_size = size; } return EOK; } /** Retrieve standard device descriptor of a USB device. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[out] descriptor Storage for the device descriptor. * @return Error code. */ int usb_request_get_device_descriptor(usb_pipe_t *pipe, usb_standard_device_descriptor_t *descriptor) { if (descriptor == NULL) { return EBADMEM; } size_t actually_transferred = 0; usb_standard_device_descriptor_t descriptor_tmp; int rc = usb_request_get_descriptor(pipe, USB_REQUEST_TYPE_STANDARD, USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_DEVICE, 0, 0, &descriptor_tmp, sizeof(descriptor_tmp), &actually_transferred); if (rc != EOK) { return rc; } /* Verify that all data has been transferred. */ if (actually_transferred < sizeof(descriptor_tmp)) { return ELIMIT; } /* Everything is okay, copy the descriptor. */ memcpy(descriptor, &descriptor_tmp, sizeof(descriptor_tmp)); return EOK; } /** Retrieve configuration descriptor of a USB device. * * The function does not retrieve additional data binded with configuration * descriptor (such as its interface and endpoint descriptors) - use * usb_request_get_full_configuration_descriptor() instead. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] index Descriptor index. * @param[out] descriptor Storage for the device descriptor. * @return Error code. */ int usb_request_get_bare_configuration_descriptor(usb_pipe_t *pipe, int index, usb_standard_configuration_descriptor_t *descriptor) { if (descriptor == NULL) { return EBADMEM; } if ((index < 0) || (index > 0xFF)) { return ERANGE; } size_t actually_transferred = 0; usb_standard_configuration_descriptor_t descriptor_tmp; int rc = usb_request_get_descriptor(pipe, USB_REQUEST_TYPE_STANDARD, USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_CONFIGURATION, index, 0, &descriptor_tmp, sizeof(descriptor_tmp), &actually_transferred); if (rc != EOK) { return rc; } /* Verify that all data has been transferred. */ if (actually_transferred < sizeof(descriptor_tmp)) { return ELIMIT; } /* Everything is okay, copy the descriptor. */ memcpy(descriptor, &descriptor_tmp, sizeof(descriptor_tmp)); return EOK; } /** Retrieve full configuration descriptor of a USB device. * * @warning The @p buffer might be touched (i.e. its contents changed) * even when error occurs. * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] index Descriptor index. * @param[out] descriptor Storage for the device descriptor. * @param[in] descriptor_size Size of @p descriptor buffer. * @param[out] actual_size Number of bytes actually transferred. * @return Error code. */ int usb_request_get_full_configuration_descriptor(usb_pipe_t *pipe, int index, void *descriptor, size_t descriptor_size, size_t *actual_size) { if ((index < 0) || (index > 0xFF)) { return ERANGE; } return usb_request_get_descriptor(pipe, USB_REQUEST_TYPE_STANDARD, USB_REQUEST_RECIPIENT_DEVICE, USB_DESCTYPE_CONFIGURATION, index, 0, descriptor, descriptor_size, actual_size); } /** Retrieve full configuration descriptor, allocate space for it. * * The function takes care that full configuration descriptor is returned * (i.e. the function will fail when less data then descriptor.totalLength * is returned). * * @param[in] pipe Control endpoint pipe (session must be already started). * @param[in] index Configuration index. * @param[out] descriptor_ptr Where to store pointer to allocated buffer. * @param[out] descriptor_size Where to store the size of the descriptor. * @return Error code. */ int usb_request_get_full_configuration_descriptor_alloc( usb_pipe_t *pipe, int index, void **descriptor_ptr, size_t *descriptor_size) { int rc; if (descriptor_ptr == NULL) { return EBADMEM; } usb_standard_configuration_descriptor_t bare_config; rc = usb_request_get_bare_configuration_descriptor(pipe, index, &bare_config); if (rc != EOK) { return rc; } if (bare_config.descriptor_type != USB_DESCTYPE_CONFIGURATION) { return ENOENT; } const size_t total_length = uint16_usb2host(bare_config.total_length); if (total_length < sizeof(bare_config)) { return ELIMIT; } void *buffer = malloc(total_length); if (buffer == NULL) { return ENOMEM; } size_t transferred = 0; rc = usb_request_get_full_configuration_descriptor(pipe, index, buffer, total_length, &transferred); if (rc != EOK) { free(buffer); return rc; } if (transferred != total_length) { free(buffer); return ELIMIT; } /* Everything looks okay, copy the pointers. */ *descriptor_ptr = buffer; if (descriptor_size != NULL) { *descriptor_size = total_length; } return EOK; }