mirror of
https://github.com/Mathewnd/Astral.git
synced 2026-10-27 12:48:22 +00:00
309 lines
8.8 KiB
C
309 lines
8.8 KiB
C
#include <kernel/usb.h>
|
|
#include <kernel/alloc.h>
|
|
#include <errno.h>
|
|
#include <logging.h>
|
|
#include <kernel/usb_hid.h>
|
|
|
|
// TODO proper cleanup
|
|
|
|
#define USB_INTERFACE_CLASS_HID 0x03
|
|
|
|
#define USB_DESC_TYPE_HID 0x21
|
|
#define USB_DESC_TYPE_HID_REPORT 0x22
|
|
#define USB_DESC_TYPE_HID_PHYSICAL 0x23
|
|
|
|
typedef struct {
|
|
uint8_t bDescriptorType;
|
|
uint16_t wDescriptorLength;
|
|
} __attribute__((packed)) usb_hid_class_desc_t;
|
|
|
|
typedef struct {
|
|
uint8_t bLength;
|
|
uint8_t bDescriptorType;
|
|
uint16_t bcdHID;
|
|
uint8_t bCountryCode;
|
|
uint8_t bNumDescriptors;
|
|
usb_hid_class_desc_t descriptors[];
|
|
} __attribute__((packed)) usb_hid_desc_t;
|
|
|
|
|
|
typedef struct {
|
|
uint8_t *report_descriptor;
|
|
size_t report_descriptor_length;
|
|
hid_parser_t parser;
|
|
usb_endpoint_t *interrupt_in_endpoint;
|
|
size_t interrupt_in_packet_size;
|
|
void *data_buffer;
|
|
} hid_driver_t;
|
|
|
|
#define HID_GD_POINTER 0x01
|
|
#define HID_GD_MOUSE 0x02
|
|
#define HID_GD_JOYSTICK 0x04
|
|
#define HID_GD_GAMEPAD 0x05
|
|
#define HID_GD_KEYBOARD 0x06
|
|
#define HID_GD_KEYPAD 0x07
|
|
#define HID_GD_MULTI_AXIS_CONTROLLER 0x08
|
|
|
|
static inline const char *get_pretty_name_gd(uint32_t usage) {
|
|
switch (USAGE(usage)) {
|
|
case HID_GD_POINTER:
|
|
return "HID Pointer";
|
|
case HID_GD_MOUSE:
|
|
return "HID Mouse";
|
|
case HID_GD_JOYSTICK:
|
|
return "HID Joystick";
|
|
case HID_GD_GAMEPAD:
|
|
return "HID Gamepad";
|
|
case HID_GD_KEYBOARD:
|
|
return "HID Keyboard";
|
|
case HID_GD_KEYPAD:
|
|
return "HID Keypad";
|
|
case HID_GD_MULTI_AXIS_CONTROLLER:
|
|
return "HID Multi Axis Controller";
|
|
}
|
|
|
|
return "HID Device (GD)";
|
|
}
|
|
|
|
static usb_hid_desc_t *hid_find_descriptor(usb_device_t *dev, usb_interface_t *interface) {
|
|
bool in_interface = false;
|
|
|
|
usb_for_each_descriptor(dev->config_desc, desc) {
|
|
USB_FOR_EACH_DESCRIPTOR_CHECK(dev->config_desc, desc) {
|
|
return NULL;
|
|
}
|
|
|
|
if (desc == (usb_desc_hdr_t *)interface->desc) {
|
|
in_interface = true;
|
|
continue;
|
|
}
|
|
|
|
if (!in_interface)
|
|
continue;
|
|
|
|
if (desc->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE)
|
|
break;
|
|
|
|
if (desc->bDescriptorType != USB_DESC_TYPE_HID)
|
|
continue;
|
|
|
|
if (desc->bLength < sizeof(usb_hid_desc_t))
|
|
return NULL;
|
|
|
|
usb_hid_desc_t *usb_hid_desc = (usb_hid_desc_t *)desc;
|
|
size_t expected_length = sizeof(usb_hid_desc_t) + usb_hid_desc->bNumDescriptors * sizeof(usb_hid_class_desc_t);
|
|
if (desc->bLength < expected_length)
|
|
return NULL;
|
|
|
|
return usb_hid_desc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
hid_report_t *hid_get_report_from_id(hid_parser_t *parser, unsigned int report_id) {
|
|
hid_report_t *report = NULL;
|
|
|
|
for (size_t i = 0; i < parser->report_count; ++i) {
|
|
if (parser->reports[i].report_id == report_id) {
|
|
report = &parser->reports[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return report;
|
|
}
|
|
|
|
static void interrupt_in_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) {
|
|
hid_driver_t *hid_driver = ctx;
|
|
|
|
if (status != USB_STATUS_SUCCESS && status != USB_STATUS_SHORT_PACKET) {
|
|
printf("hid: interrupt transfer failed. retrying\n");
|
|
} else {
|
|
if (transferred == 0)
|
|
goto resend;
|
|
|
|
uint8_t *data = hid_driver->data_buffer;
|
|
hid_report_t *report = NULL;
|
|
if (hid_driver->parser.report_count) {
|
|
report = hid_get_report_from_id(&hid_driver->parser, data[0]);
|
|
if (report == NULL) {
|
|
printf("hid: got unknown report. ignoring\n");
|
|
goto resend;
|
|
}
|
|
|
|
++data;
|
|
--transferred;
|
|
}
|
|
|
|
hid_parse_report(&hid_driver->parser, report, data, transferred);
|
|
}
|
|
|
|
resend:
|
|
usb_xfer_t xfer = {
|
|
.ep = hid_driver->interrupt_in_endpoint,
|
|
.flags = USB_XFER_FLAG_TO_HOST,
|
|
.type = USB_XFER_TYPE_INTERRUPT,
|
|
.data = hid_driver->data_buffer,
|
|
.data_length = hid_driver->interrupt_in_packet_size,
|
|
.completion = interrupt_in_callback,
|
|
.completion_ctx = hid_driver
|
|
};
|
|
|
|
int error = usb_submit_xfer(dev, &xfer);
|
|
if (error) {
|
|
printf("hid: failed to submit HID interrupt xfer: %s\n", strerror(error));
|
|
}
|
|
}
|
|
|
|
static void get_report_descriptor_callback(usb_device_t *dev, void *ctx, usb_status_t status, size_t transferred) {
|
|
if (status != USB_STATUS_SUCCESS) {
|
|
printf("hid: failed to get report descriptor\n");
|
|
return;
|
|
}
|
|
|
|
usb_interface_t *interface = ctx;
|
|
hid_driver_t *hid_driver = interface->driver_data;
|
|
|
|
if (transferred != hid_driver->report_descriptor_length) {
|
|
printf("hid: transferred != hid_driver->report_descriptor_length\n");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < interface->desc->bNumEndpoints; ++i) {
|
|
if (interface->endpoints[i].desc && (interface->endpoints[i].desc->bEndpointAddress & USB_ENDPOINT_ADDRESS_DIR_IN) && (interface->endpoints[i].desc->bmAttributes & USB_ENDPOINT_ATTRIB_TYPE_MASK) == USB_ENDPOINT_ATTRIB_TYPE_INTR)
|
|
hid_driver->interrupt_in_endpoint = &interface->endpoints[i];
|
|
}
|
|
|
|
if (hid_driver->interrupt_in_endpoint == NULL) {
|
|
printf("hid: no IN interrupt endpoint found\n");
|
|
return;
|
|
}
|
|
|
|
int error = hid_parser_init(&hid_driver->parser, hid_driver->report_descriptor, hid_driver->report_descriptor_length);
|
|
if (error) {
|
|
printf("hid: failed to parse report descriptor\n");
|
|
return;
|
|
}
|
|
|
|
printf("hid: %lu applications with %lu %s%s\n",
|
|
hid_driver->parser.application_count,
|
|
hid_driver->parser.report_count ? hid_driver->parser.report_count : hid_driver->parser.input_count,
|
|
hid_driver->parser.report_count ? "report" : "anonymous input",
|
|
(hid_driver->parser.report_count + hid_driver->parser.input_count != 1) ? "s" : ""
|
|
);
|
|
|
|
hid_driver->interrupt_in_packet_size = usb_endpoint_max_packet_size(hid_driver->interrupt_in_endpoint->desc);
|
|
hid_driver->data_buffer = alloc(hid_driver->interrupt_in_packet_size);
|
|
if (hid_driver->data_buffer == NULL) {
|
|
printf("hid: out of memory\n");
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < hid_driver->parser.application_count; ++i) {
|
|
hid_application_t *application = &hid_driver->parser.applications[i];
|
|
const char *pretty_name = "HID Device";
|
|
if (USAGE_PAGE(application->usage) == HID_USAGE_PAGE_GENERIC_DESKTOP)
|
|
pretty_name = get_pretty_name_gd(application->usage);
|
|
|
|
application->input_device = input_new();
|
|
if (application->input_device == NULL) {
|
|
printf("hid: failed to create new input device\n");
|
|
return;
|
|
}
|
|
|
|
application->input_device->id_bus = INPUT_DEVICE_BUS_USB;
|
|
application->input_device->id_vendor = dev->desc.idVendor;
|
|
application->input_device->id_product = dev->desc.idProduct;
|
|
snprintf(application->input_device->name, sizeof(application->input_device->name), pretty_name);
|
|
application->input_device->id_version = 1;
|
|
application->input_device->ver_major = 1;
|
|
application->input_device->ver_minor = 0;
|
|
application->input_device->ver_patch = 0;
|
|
}
|
|
|
|
hid_advertise_events(&hid_driver->parser);
|
|
|
|
usb_xfer_t xfer = {
|
|
.ep = hid_driver->interrupt_in_endpoint,
|
|
.flags = USB_XFER_FLAG_TO_HOST,
|
|
.type = USB_XFER_TYPE_INTERRUPT,
|
|
.data = hid_driver->data_buffer,
|
|
.data_length = hid_driver->interrupt_in_packet_size,
|
|
.completion = interrupt_in_callback,
|
|
.completion_ctx = hid_driver
|
|
};
|
|
|
|
error = usb_submit_xfer(dev, &xfer);
|
|
if (error) {
|
|
printf("hid: failed to submit initial hid xfer: %s\n", strerror(error));
|
|
}
|
|
}
|
|
|
|
static int hid_attach(usb_device_t *dev, usb_interface_t *interface) {
|
|
usb_hid_desc_t *usb_hid_desc = hid_find_descriptor(dev, interface);
|
|
if (usb_hid_desc == NULL) {
|
|
printf("hid: invalid interface descriptor\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
usb_hid_class_desc_t *usb_hid_class_desc = NULL;
|
|
for (int i = 0; i < usb_hid_desc->bNumDescriptors; ++i) {
|
|
if (usb_hid_desc->descriptors[i].bDescriptorType != USB_DESC_TYPE_HID_REPORT)
|
|
continue;
|
|
|
|
usb_hid_class_desc = &usb_hid_desc->descriptors[i];
|
|
break;
|
|
}
|
|
|
|
if (usb_hid_class_desc == NULL) {
|
|
printf("hid: no report descriptor\n");
|
|
return EINVAL;
|
|
}
|
|
|
|
hid_driver_t *hid_driver = alloc(sizeof(hid_driver_t));
|
|
if (hid_driver == NULL) {
|
|
printf("hid: out of memory\n");
|
|
return ENOMEM;
|
|
}
|
|
|
|
hid_driver->report_descriptor_length = usb_hid_class_desc->wDescriptorLength;
|
|
if (hid_driver->report_descriptor_length == 0) {
|
|
printf("hid: invalid report descriptor length\n");
|
|
free(hid_driver);
|
|
return EINVAL;
|
|
}
|
|
|
|
hid_driver->report_descriptor = alloc(hid_driver->report_descriptor_length);
|
|
if (hid_driver->report_descriptor == NULL) {
|
|
printf("hid: out of memory\n");
|
|
free(hid_driver);
|
|
return ENOMEM;
|
|
}
|
|
|
|
interface->driver_data = hid_driver;
|
|
|
|
int error = usb_get_interface_descriptor(dev, interface->desc->bInterfaceNumber, USB_DESC_TYPE_HID_REPORT, 0, hid_driver->report_descriptor, hid_driver->report_descriptor_length, get_report_descriptor_callback, interface);
|
|
if (error) {
|
|
interface->driver_data = NULL;
|
|
free(hid_driver->report_descriptor);
|
|
free(hid_driver);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int hid_probe(usb_device_t *, usb_interface_t *interface) {
|
|
if (interface->desc->bInterfaceClass == USB_INTERFACE_CLASS_HID)
|
|
return USB_DRIVER_SCORE_CLASS_MATCH;
|
|
|
|
return USB_DRIVER_SCORE_NONE;
|
|
}
|
|
|
|
DEFINE_USB_CLASS_DRIVER(hid_driver,
|
|
.name = "USB Human Interface Device",
|
|
.probe = hid_probe,
|
|
.attach = hid_attach
|
|
// TODO detach
|
|
);
|