| // 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/debug.h> |
| #include <ddk/protocol/usb-old.h> |
| #include <usb/usb-request.h> |
| #include <endian.h> |
| #include <lib/sync/completion.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <utf_conversion/utf_conversion.h> |
| |
| #include "usb-bus.h" |
| #include "usb-device.h" |
| #include "util.h" |
| |
| #define MIN(a, b) (((a) < (b)) ? (a) : (b)) |
| |
| typedef struct usb_langid_desc { |
| uint8_t bLength; |
| uint8_t bDescriptorType; |
| uint16_t wLangIds[127]; |
| } __PACKED usb_langid_desc_t; |
| |
| static void usb_util_control_complete(void* ctx, usb_request_t* req) { |
| sync_completion_signal((sync_completion_t*)ctx); |
| } |
| |
| zx_status_t usb_util_control(usb_device_t* dev, uint8_t request_type, uint8_t request, |
| uint16_t value, uint16_t index, void* data, size_t length, |
| size_t* out_actual) { |
| 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, dev->req_size); |
| 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 = htole16(value); |
| setup->wIndex = htole16(index); |
| setup->wLength = htole16(static_cast<uint16_t>(length)); |
| req->header.device_id = dev->device_id; |
| |
| bool out = !!((request_type & USB_DIR_MASK) == USB_DIR_OUT); |
| if (length > 0 && out) { |
| usb_request_copy_to(req, data, length, 0); |
| } |
| |
| sync_completion_t completion; |
| |
| req->header.length = length; |
| |
| usb_request_complete_t complete = { |
| .callback = usb_util_control_complete, |
| .ctx = &completion, |
| }; |
| usb_hci_request_queue(&dev->hci, req, &complete); |
| zx_status_t status = sync_completion_wait(&completion, ZX_SEC(1)); |
| if (status == ZX_OK) { |
| status = req->response.status; |
| } else if (status == ZX_ERR_TIMED_OUT) { |
| sync_completion_reset(&completion); |
| status = usb_hci_cancel_all(&dev->hci, dev->device_id, 0); |
| if (status == ZX_OK) { |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| status = ZX_ERR_TIMED_OUT; |
| } |
| } |
| |
| if (status == ZX_OK) { |
| status = req->response.actual; |
| |
| if (length > 0 && !out) { |
| usb_request_copy_from(req, data, req->response.actual, 0); |
| } |
| } |
| if (use_free_list) { |
| if (usb_request_pool_add(&dev->free_reqs, req) != ZX_OK) { |
| zxlogf(TRACE, "Unable to add back request to the free pool\n"); |
| usb_request_release(req); |
| } |
| } else { |
| usb_request_release(req); |
| } |
| return status; |
| } |
| |
| zx_status_t usb_util_get_descriptor(usb_device_t* dev, uint16_t type, uint16_t index, |
| uint16_t language, void* data, size_t length) { |
| return usb_util_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_util_get_string_descriptor(usb_device_t* dev, uint8_t desc_id, uint16_t lang_id, |
| uint8_t* buf, size_t buflen, size_t* out_actual, |
| uint16_t* out_actual_lang_id) { |
| // If we have never attempted to load our language ID table, do so now. |
| zx_status_t result; |
| if (!atomic_load_explicit(&dev->langids_fetched, memory_order_relaxed)) { |
| auto* id_desc = static_cast<usb_langid_desc_t*>(calloc(1, sizeof(usb_langid_desc_t))); |
| |
| if (id_desc != NULL) { |
| result = usb_util_get_descriptor(dev, USB_DT_STRING, 0, 0, id_desc, sizeof(*id_desc)); |
| if (result == ZX_ERR_IO_REFUSED || result == ZX_ERR_IO_INVALID) { |
| // 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); |
| id_desc->bLength = 4; |
| id_desc->wLangIds[0] = htole16(0x0409); |
| result = 4; |
| } else if ((result >= 0) && |
| ((result < 4) || (result != id_desc->bLength) || (result & 0x1))) { |
| result = ZX_ERR_INTERNAL; |
| } |
| |
| // So, if we have managed to fetch/synthesize a language ID table, |
| // go ahead and perform a bit of fixup. Redefine bLength to be the |
| // valid number of entires in the table, and fixup the endianness of |
| // all the entires in the table. Then, attempt to swap in the new |
| // language ID table. |
| if (result >= 0) { |
| id_desc->bLength = (id_desc->bLength - 2) >> 1; |
| #if BYTE_ORDER != LITTLE_ENDIAN |
| for (uint8_t i = 0; i < id_desc->bLength; ++i) { |
| id_desc->wLangIds[i] = letoh16(id_desc->wLangIds[i]); |
| } |
| #endif |
| uintptr_t expected = 0; |
| if (atomic_compare_exchange_strong(&dev->lang_ids, &expected, (uintptr_t)id_desc)) { |
| id_desc = NULL; |
| } |
| } |
| |
| // Make sure that we free any table that we allocated, but did not |
| // end up swapping into place. |
| free(id_desc); |
| } else { |
| result = ZX_ERR_NO_MEMORY; |
| } |
| |
| atomic_store_explicit(&dev->langids_fetched, true, memory_order_relaxed); |
| if (result < 0) { |
| return result; |
| } |
| } |
| |
| // At this point in time, if we don't have a language id table, but we have |
| // tried to obtain or synthesize one in the past, we are not going to get |
| // one. Just fail. |
| usb_langid_desc_t* lang_ids = |
| (usb_langid_desc_t*)(dev->lang_ids.load(memory_order_relaxed)); |
| if (!lang_ids) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Handle the special case that the user asked for the language ID table. |
| if (desc_id == 0) { |
| size_t table_sz = (lang_ids->bLength << 1); |
| buflen &= ~1; |
| |
| size_t actual = MIN(table_sz, buflen); |
| memcpy(buf, lang_ids->wLangIds, actual); |
| *out_actual = actual; |
| return ZX_OK; |
| } |
| |
| // Search for the requested language ID. |
| uint32_t lang_ndx; |
| for (lang_ndx = 0; lang_ndx < lang_ids->bLength; ++ lang_ndx) { |
| if (lang_id == lang_ids->wLangIds[lang_ndx]) { |
| break; |
| } |
| } |
| |
| // If we didn't find it, default to the first entry in the table. |
| if (lang_ndx >= lang_ids->bLength) { |
| ZX_DEBUG_ASSERT(lang_ids->bLength >= 1); |
| lang_id = lang_ids->wLangIds[0]; |
| } |
| |
| struct { |
| uint8_t bLength; |
| uint8_t bDescriptorType; |
| uint16_t code_points[127]; |
| } string_desc; |
| |
| result = usb_util_get_descriptor(dev, USB_DT_STRING, desc_id, le16toh(lang_id), |
| &string_desc, sizeof(string_desc)); |
| |
| if (result == ZX_ERR_IO_REFUSED || result == ZX_ERR_IO_INVALID) { |
| zx_status_t reset_result = usb_hci_reset_endpoint(&dev->hci, dev->device_id, 0); |
| if (reset_result != ZX_OK) { |
| zxlogf(ERROR, "failed to reset endpoint, err: %d\n", reset_result); |
| return result; |
| } |
| result = usb_util_get_descriptor(dev, USB_DT_STRING, desc_id, le16toh(lang_id), |
| &string_desc, sizeof(string_desc)); |
| if (result == ZX_ERR_IO_REFUSED || result == ZX_ERR_IO_INVALID) { |
| reset_result = usb_hci_reset_endpoint(&dev->hci, dev->device_id, 0); |
| if (reset_result != ZX_OK) { |
| zxlogf(ERROR, "failed to reset endpoint, err: %d\n", reset_result); |
| return result; |
| } |
| } |
| } |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| if ((result < 2) || (result != string_desc.bLength)) { |
| result = ZX_ERR_INTERNAL; |
| } else { |
| // Success! Convert this result from UTF16LE to UTF8 and store the |
| // language ID we actually fetched (if it was not what the user |
| // requested). |
| *out_actual = buflen; |
| *out_actual_lang_id = lang_id; |
| utf16_to_utf8(string_desc.code_points, (string_desc.bLength >> 1) - 1, |
| buf, out_actual, |
| UTF_CONVERT_FLAG_FORCE_LITTLE_ENDIAN); |
| return ZX_OK; |
| } |
| |
| return result; |
| } |