blob: edef3d39a4a9385b156e940951dd22a7a78f899f [file] [log] [blame]
// 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;
}