/* * This file is part of the libusbhost library * hosted at http://github.com/libusbhost/libusbhost * * Copyright (C) 2016 Amir Hammad * * * libusbhost is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include "usbh_core.h" #include "driver/usbh_device_driver.h" #include "usbh_driver_hid.h" #include "usart_helpers.h" #include #include #include #include #define USB_HID_SET_REPORT 0x09 #define USB_HID_SET_IDLE 0x0A enum STATES { STATE_INACTIVE, STATE_READING_REQUEST, STATE_READING_COMPLETE_AND_CHECK_REPORT, STATE_SET_REPORT_EMPTY_READ, STATE_GET_REPORT_DESCRIPTOR_READ_SETUP,// configuration is complete at this point. We write request STATE_GET_REPORT_DESCRIPTOR_READ_COMPLETE,// after the read finishes, we parse that descriptor STATE_SET_IDLE, STATE_SET_IDLE_COMPLETE, }; enum REPORT_STATE { REPORT_STATE_NULL, REPORT_STATE_READY, REPORT_STATE_PENDING, }; struct _hid_device { usbh_device_t *usbh_device; uint8_t buffer[USBH_HID_BUFFER]; uint16_t endpoint_in_maxpacketsize; uint8_t endpoint_in_address; enum STATES state_next; uint8_t endpoint_in_toggle; uint8_t device_id; uint8_t configuration_value; uint16_t report0_length; enum REPORT_STATE report_state; uint8_t report_data[USBH_HID_REPORT_BUFFER]; uint8_t report_data_length; enum HID_TYPE hid_type; uint8_t interface_number; }; typedef struct _hid_device hid_device_t; struct hid_report_decriptor { struct usb_hid_descriptor header; struct _report_descriptor_info { uint8_t bDescriptorType; uint16_t wDescriptorLength; } __attribute__((packed)) report_descriptors_info[]; } __attribute__((packed)); static hid_device_t hid_device[USBH_HID_MAX_DEVICES]; static hid_config_t hid_config; static bool initialized = false; void hid_driver_init(const hid_config_t *config) { uint32_t i; initialized = true; hid_config = *config; for (i = 0; i < USBH_HID_MAX_DEVICES; i++) { hid_device[i].state_next = STATE_INACTIVE; } } static void *init(usbh_device_t *usbh_dev) { if (!initialized) { LOG_PRINTF("\n%s/%d : driver not initialized\n", __FILE__, __LINE__); return 0; } uint32_t i; hid_device_t *drvdata = NULL; // find free data space for HID device for (i = 0; i < USBH_HID_MAX_DEVICES; i++) { if (hid_device[i].state_next == STATE_INACTIVE) { drvdata = &hid_device[i]; drvdata->device_id = i; drvdata->endpoint_in_address = 0; drvdata->endpoint_in_toggle = 0; drvdata->report0_length = 0; drvdata->usbh_device = usbh_dev; drvdata->report_state = REPORT_STATE_NULL; drvdata->hid_type = HID_TYPE_NONE; break; } } return drvdata; } static void parse_report_descriptor(hid_device_t *hid, const uint8_t *buffer, uint32_t length) { // TODO // Do some parsing! // add some checks hid->report_state = REPORT_STATE_READY; // TODO: parse this from buffer! hid->report_data_length = 1; (void)buffer; (void)length; } /** * Returns true if all needed data are parsed */ static bool analyze_descriptor(void *drvdata, void *descriptor) { hid_device_t *hid = (hid_device_t *)drvdata; uint8_t desc_type = ((uint8_t *)descriptor)[1]; switch (desc_type) { case USB_DT_CONFIGURATION: { const struct usb_config_descriptor * cfg = (const struct usb_config_descriptor*)descriptor; hid->configuration_value = cfg->bConfigurationValue; } break; case USB_DT_DEVICE: { const struct usb_device_descriptor *devDesc = (const struct usb_device_descriptor *)descriptor; (void)devDesc; } break; case USB_DT_INTERFACE: { const struct usb_interface_descriptor *ifDesc = (const struct usb_interface_descriptor *)descriptor; if (ifDesc->bInterfaceProtocol == 0x01) { hid->hid_type = HID_TYPE_KEYBOARD; hid->interface_number = ifDesc->bInterfaceNumber; } else if (ifDesc->bInterfaceProtocol == 0x02) { hid->hid_type = HID_TYPE_MOUSE; hid->interface_number = ifDesc->bInterfaceNumber; } } break; case USB_DT_ENDPOINT: { const struct usb_endpoint_descriptor *ep = (const struct usb_endpoint_descriptor *)descriptor; if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) { uint8_t epaddr = ep->bEndpointAddress; if (epaddr & (1<<7)) { hid->endpoint_in_address = epaddr&0x7f; if (ep->wMaxPacketSize < USBH_HID_BUFFER) { hid->endpoint_in_maxpacketsize = ep->wMaxPacketSize; } else { hid->endpoint_in_maxpacketsize = USBH_HID_BUFFER; } } } } break; case USB_DT_HID: { const struct hid_report_decriptor *desc = (const struct hid_report_decriptor *)descriptor; if (desc->header.bNumDescriptors > 0 && desc->report_descriptors_info[0].bDescriptorType == USB_DT_REPORT) { hid->report0_length = desc->report_descriptors_info[0].wDescriptorLength; } } break; default: break; } if (hid->endpoint_in_address && hid->report0_length) { hid->state_next = STATE_GET_REPORT_DESCRIPTOR_READ_SETUP; return true; } return false; } static void report_event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data) { (void)cb_data;// UNUSED hid_device_t *hid = (hid_device_t *)dev->drvdata; hid->report_state = REPORT_STATE_READY; } static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data) { hid_device_t *hid = (hid_device_t *)dev->drvdata; switch (hid->state_next) { case STATE_READING_COMPLETE_AND_CHECK_REPORT: { switch (cb_data.status) { case USBH_PACKET_CALLBACK_STATUS_OK: case USBH_PACKET_CALLBACK_STATUS_ERRSIZ: if (hid_config.hid_in_message_handler) { hid_config.hid_in_message_handler(hid->device_id, hid->buffer, cb_data.transferred_length); } hid->state_next = STATE_READING_REQUEST; break; default: ERROR(cb_data.status); hid->state_next = STATE_INACTIVE; break; } } break; case STATE_GET_REPORT_DESCRIPTOR_READ_COMPLETE: // read complete, SET_IDLE to 0 { switch (cb_data.status) { case USBH_PACKET_CALLBACK_STATUS_OK: LOG_PRINTF("READ REPORT COMPLETE \n"); hid->state_next = STATE_READING_REQUEST; hid->endpoint_in_toggle = 0; parse_report_descriptor(hid, hid->buffer, cb_data.transferred_length); break; default: ERROR(cb_data.status); hid->state_next = STATE_INACTIVE; break; } } break; default: break; } } static void read_hid_in_endpoint(void *drvdata) { hid_device_t *hid = (hid_device_t *)drvdata; usbh_packet_t packet; packet.address = hid->usbh_device->address; packet.data.in = &hid->buffer[0]; packet.datalen = hid->endpoint_in_maxpacketsize; packet.endpoint_address = hid->endpoint_in_address; packet.endpoint_size_max = hid->endpoint_in_maxpacketsize; packet.endpoint_type = USBH_ENDPOINT_TYPE_INTERRUPT; packet.speed = hid->usbh_device->speed; packet.callback = event; packet.callback_arg = hid->usbh_device; packet.toggle = &hid->endpoint_in_toggle; hid->state_next = STATE_READING_COMPLETE_AND_CHECK_REPORT; usbh_read(hid->usbh_device, &packet); } /** * @param time_curr_us - monotically rising time * unit is microseconds * @see usbh_poll() */ static void poll(void *drvdata, uint32_t time_curr_us) { (void)time_curr_us; hid_device_t *hid = (hid_device_t *)drvdata; usbh_device_t *dev = hid->usbh_device; switch (hid->state_next) { case STATE_READING_REQUEST: { read_hid_in_endpoint(drvdata); } break; case STATE_GET_REPORT_DESCRIPTOR_READ_SETUP: { hid->endpoint_in_toggle = 0; // We support only the first report descriptor with index 0 // limit the size of the report descriptor! if (hid->report0_length > USBH_HID_BUFFER) { hid->report0_length = USBH_HID_BUFFER; } struct usb_setup_data setup_data; setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_INTERFACE; setup_data.bRequest = USB_REQ_GET_DESCRIPTOR; setup_data.wValue = USB_DT_REPORT << 8; setup_data.wIndex = 0; setup_data.wLength = hid->report0_length; hid->state_next = STATE_GET_REPORT_DESCRIPTOR_READ_COMPLETE; device_control(dev, event, &setup_data, hid->buffer); } break; default: // do nothing - probably transfer is in progress break; } } static void remove(void *drvdata) { hid_device_t *hid = (hid_device_t *)drvdata; hid->state_next = STATE_INACTIVE; hid->endpoint_in_address = 0; } bool hid_set_report(uint8_t device_id, uint8_t val) { if (device_id >= USBH_HID_MAX_DEVICES) { LOG_PRINTF("invalid device id\n"); return false; } hid_device_t *hid = &hid_device[device_id]; if (hid->report_state != REPORT_STATE_READY) { LOG_PRINTF("reporting is not ready\n"); // store and update afterwards return false; } if (hid->report_data_length == 0) { LOG_PRINTF("reporting is not available (report len=0)\n"); return false; } struct usb_setup_data setup_data; setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE; setup_data.bRequest = USB_HID_SET_REPORT; setup_data.wValue = 0x02 << 8; setup_data.wIndex = hid->interface_number; setup_data.wLength = hid->report_data_length; hid->report_data[0] = val; hid->report_state = REPORT_STATE_PENDING; device_control(hid->usbh_device, report_event, &setup_data, &hid->report_data); return true; } bool hid_is_connected(uint8_t device_id) { if (device_id >= USBH_HID_MAX_DEVICES) { LOG_PRINTF("is connected: invalid device id\n"); return false; } return hid_device[device_id].state_next == STATE_INACTIVE; } enum HID_TYPE hid_get_type(uint8_t device_id) { if (hid_is_connected(device_id)) { return HID_TYPE_NONE; } return hid_device[device_id].hid_type; } static const usbh_dev_driver_info_t driver_info = { .deviceClass = -1, .deviceSubClass = -1, .deviceProtocol = -1, .idVendor = -1, .idProduct = -1, .ifaceClass = 0x03, // HID class .ifaceSubClass = -1, .ifaceProtocol = -1, // Do not care }; const usbh_dev_driver_t usbh_hid_driver = { .init = init, .analyze_descriptor = analyze_descriptor, .poll = poll, .remove = remove, .info = &driver_info };