Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Human Interface Device (HID) USB class core
 *
 * Copyright (c) 2018 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define SYS_LOG_LEVEL CONFIG_SYS_LOG_USB_DEVICE_LEVEL
#define SYS_LOG_DOMAIN "usb/hid"
#include <logging/sys_log.h>

#include <misc/byteorder.h>
#include <usb_device.h>
#include <usb_common.h>

#include <usb_descriptor.h>
#include <class/usb_hid.h>

#define HID_INT_IN_EP_ADDR				0x81
#define HID_INT_OUT_EP_ADDR				0x01

#define HID_INT_IN_EP_IDX			0
#define HID_INT_OUT_EP_IDX			1

struct usb_hid_config {
	struct usb_if_descriptor if0;
	struct usb_hid_descriptor if0_hid;
	struct usb_ep_descriptor if0_int_in_ep;
#ifdef CONFIG_ENABLE_HID_INT_OUT_EP
	struct usb_ep_descriptor if0_int_out_ep;
#endif
} __packed;

USBD_CLASS_DESCR_DEFINE(primary) struct usb_hid_config hid_cfg = {
	/* Interface descriptor */
	.if0 = {
		.bLength = sizeof(struct usb_if_descriptor),
		.bDescriptorType = USB_INTERFACE_DESC,
		.bInterfaceNumber = 0,
		.bAlternateSetting = 0,
		.bNumEndpoints = 1,
		.bInterfaceClass = HID_CLASS,
		.bInterfaceSubClass = 0,
		.bInterfaceProtocol = 0,
		.iInterface = 0,
	},
	.if0_hid = {
		.bLength = sizeof(struct usb_hid_descriptor),
		.bDescriptorType = USB_HID_DESC,
		.bcdHID = sys_cpu_to_le16(USB_1_1),
		.bCountryCode = 0,
		.bNumDescriptors = 1,
		.subdesc[0] = {
			.bDescriptorType = USB_HID_REPORT_DESC,
			/*
			 * descriptor length needs to be set
			 * after initialization
			 */
			.wDescriptorLength = 0,
		},
	},
	.if0_int_in_ep = {
		.bLength = sizeof(struct usb_ep_descriptor),
		.bDescriptorType = USB_ENDPOINT_DESC,
		.bEndpointAddress = HID_INT_IN_EP_ADDR,
		.bmAttributes = USB_DC_EP_INTERRUPT,
		.wMaxPacketSize =
			sys_cpu_to_le16(CONFIG_HID_INTERRUPT_EP_MPS),
		.bInterval = 0x09,
	},
#ifdef CONFIG_ENABLE_HID_INT_OUT_EP
	.if0_int_out_ep = {
		.bLength = sizeof(struct usb_ep_descriptor),
		.bDescriptorType = USB_ENDPOINT_DESC,
		.bEndpointAddress = HID_INT_OUT_EP_ADDR,
		.bmAttributes = USB_DC_EP_INTERRUPT,
		.wMaxPacketSize =
			sys_cpu_to_le16(CONFIG_HID_INTERRUPT_EP_MPS),
		.bInterval = 0x09,
	},
#endif

};

static void usb_set_hid_report_size(u16_t report_desc_size)
{
	hid_cfg.if0_hid.subdesc[0].wDescriptorLength =
		sys_cpu_to_le16(report_desc_size);
}

static struct hid_device_info {
	const u8_t *report_desc;
	size_t report_size;

	const struct hid_ops *ops;
} hid_device;

static void hid_status_cb(enum usb_dc_status_code status, u8_t *param)
{
	/* Check the USB status and do needed action if required */
	switch (status) {
	case USB_DC_ERROR:
		SYS_LOG_DBG("USB device error");
		break;
	case USB_DC_RESET:
		SYS_LOG_DBG("USB device reset detected");
		break;
	case USB_DC_CONNECTED:
		SYS_LOG_DBG("USB device connected");
		break;
	case USB_DC_CONFIGURED:
		SYS_LOG_DBG("USB device configured");
		break;
	case USB_DC_DISCONNECTED:
		SYS_LOG_DBG("USB device disconnected");
		break;
	case USB_DC_SUSPEND:
		SYS_LOG_DBG("USB device suspended");
		break;
	case USB_DC_RESUME:
		SYS_LOG_DBG("USB device resumed");
		break;
	case USB_DC_UNKNOWN:
	default:
		SYS_LOG_DBG("USB unknown state");
		break;
	}
}

static int hid_class_handle_req(struct usb_setup_packet *setup,
				s32_t *len, u8_t **data)
{
	SYS_LOG_DBG("Class request: bRequest 0x%x bmRequestType 0x%x len %d",
		    setup->bRequest, setup->bmRequestType, *len);

	if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST) {
		switch (setup->bRequest) {
		case HID_GET_REPORT:
			SYS_LOG_DBG("Get Report");
			if (hid_device.ops->get_report) {
				return hid_device.ops->get_report(setup, len,
								  data);
			} else {
				SYS_LOG_ERR("Mandatory request not supported");
				return -EINVAL;
			}
			break;
		default:
			SYS_LOG_ERR("Unhandled request 0x%x", setup->bRequest);
			break;
		}
	} else {
		switch (setup->bRequest) {
		case HID_SET_IDLE:
			SYS_LOG_DBG("Set Idle");
			if (hid_device.ops->set_idle) {
				return hid_device.ops->set_idle(setup, len,
								data);
			}
			break;
		case HID_SET_REPORT:
			if (hid_device.ops->set_report == NULL) {
				SYS_LOG_ERR("set_report not implemented");
				return -EINVAL;
			}
			return hid_device.ops->set_report(setup, len, data);
		default:
			SYS_LOG_ERR("Unhandled request 0x%x", setup->bRequest);
			break;
		}
	}

	return -ENOTSUP;
}

static int hid_custom_handle_req(struct usb_setup_packet *setup,
				s32_t *len, u8_t **data)
{
	SYS_LOG_DBG("Standard request: bRequest 0x%x bmRequestType 0x%x len %d",
		    setup->bRequest, setup->bmRequestType, *len);

	if (REQTYPE_GET_DIR(setup->bmRequestType) == REQTYPE_DIR_TO_HOST &&
	    REQTYPE_GET_RECIP(setup->bmRequestType) ==
					REQTYPE_RECIP_INTERFACE &&
					setup->bRequest == REQ_GET_DESCRIPTOR) {
		switch (setup->wValue) {
		case 0x2200:
			SYS_LOG_DBG("Return Report Descriptor");

			/* Some buggy system may be pass a larger wLength when
			 * it try read HID report descriptor, although we had
			 * already tell it the right descriptor size.
			 * So truncated wLength if it doesn't match. */
			if (*len != hid_device.report_size) {
				SYS_LOG_WRN("len %d doesn't match"
					    "Report Descriptor size", *len);
				*len = min(*len, hid_device.report_size);
			}
			*data = (u8_t *)hid_device.report_desc;
			break;
		default:
			return -ENOTSUP;
		}

		return 0;
	}

	return -ENOTSUP;
}

static void hid_int_in(u8_t ep, enum usb_dc_ep_cb_status_code ep_status)
{
	if (ep_status != USB_DC_EP_DATA_IN ||
	    hid_device.ops->int_in_ready == NULL) {
		return;
	}
	hid_device.ops->int_in_ready();
}

#ifdef CONFIG_ENABLE_HID_INT_OUT_EP
static void hid_int_out(u8_t ep, enum usb_dc_ep_cb_status_code ep_status)
{
	if (ep_status != USB_DC_EP_DATA_OUT ||
	    hid_device.ops->int_out_ready == NULL) {
		return;
	}
	hid_device.ops->int_out_ready();
}
#endif

/* Describe Endpoints configuration */
static struct usb_ep_cfg_data hid_ep_data[] = {
	{
		.ep_cb = hid_int_in,
		.ep_addr = HID_INT_IN_EP_ADDR
	},
#ifdef CONFIG_ENABLE_HID_INT_OUT_EP
	{
		.ep_cb = hid_int_out,
		.ep_addr = HID_INT_OUT_EP_ADDR,

	}
#endif
};

static void hid_interface_config(u8_t bInterfaceNumber)
{
	hid_cfg.if0.bInterfaceNumber = bInterfaceNumber;
	hid_cfg.if0.bNumEndpoints = ARRAY_SIZE(hid_ep_data);
}

USBD_CFG_DATA_DEFINE(hid) struct usb_cfg_data hid_config = {
	.usb_device_description = NULL,
	.interface_config = hid_interface_config,
	.interface_descriptor = &hid_cfg.if0,
	.cb_usb_status = hid_status_cb,
	.interface = {
		.class_handler = hid_class_handle_req,
		.custom_handler = hid_custom_handle_req,
		.payload_data = NULL,
	},
	.num_endpoints = ARRAY_SIZE(hid_ep_data),
	.endpoint = hid_ep_data,
};

#if !defined(CONFIG_USB_COMPOSITE_DEVICE)
static u8_t interface_data[CONFIG_USB_HID_MAX_PAYLOAD_SIZE];
#endif

int usb_hid_init(void)
{
	SYS_LOG_DBG("Iinitializing HID Device");

	/*
	 * Modify Report Descriptor Size
	 */
	usb_set_hid_report_size(hid_device.report_size);

#ifndef CONFIG_USB_COMPOSITE_DEVICE
	int ret;

	hid_config.interface.payload_data = interface_data;
	hid_config.usb_device_description = usb_get_device_descriptor();

	/* Initialize the USB driver with the right configuration */
	ret = usb_set_config(&hid_config);
	if (ret < 0) {
		SYS_LOG_ERR("Failed to config USB");
		return ret;
	}

	/* Enable USB driver */
	ret = usb_enable(&hid_config);
	if (ret < 0) {
		SYS_LOG_ERR("Failed to enable USB");
		return ret;
	}
#endif

	return 0;
}

void usb_hid_register_device(const u8_t *desc, size_t size,
			     const struct hid_ops *ops)
{
	hid_device.report_desc = desc;
	hid_device.report_size = size;

	hid_device.ops = ops;
}

int hid_int_ep_write(const u8_t *data, u32_t data_len, u32_t *bytes_ret)
{
	return usb_write(hid_ep_data[HID_INT_IN_EP_IDX].ep_addr, data,
			 data_len, bytes_ret);
}

int hid_int_ep_read(u8_t *data, u32_t max_data_len, u32_t *ret_bytes)
{
#ifdef CONFIG_ENABLE_HID_INT_OUT_EP
	return usb_read(hid_ep_data[HID_INT_OUT_EP_IDX].ep_addr,
			data, max_data_len, ret_bytes);
#else
	return -ENOTSUP;
#endif
}