| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| /** |
| * @brief USB HID subclass driver |
| * @see USB HID subclass specification |
| */ |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "../services.h" |
| #include "hid.h" |
| #include "usbkbd.h" |
| |
| static inline struct usbreq |
| __set_protocol_req(enum hid_protocol p, int iface) { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_INTERFACE), |
| .bRequest = SET_PROTOCOL, |
| .wValue = p, |
| .wIndex = iface, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq |
| __get_protocol_req(int iface) { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_CLS | USB_RCPT_INTERFACE), |
| .bRequest = GET_PROTOCOL, |
| .wValue = 0, |
| .wIndex = iface, |
| .wLength = 1 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq |
| __set_idle_req(int idle_ms, int id, int iface) { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_INTERFACE), |
| .bRequest = SET_IDLE, |
| .wValue = idle_ms << 8 | id, |
| .wIndex = iface, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| |
| static inline struct usbreq |
| __get_report_req(enum hid_report_type type, int id, int iface, int len) { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_CLS | USB_RCPT_INTERFACE), |
| .bRequest = GET_REPORT, |
| .wValue = type << 8 | id, |
| .wIndex = iface, |
| .wLength = len |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq |
| __set_report_req(enum hid_report_type type, int id, int iface, int len) { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_INTERFACE), |
| .bRequest = SET_REPORT, |
| .wValue = type << 8 | id, |
| .wIndex = iface, |
| .wLength = len |
| }; |
| return r; |
| } |
| |
| static int |
| usb_hid_config_cb(void *token, int cfg, int iface, struct anon_desc *desc) |
| { |
| struct usb_hid_device *hid; |
| struct config_desc *cdesc; |
| struct iface_desc *idesc; |
| struct hid_desc *hdesc; |
| |
| if (!desc) { |
| return 0; |
| } |
| |
| hid = (struct usb_hid_device *)token; |
| |
| switch (desc->bDescriptorType) { |
| case CONFIGURATION: |
| cdesc = (struct config_desc*)desc; |
| hid->config = cdesc->bConfigurationValue; |
| break; |
| case INTERFACE: |
| idesc = (struct iface_desc *)desc; |
| if (idesc->bInterfaceSubClass == 1) { |
| hid->subclass = idesc->bInterfaceSubClass; |
| hid->protocol = idesc->bInterfaceProtocol; |
| hid->iface = idesc->bInterfaceNumber; |
| } |
| break; |
| case HID: |
| hdesc = (struct hid_desc *)desc; |
| if (!hid->subclass) { |
| if (hdesc->bReportDescriptorType== HID_REPORT) { |
| hid->report_size = hdesc->wReportDescriptorLength; |
| } else { |
| ZF_LOGD("Descriptor %u not supported!\n", |
| hdesc->bReportDescriptorType); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| struct usb_hid_device *usb_hid_alloc(struct usb_dev *udev) |
| { |
| int err; |
| struct usb_hid_device *hid; |
| struct xact xact; |
| struct usbreq *req; |
| int class; |
| |
| if (!udev) { |
| ZF_LOGF("Invalid device\n"); |
| } |
| |
| hid = usb_malloc(sizeof(struct usb_hid_device)); |
| if (!hid) { |
| ZF_LOGD("Not enough memory!\n"); |
| return NULL; |
| } |
| |
| hid->udev = udev; |
| hid->subclass = 0xFF; |
| |
| /* Parse the descriptors */ |
| err = usbdev_parse_config(udev, usb_hid_config_cb, hid); |
| if (err) { |
| ZF_LOGF("Invalid descriptors\n"); |
| } |
| |
| class = usbdev_get_class(udev); |
| if (class != USB_CLASS_HID) { |
| ZF_LOGD("Not a HID device(%d)\n", class); |
| usb_free(hid); |
| return NULL; |
| } |
| |
| /* Activate configuration */ |
| xact.len = sizeof(struct usbreq); |
| err = usb_alloc_xact(udev->dman, &xact, 1); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| |
| /* Fill in the request */ |
| xact.type = PID_SETUP; |
| req = xact_get_vaddr(&xact); |
| *req = __set_configuration_req(hid->config); |
| |
| /* Send the request to the host */ |
| err = usbdev_schedule_xact(udev, udev->ep_ctrl, &xact, 1, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| usb_destroy_xact(udev->dman, &xact, 1); |
| |
| return hid; |
| } |
| |
| void usb_hid_free(struct usb_hid_device *hid) |
| { |
| usb_free(hid); |
| } |
| |
| int usb_hid_set_idle(struct usb_hid_device *hid, int idle_ms) |
| { |
| int err; |
| struct usbreq *req; |
| struct xact xact; |
| |
| xact.type = PID_SETUP; |
| xact.len = sizeof(struct usbreq); |
| |
| err = usb_alloc_xact(hid->udev->dman, &xact, 1); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| |
| req = xact_get_vaddr(&xact); |
| *req = __set_idle_req(idle_ms, 0, hid->iface); |
| |
| err = usbdev_schedule_xact(hid->udev, hid->udev->ep_ctrl, &xact, 1, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| usb_destroy_xact(hid->udev->dman, &xact, 1); |
| |
| return err; |
| } |
| |
| int usb_hid_get_idle(struct usb_hid_device *hid) |
| { |
| return 0; |
| } |
| |
| int usb_hid_get_report(struct usb_hid_device *hid, enum hid_report_type type, void *buf) |
| { |
| return 0; |
| } |
| |
| int usb_hid_set_report(struct usb_hid_device *hid, enum hid_report_type type, |
| void *buf, int size) |
| { |
| int err; |
| struct usbreq *req; |
| struct xact xact[2]; |
| |
| xact[0].type = PID_SETUP; |
| xact[0].len = sizeof(struct usbreq); |
| xact[1].type = PID_OUT; |
| xact[1].len = size; |
| |
| err = usb_alloc_xact(hid->udev->dman, xact, 2); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| |
| req = xact_get_vaddr(&xact[0]); |
| *req = __set_report_req(REPORT_OUTPUT, 0, hid->iface, size); |
| |
| memcpy(xact_get_vaddr(&xact[1]), buf, size); |
| |
| err = usbdev_schedule_xact(hid->udev, hid->udev->ep_ctrl, xact, 2, NULL, NULL); |
| if (err) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| usb_destroy_xact(hid->udev->dman, xact, 2); |
| |
| return err; |
| } |
| |
| int usb_hid_get_protocol(struct usb_hid_device *hid) |
| { |
| return 0; |
| } |
| |
| int usb_hid_set_protocol(struct usb_hid_device *hid) |
| { |
| return 0; |
| } |
| |