/* * Copyright (C) 2020 Nils Ollrogge * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup usbus_hid * @{ * @file * * @author Nils Ollrogge * @} */ #define USB_H_USER_IS_RIOT_INTERNAL #include #include "usb/usbus.h" #include "usb/usbus/control.h" #include "usb/usbus/hid.h" #include "tsrb.h" #define ENABLE_DEBUG 0 #include "debug.h" static void _init(usbus_t *usbus, usbus_handler_t *handler); static void _event_handler(usbus_t *usbus, usbus_handler_t *handler, usbus_event_usb_t event); static int _control_handler(usbus_t *usbus, usbus_handler_t *handler, usbus_control_request_state_t state, usb_setup_t *setup); static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, usbdev_ep_t *ep, usbus_event_transfer_t event); static void _handle_tx_ready(event_t *ev); static const usbus_handler_driver_t hid_driver = { .init = _init, .event_handler = _event_handler, .control_handler = _control_handler, .transfer_handler = _transfer_handler }; static size_t _gen_hid_descriptor(usbus_t *usbus, void *arg); static const usbus_descr_gen_funcs_t _hid_descriptor = { .fmt_post_descriptor = _gen_hid_descriptor, .len = { .fixed_len = sizeof(usb_desc_hid_t) }, .len_type = USBUS_DESCR_LEN_FIXED }; static size_t _gen_hid_descriptor(usbus_t *usbus, void *arg) { usbus_hid_device_t *hid_dev = arg; usb_desc_hid_t hid_desc; hid_desc.length = sizeof(usb_desc_hid_t); hid_desc.desc_type = USB_HID_DESCR_HID; hid_desc.bcd_hid = USB_HID_VERSION_BCD; hid_desc.country_code = USB_HID_COUNTRY_CODE_NOTSUPPORTED; hid_desc.num_descrs = 0x01; hid_desc.report_type = USB_HID_DESCR_REPORT; hid_desc.report_length = hid_dev->report_desc_size; usbus_control_slicer_put_bytes(usbus, (uint8_t *)&hid_desc, sizeof(hid_desc)); return sizeof(usb_desc_hid_t); } static void _handle_tx_ready(event_t *ev) { usbus_hid_device_t *hid = container_of(ev, usbus_hid_device_t, tx_ready); usbdev_ep_ready(hid->ep_in->ep, hid->occupied); } void usbus_hid_init(usbus_t *usbus, usbus_hid_device_t *hid, usbus_hid_cb_t cb, const uint8_t *report_desc, size_t report_desc_size) { memset(hid, 0, sizeof(usbus_hid_device_t)); hid->usbus = usbus; mutex_init(&hid->in_lock); hid->handler_ctrl.driver = &hid_driver; hid->report_desc = report_desc; hid->report_desc_size = report_desc_size; hid->cb = cb; DEBUG("hid_init: %d %d \n", report_desc_size, report_desc[0]); usbus_register_event_handler(usbus, &hid->handler_ctrl); } static void _init(usbus_t *usbus, usbus_handler_t *handler) { DEBUG("USB_HID: initialization\n"); usbus_hid_device_t *hid = (usbus_hid_device_t *)handler; hid->tx_ready.handler = _handle_tx_ready; hid->hid_descr.next = NULL; hid->hid_descr.funcs = &_hid_descriptor; hid->hid_descr.arg = hid; /* Configure Interface as USB_HID interface, choosing NONE for subclass and protocol in order to represent a generic I/O device */ hid->iface.class = USB_CLASS_HID; hid->iface.subclass = USB_HID_SUBCLASS_NONE; hid->iface.protocol = USB_HID_PROTOCOL_NONE; hid->iface.descr_gen = &hid->hid_descr; hid->iface.handler = handler; /* IN endpoint to send data to host */ hid->ep_in = usbus_add_endpoint(usbus, &hid->iface, USB_EP_TYPE_INTERRUPT, USB_EP_DIR_IN, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE); /* interrupt endpoint polling rate in ms */ hid->ep_in->interval = 0x05; usbus_enable_endpoint(hid->ep_in); /* OUT endpoint to receive data from host */ hid->ep_out = usbus_add_endpoint(usbus, &hid->iface, USB_EP_TYPE_INTERRUPT, USB_EP_DIR_OUT, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE); /* interrupt endpoint polling rate in ms */ hid->ep_out->interval = 0x05; usbus_enable_endpoint(hid->ep_out); /* signal that INTERRUPT OUT is ready to receive data */ usbdev_ep_ready(hid->ep_out->ep, 0); usbus_add_interface(usbus, &hid->iface); } static void _event_handler(usbus_t *usbus, usbus_handler_t *handler, usbus_event_usb_t event) { (void)usbus; (void)handler; switch (event) { default: DEBUG("USB HID unhandled event: 0x%x\n", event); break; } } static int _control_handler(usbus_t *usbus, usbus_handler_t *handler, usbus_control_request_state_t state, usb_setup_t *setup) { usbus_hid_device_t *hid = (usbus_hid_device_t *)handler; DEBUG("USB_HID: request: %d type: %d value: %d length: %d state: %d \n", setup->request, setup->type, setup->value >> 8, setup->length, state); /* Requests defined in USB HID 1.11 spec section 7 */ switch (setup->request) { case USB_SETUP_REQ_GET_DESCRIPTOR: { uint8_t desc_type = setup->value >> 8; if (desc_type == USB_HID_DESCR_REPORT) { usbus_control_slicer_put_bytes(usbus, hid->report_desc, hid->report_desc_size); } else if (desc_type == USB_HID_DESCR_HID) { _gen_hid_descriptor(usbus, NULL); } break; } case USB_HID_REQUEST_GET_REPORT: break; case USB_HID_REQUEST_GET_IDLE: break; case USB_HID_REQUEST_GET_PROTOCOL: break; case USB_HID_REQUEST_SET_REPORT: if ((state == USBUS_CONTROL_REQUEST_STATE_OUTDATA)) { size_t size = 0; uint8_t *data = usbus_control_get_out_data(usbus, &size); if (size > 0) { hid->cb(hid, data, size); } } break; case USB_HID_REQUEST_SET_IDLE: break; case USB_HID_REQUEST_SET_PROTOCOL: break; default: DEBUG("USB_HID: unknown request %d \n", setup->request); return -1; } return 1; } static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, usbdev_ep_t *ep, usbus_event_transfer_t event) { (void)usbus; (void)event; DEBUG("USB_HID: transfer_handler\n"); usbus_hid_device_t *hid = (usbus_hid_device_t *)handler; if ((ep->dir == USB_EP_DIR_IN) && (ep->type == USB_EP_TYPE_INTERRUPT)) { mutex_unlock(&hid->in_lock); hid->occupied = 0; } else if ((ep->dir == USB_EP_DIR_OUT) && (ep->type == USB_EP_TYPE_INTERRUPT)) { size_t len; usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t)); if (len > 0) { hid->cb(hid, ep->buf, len); } usbdev_ep_ready(ep, 0); } }