| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <ddk/protocol/usb.h> |
| #include <ddk/usb-request.h> |
| #include <sync/completion.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "usb-device.h" |
| #include "util.h" |
| |
| static void usb_device_control_complete(usb_request_t* req, void* cookie) { |
| completion_signal((completion_t*)cookie); |
| } |
| |
| zx_status_t usb_device_control(usb_device_t* dev, uint8_t request_type, |
| uint8_t request, uint16_t value, |
| uint16_t index, void* data, size_t length) { |
| usb_request_t* req = NULL; |
| bool use_free_list = length == 0; |
| if (use_free_list) { |
| req = usb_request_pool_get(&dev->free_reqs, length); |
| } |
| if (req == NULL) { |
| zx_status_t status = usb_request_alloc(&req, length, 0); |
| if (status != ZX_OK) return status; |
| } |
| |
| // fill in protocol data |
| usb_setup_t* setup = &req->setup; |
| setup->bmRequestType = request_type; |
| setup->bRequest = request; |
| setup->wValue = value; |
| setup->wIndex = index; |
| setup->wLength = length; |
| req->header.device_id = dev->device_id; |
| |
| bool out = !!((request_type & USB_DIR_MASK) == USB_DIR_OUT); |
| if (length > 0 && out) { |
| usb_request_copyto(req, data, length, 0); |
| } |
| |
| completion_t completion = COMPLETION_INIT; |
| |
| req->header.length = length; |
| req->complete_cb = usb_device_control_complete; |
| req->cookie = &completion; |
| |
| usb_hci_request_queue(&dev->hci, req); |
| completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| zx_status_t status = req->response.status; |
| if (status == ZX_OK) { |
| status = req->response.actual; |
| |
| if (length > 0 && !out) { |
| usb_request_copyfrom(req, data, req->response.actual, 0); |
| } |
| } |
| if (use_free_list) { |
| usb_request_pool_add(&dev->free_reqs, req); |
| } else { |
| usb_request_release(req); |
| } |
| return status; |
| } |
| |
| zx_status_t usb_device_get_descriptor(usb_device_t* dev, uint16_t type, uint16_t index, |
| uint16_t language, void* data, size_t length) { |
| return usb_device_control(dev, USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, |
| USB_REQ_GET_DESCRIPTOR, type << 8 | index, language, data, length); |
| } |
| |
| zx_status_t usb_device_get_string_descriptor(usb_device_t* dev, uint8_t id, |
| char* buf, size_t buflen) { |
| uint16_t buffer[128]; |
| uint16_t languages[128]; |
| int languageCount = 0; |
| |
| buf[0] = 0; |
| memset(languages, 0, sizeof(languages)); |
| |
| // read list of supported languages |
| zx_status_t result = usb_device_get_descriptor(dev, USB_DT_STRING, 0, 0, |
| languages, sizeof(languages)); |
| if (result == ZX_ERR_IO_REFUSED) { |
| // some devices do not support fetching language list |
| // in that case assume US English (0x0409) |
| usb_hci_reset_endpoint(&dev->hci, dev->device_id, 0); |
| languages[1] = htole16(0x0409); |
| result = 4; |
| } else if (result < 0) { |
| return result; |
| } |
| languageCount = (result - 2) / 2; |
| |
| for (int language = 1; language <= languageCount; language++) { |
| memset(buffer, 0, sizeof(buffer)); |
| |
| result = usb_device_get_descriptor(dev, USB_DT_STRING, id, |
| le16toh(languages[language]), buffer, sizeof(buffer)); |
| // use first language on the list |
| if (result > 0) { |
| // First word is descriptor length and type |
| usb_descriptor_header_t* header = (usb_descriptor_header_t *)buffer; |
| uint8_t length = header->bLength; |
| if (length > result) { |
| length = result; |
| } |
| |
| uint16_t* src = &buffer[1]; |
| uint16_t* src_end = src + length / sizeof(uint16_t); |
| char* dest = buf; |
| // subtract 2 since our output UTF8 chars can be up to 3 bytes long, |
| // plus one extra for zero termination |
| char* dest_end = buf + buflen - 3; |
| |
| // convert to UTF8 while copying to buffer |
| while (src < src_end && dest < dest_end) { |
| uint16_t uch = *src++; |
| if (uch < 0x80u) { |
| *dest++ = (char)uch; |
| } else if (uch < 0x800u) { |
| *dest++ = 0xC0 | (uch >> 6); |
| *dest++ = 0x80 | (uch & 0x3F); |
| } else { |
| // with 16 bit input, 3 characters of output is the maximum we will see |
| *dest++ = 0xE0 | (uch >> 12); |
| *dest++ = 0x80 | (uch >> 6); |
| *dest++ = 0x80 | (uch & 0x3F); |
| } |
| } |
| *dest++ = 0; |
| return dest - buf; |
| } else if (result == ZX_ERR_IO_REFUSED) { |
| usb_hci_reset_endpoint(&dev->hci, dev->device_id, 0); |
| } |
| } |
| // default to empty string |
| return 0; |
| } |