Files
kernel/drivers/usb/device.c
T
2020-02-04 12:45:15 +02:00

336 lines
12 KiB
C

#include "drivers/usb/request.h"
#include "drivers/usb/device.h"
#include "drivers/usb/driver.h"
#include "sys/string.h"
#include "sys/assert.h"
#include "sys/debug.h"
#include "sys/heap.h"
static uint32_t g_device_address = 0;
int usb_device_request(struct usb_device *dev, uint8_t type, uint8_t request, uint16_t index, uint16_t value, uint16_t len, void *data) {
_assert(dev);
struct usb_request req;
req.request = request;
req.type = type;
req.value = value;
req.index = index;
req.length = len;
struct usb_transfer t;
t.request = &req;
t.data = data;
t.length = len;
t.flags = 0;
_assert(dev->hc_control);
dev->hc_control(dev, &t);
return !(t.flags & USB_TRANSFER_SUCCESS);
}
static int usb_device_get_langs(struct usb_device *dev, uint16_t *langs, uint16_t lim) {
char buf[256];
struct usb_desc_string *desc;
int res;
langs[0] = 0;
desc = (struct usb_desc_string *) buf;
// Get string length
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
0,
USB_DESCRIPTOR_STRING << 8,
1, desc)) != 0) {
kwarn("Failed to get string descriptor length\n");
return res;
}
if (((desc->len - 2) / 2) >= lim) {
kwarn("Not enough buffer space to store device language data\n");
return -1;
}
// Get lang data
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
0,
USB_DESCRIPTOR_STRING << 8,
desc->len, desc))) {
kwarn("Failed to get string descriptor\n");
return res;
}
uint16_t lang_len = (desc->len - 2) / 2;
uint16_t i;
for (i = 0; i < lang_len; ++i) {
langs[i] = desc->data[i];
}
langs[i] = 0;
return 0;
}
static int usb_device_get_string(struct usb_device *dev, char *buf, size_t lim, uint16_t lang, uint16_t index) {
buf[0] = 0;
if (!index) {
return 0;
}
char _buf[256];
struct usb_desc_string *desc = (struct usb_desc_string *) _buf;
int res;
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
lang,
(USB_DESCRIPTOR_STRING << 8) | index,
1, desc)) != 0) {
kwarn("Failed to get string length\n");
return res;
}
size_t len = (desc->len - 2) / 2;
// Check that buffer has enough space to fit the string
if (lim <= len) {
return -1;
}
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
lang,
(USB_DESCRIPTOR_STRING << 8) | index,
desc->len, desc)) != 0) {
kwarn("Failed to get string length\n");
return res;
}
// Fuck unicode, we're only doing ASCII here
for (size_t i = 0; i < len; ++i) {
buf[i] = (char) desc->data[i];
}
buf[len] = 0;
return len;
}
int usb_device_init(struct usb_device *dev) {
struct usb_desc_device device_desc;
char string_buffer[256];
uint16_t langs[32];
uint32_t dev_addr;
int res;
kinfo("Getting partial descriptor\n");
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
0,
USB_DESCRIPTOR_DEVICE << 8,
8, &device_desc)) != 0) {
kwarn("Failed to get device descriptor\n");
return res;
}
dev->max_packet = device_desc.max_packet_zero;
// Allocate a new address for the device
dev_addr = ++g_device_address;
kinfo("Assigning new address\n");
if ((res = usb_device_request(dev,
0,
USB_REQUEST_SET_ADDRESS,
0,
dev_addr,
0, 0))) {
kwarn("Failed to set device address\n");
return res;
}
dev->address = dev_addr;
// Wait a bit
for (size_t _ = 0; _ < 100000; ++_);
kinfo("Reading full descriptor\n");
memset(&device_desc, 0, sizeof(struct usb_desc_device));
// Read full device descriptor
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
0,
USB_DESCRIPTOR_DEVICE << 8,
sizeof(struct usb_desc_device), &device_desc)) != 0) {
kwarn("Failed to get full device descriptor\n");
return res;
}
kinfo("Specification version: %04x\n", device_desc.bcd_usb);
kinfo("Device class: %02x:%02x\n", device_desc.dev_class, device_desc.dev_subclass);
kinfo("Protocol: %02x\n", device_desc.dev_protocol);
kinfo("Device ID: %04x:%04x\n", device_desc.id_vendor, device_desc.id_product);
// Dump detailed info
if ((res = usb_device_get_langs(dev, langs, sizeof(langs) / sizeof(langs[0]))) != 0) {
return res;
}
uint16_t lang = langs[0];
if (langs[0]) {
if ((res = usb_device_get_string(dev, string_buffer, sizeof(string_buffer), lang, device_desc.idx_manufacturer)) <= 0) {
kwarn("Failed to get device vendor name\n");
} else {
kinfo("Vendor name: %s\n", string_buffer);
}
if ((res = usb_device_get_string(dev, string_buffer, sizeof(string_buffer), lang, device_desc.idx_product)) <= 0) {
kwarn("Failed to get device product name\n");
} else {
kinfo("Product name: %s\n", string_buffer);
}
} else {
kwarn("No language data for device\n");
}
// Get device configuration
char config_buf[256];
struct usb_desc_configuration *configuration_desc = (struct usb_desc_configuration *) config_buf;
struct usb_desc_interface *interface_desc = NULL;
struct usb_desc_endpoint *endpoint_desc = NULL;
// This stores first valid configuration
uint8_t picked_config_value = 0;
struct usb_desc_interface *picked_interface_desc = NULL;
struct usb_desc_endpoint *picked_endpoint_desc = NULL;
for (uint32_t conf_index = 0; conf_index < device_desc.num_configurations; ++conf_index) {
// Get configuration size
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
0,
(USB_DESCRIPTOR_CONFIGURATION << 8) | conf_index,
4, config_buf)) != 0) {
kwarn("Failed to get configuration header\n");
continue;
}
if (configuration_desc->total_length > sizeof(config_buf)) {
kwarn("Configuration buffer too small\n");
continue;
}
// Read the rest of data
if ((res = usb_device_request(dev,
USB_REQUEST_TYPE_D2H,
USB_REQUEST_GET_DESCRIPTOR,
0,
(USB_DESCRIPTOR_CONFIGURATION << 8) | conf_index,
configuration_desc->total_length, config_buf)) != 0) {
kwarn("Failed to get device configuration\n");
continue;
}
// Dump configuration info
kinfo("Device %04x, configuration %u:\n", dev->address, conf_index);
kinfo("Interface count: %u, value: %u\n",
configuration_desc->num_interfaces, configuration_desc->configuration_value);
if (usb_device_get_string(dev, string_buffer, sizeof(string_buffer), lang, configuration_desc->idx_configuration) > 0) {
kinfo("Configuration name: \"%s\"\n", string_buffer);
}
// Select a configuration
void *data = config_buf + configuration_desc->length;
void *end = config_buf + configuration_desc->total_length;
kinfo("Configuration data:\n");
picked_interface_desc = NULL;
picked_endpoint_desc = NULL;
while (data < end) {
uint8_t len = ((uint8_t *) data)[0];
uint8_t type = ((uint8_t *) data)[1];
switch (type) {
case USB_DESCRIPTOR_INTERFACE:
interface_desc = (struct usb_desc_interface *) data;
if (!picked_interface_desc) {
picked_interface_desc = interface_desc;
}
kinfo("* Interface descriptor\n");
kinfo("Number %02x, %u endpoints\n",
interface_desc->interface_number,
interface_desc->num_endpoints);
kinfo("Class: %02x:%02x, Protocol %02x\n",
interface_desc->interface_class,
interface_desc->interface_subclass,
interface_desc->interface_protocol);
if (usb_device_get_string(dev, string_buffer, sizeof(string_buffer), lang, interface_desc->idx_interface) > 0) {
kinfo("Interface name: \"%s\"\n", string_buffer);
}
break;
case USB_DESCRIPTOR_ENDPOINT:
endpoint_desc = (struct usb_desc_endpoint *) data;
if (!picked_endpoint_desc) {
picked_endpoint_desc = endpoint_desc;
}
kinfo("* Endpoint descriptor\n");
kinfo("Address: %02x\n", endpoint_desc->address);
switch (endpoint_desc->attributes & 0x3) {
case 0:
kinfo("Control transfers\n");
break;
case 1:
kinfo("Isochronous transfers\n");
break;
case 2:
kinfo("Bulk transfers\n");
break;
case 3:
kinfo("Interrupt transfers\n");
break;
}
break;
}
data += len;
}
if (picked_interface_desc && picked_endpoint_desc) {
picked_config_value = configuration_desc->configuration_value;
break;
}
}
if (picked_interface_desc && picked_endpoint_desc) {
kinfo("Configuring device with value %02x\n", picked_config_value);
if ((res = usb_device_request(dev,
0,
USB_REQUEST_SET_CONFIGURATION,
0,
picked_config_value,
0, 0)) != 0) {
return res;
}
memcpy(&dev->desc_interface, picked_interface_desc, sizeof(struct usb_desc_interface));
memcpy(&dev->desc_endpoint, picked_endpoint_desc, sizeof(struct usb_desc_endpoint));
if ((res = usb_select_driver(dev)) != 0) {
kwarn("Failed to pick driver for the device\n");
}
}
return 0;
}