1700 lines · 43.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* * f_hid.c -- USB HID function driver * * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com> */ #include <linux/kernel.h>#include <linux/module.h>#include <linux/hid.h>#include <linux/idr.h>#include <linux/cdev.h>#include <linux/mutex.h>#include <linux/poll.h>#include <linux/uaccess.h>#include <linux/wait.h>#include <linux/sched.h>#include <linux/workqueue.h>#include <linux/usb/func_utils.h>#include <linux/usb/g_hid.h>#include <uapi/linux/usb/g_hid.h> #include "u_hid.h" #define HIDG_MINORS	4 /* * Most operating systems seem to allow for 5000ms timeout, we will allow * userspace half that time to respond before we return an empty report. */#define GET_REPORT_TIMEOUT_MS 2500 static int major, minors; static const struct class hidg_class = {	.name = "hidg",}; static DEFINE_IDA(hidg_ida);static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */ struct report_entry {	struct usb_hidg_report report_data;	struct list_head node;}; /*-------------------------------------------------------------------------*//*                            HID gadget struct                            */ struct f_hidg_req_list {	struct usb_request	*req;	unsigned int		pos;	struct list_head 	list;}; struct f_hidg {	/* configuration */	unsigned char			bInterfaceSubClass;	unsigned char			bInterfaceProtocol;	unsigned char			protocol;	unsigned char			idle;	unsigned short			report_desc_length;	char				*report_desc;	unsigned short			report_length;	unsigned char			interval;	bool				interval_user_set; 	/*	 * use_out_ep - if true, the OUT Endpoint (interrupt out method)	 *              will be used to receive reports from the host	 *              using functions with the "intout" suffix.	 *              Otherwise, the OUT Endpoint will not be configured	 *              and the SETUP/SET_REPORT method ("ssreport" suffix)	 *              will be used to receive reports.	 */	bool				use_out_ep; 	/* recv report */	spinlock_t			read_spinlock;	wait_queue_head_t		read_queue;	bool				disabled;	/* recv report - interrupt out only (use_out_ep == 1) */	struct list_head		completed_out_req;	unsigned int			qlen;	/* recv report - setup set_report only (use_out_ep == 0) */	char				*set_report_buf;	unsigned int			set_report_length; 	/* send report */	spinlock_t			write_spinlock;	bool				write_pending;	wait_queue_head_t		write_queue;	struct usb_request		*req; 	/* get report */	struct usb_request		*get_req;	struct usb_hidg_report		get_report;	bool				get_report_returned;	int				get_report_req_report_id;	int				get_report_req_report_length;	spinlock_t			get_report_spinlock;	wait_queue_head_t		get_queue;    /* Waiting for userspace response */	wait_queue_head_t		get_id_queue; /* Get ID came in */	struct work_struct		work;	struct workqueue_struct		*workqueue;	struct list_head		report_list; 	struct device			dev;	struct cdev			*cdev;	struct usb_function		func; 	struct usb_ep			*in_ep;	struct usb_ep			*out_ep;}; static inline struct f_hidg *func_to_hidg(struct usb_function *f){	return container_of(f, struct f_hidg, func);} static void hidg_release(struct device *dev){	struct f_hidg *hidg = container_of(dev, struct f_hidg, dev); 	kfree(hidg->report_desc);	kfree(hidg->set_report_buf);	kfree(hidg);} /*-------------------------------------------------------------------------*//*                           Static descriptors                            */ static struct usb_interface_descriptor hidg_interface_desc = {	.bLength		= sizeof hidg_interface_desc,	.bDescriptorType	= USB_DT_INTERFACE,	/* .bInterfaceNumber	= DYNAMIC */	.bAlternateSetting	= 0,	/* .bNumEndpoints	= DYNAMIC (depends on use_out_ep) */	.bInterfaceClass	= USB_CLASS_HID,	/* .bInterfaceSubClass	= DYNAMIC */	/* .bInterfaceProtocol	= DYNAMIC */	/* .iInterface		= DYNAMIC */}; static struct hid_descriptor hidg_desc = {	.bLength			= sizeof hidg_desc,	.bDescriptorType		= HID_DT_HID,	.bcdHID				= cpu_to_le16(0x0101),	.bCountryCode			= 0x00,	.bNumDescriptors		= 0x1,	/*.rpt_desc.bDescriptorType	= DYNAMIC */	/*.rpt_desc.wDescriptorLength	= DYNAMIC */}; /* Super-Speed Support */ static struct usb_endpoint_descriptor hidg_ss_in_ep_desc = {	.bLength		= USB_DT_ENDPOINT_SIZE,	.bDescriptorType	= USB_DT_ENDPOINT,	.bEndpointAddress	= USB_DIR_IN,	.bmAttributes		= USB_ENDPOINT_XFER_INT,	/*.wMaxPacketSize	= DYNAMIC */	/*.bInterval		= DYNAMIC */}; static struct usb_ss_ep_comp_descriptor hidg_ss_in_comp_desc = {	.bLength                = sizeof(hidg_ss_in_comp_desc),	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP, 	/* .bMaxBurst           = 0, */	/* .bmAttributes        = 0, */	/* .wBytesPerInterval   = DYNAMIC */}; static struct usb_endpoint_descriptor hidg_ss_out_ep_desc = {	.bLength		= USB_DT_ENDPOINT_SIZE,	.bDescriptorType	= USB_DT_ENDPOINT,	.bEndpointAddress	= USB_DIR_OUT,	.bmAttributes		= USB_ENDPOINT_XFER_INT,	/*.wMaxPacketSize	= DYNAMIC */	/*.bInterval		= DYNAMIC */}; static struct usb_ss_ep_comp_descriptor hidg_ss_out_comp_desc = {	.bLength                = sizeof(hidg_ss_out_comp_desc),	.bDescriptorType        = USB_DT_SS_ENDPOINT_COMP, 	/* .bMaxBurst           = 0, */	/* .bmAttributes        = 0, */	/* .wBytesPerInterval   = DYNAMIC */}; static struct usb_descriptor_header *hidg_ss_descriptors_intout[] = {	(struct usb_descriptor_header *)&hidg_interface_desc,	(struct usb_descriptor_header *)&hidg_desc,	(struct usb_descriptor_header *)&hidg_ss_in_ep_desc,	(struct usb_descriptor_header *)&hidg_ss_in_comp_desc,	(struct usb_descriptor_header *)&hidg_ss_out_ep_desc,	(struct usb_descriptor_header *)&hidg_ss_out_comp_desc,	NULL,}; static struct usb_descriptor_header *hidg_ss_descriptors_ssreport[] = {	(struct usb_descriptor_header *)&hidg_interface_desc,	(struct usb_descriptor_header *)&hidg_desc,	(struct usb_descriptor_header *)&hidg_ss_in_ep_desc,	(struct usb_descriptor_header *)&hidg_ss_in_comp_desc,	NULL,}; /* High-Speed Support */ static struct usb_endpoint_descriptor hidg_hs_in_ep_desc = {	.bLength		= USB_DT_ENDPOINT_SIZE,	.bDescriptorType	= USB_DT_ENDPOINT,	.bEndpointAddress	= USB_DIR_IN,	.bmAttributes		= USB_ENDPOINT_XFER_INT,	/*.wMaxPacketSize	= DYNAMIC */	/* .bInterval		= DYNAMIC */}; static struct usb_endpoint_descriptor hidg_hs_out_ep_desc = {	.bLength		= USB_DT_ENDPOINT_SIZE,	.bDescriptorType	= USB_DT_ENDPOINT,	.bEndpointAddress	= USB_DIR_OUT,	.bmAttributes		= USB_ENDPOINT_XFER_INT,	/*.wMaxPacketSize	= DYNAMIC */	/*.bInterval		= DYNAMIC */}; static struct usb_descriptor_header *hidg_hs_descriptors_intout[] = {	(struct usb_descriptor_header *)&hidg_interface_desc,	(struct usb_descriptor_header *)&hidg_desc,	(struct usb_descriptor_header *)&hidg_hs_in_ep_desc,	(struct usb_descriptor_header *)&hidg_hs_out_ep_desc,	NULL,}; static struct usb_descriptor_header *hidg_hs_descriptors_ssreport[] = {	(struct usb_descriptor_header *)&hidg_interface_desc,	(struct usb_descriptor_header *)&hidg_desc,	(struct usb_descriptor_header *)&hidg_hs_in_ep_desc,	NULL,}; /* Full-Speed Support */ static struct usb_endpoint_descriptor hidg_fs_in_ep_desc = {	.bLength		= USB_DT_ENDPOINT_SIZE,	.bDescriptorType	= USB_DT_ENDPOINT,	.bEndpointAddress	= USB_DIR_IN,	.bmAttributes		= USB_ENDPOINT_XFER_INT,	/*.wMaxPacketSize	= DYNAMIC */	/*.bInterval		= DYNAMIC */}; static struct usb_endpoint_descriptor hidg_fs_out_ep_desc = {	.bLength		= USB_DT_ENDPOINT_SIZE,	.bDescriptorType	= USB_DT_ENDPOINT,	.bEndpointAddress	= USB_DIR_OUT,	.bmAttributes		= USB_ENDPOINT_XFER_INT,	/*.wMaxPacketSize	= DYNAMIC */	/*.bInterval		= DYNAMIC */}; static struct usb_descriptor_header *hidg_fs_descriptors_intout[] = {	(struct usb_descriptor_header *)&hidg_interface_desc,	(struct usb_descriptor_header *)&hidg_desc,	(struct usb_descriptor_header *)&hidg_fs_in_ep_desc,	(struct usb_descriptor_header *)&hidg_fs_out_ep_desc,	NULL,}; static struct usb_descriptor_header *hidg_fs_descriptors_ssreport[] = {	(struct usb_descriptor_header *)&hidg_interface_desc,	(struct usb_descriptor_header *)&hidg_desc,	(struct usb_descriptor_header *)&hidg_fs_in_ep_desc,	NULL,}; /*-------------------------------------------------------------------------*//*                                 Strings                                 */ #define CT_FUNC_HID_IDX	0 static struct usb_string ct_func_string_defs[] = {	[CT_FUNC_HID_IDX].s	= "HID Interface",	{},			/* end of list */}; static struct usb_gadget_strings ct_func_string_table = {	.language	= 0x0409,	/* en-US */	.strings	= ct_func_string_defs,}; static struct usb_gadget_strings *ct_func_strings[] = {	&ct_func_string_table,	NULL,}; /*-------------------------------------------------------------------------*//*                              Char Device                                */ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,				  size_t count, loff_t *ptr){	struct f_hidg *hidg = file->private_data;	struct f_hidg_req_list *list;	struct usb_request *req;	unsigned long flags;	int ret; 	if (!count)		return 0; 	spin_lock_irqsave(&hidg->read_spinlock, flags); #define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req) || hidg->disabled) 	/* wait for at least one buffer to complete */	while (!READ_COND_INTOUT) {		spin_unlock_irqrestore(&hidg->read_spinlock, flags);		if (file->f_flags & O_NONBLOCK)			return -EAGAIN; 		if (wait_event_interruptible(hidg->read_queue, READ_COND_INTOUT))			return -ERESTARTSYS; 		spin_lock_irqsave(&hidg->read_spinlock, flags);	} 	if (hidg->disabled) {		spin_unlock_irqrestore(&hidg->read_spinlock, flags);		return -ESHUTDOWN;	} 	/* pick the first one */	list = list_first_entry(&hidg->completed_out_req,				struct f_hidg_req_list, list); 	/*	 * Remove this from list to protect it from beign free()	 * while host disables our function	 */	list_del(&list->list); 	req = list->req;	count = min_t(unsigned int, count, req->actual - list->pos);	spin_unlock_irqrestore(&hidg->read_spinlock, flags); 	/* copy to user outside spinlock */	count -= copy_to_user(buffer, req->buf + list->pos, count);	list->pos += count; 	/*	 * if this request is completely handled and transfered to	 * userspace, remove its entry from the list and requeue it	 * again. Otherwise, we will revisit it again upon the next	 * call, taking into account its current read position.	 */	if (list->pos == req->actual) {		kfree(list); 		req->length = hidg->report_length;		ret = usb_ep_queue(hidg->out_ep, req, GFP_KERNEL);		if (ret < 0) {			free_ep_req(hidg->out_ep, req);			return ret;		}	} else {		spin_lock_irqsave(&hidg->read_spinlock, flags);		list_add(&list->list, &hidg->completed_out_req);		spin_unlock_irqrestore(&hidg->read_spinlock, flags); 		wake_up(&hidg->read_queue);	} 	return count;} #define READ_COND_SSREPORT (hidg->set_report_buf != NULL || hidg->disabled) static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer,				    size_t count, loff_t *ptr){	struct f_hidg *hidg = file->private_data;	char *tmp_buf = NULL;	unsigned long flags; 	if (!count)		return 0; 	spin_lock_irqsave(&hidg->read_spinlock, flags); 	while (!READ_COND_SSREPORT) {		spin_unlock_irqrestore(&hidg->read_spinlock, flags);		if (file->f_flags & O_NONBLOCK)			return -EAGAIN; 		if (wait_event_interruptible(hidg->read_queue, READ_COND_SSREPORT))			return -ERESTARTSYS; 		spin_lock_irqsave(&hidg->read_spinlock, flags);	} 	count = min_t(unsigned int, count, hidg->set_report_length);	tmp_buf = hidg->set_report_buf;	hidg->set_report_buf = NULL; 	spin_unlock_irqrestore(&hidg->read_spinlock, flags); 	if (tmp_buf != NULL) {		count -= copy_to_user(buffer, tmp_buf, count);		kfree(tmp_buf);	} else {		count = -ENOMEM;	} 	wake_up(&hidg->read_queue); 	return count;} static ssize_t f_hidg_read(struct file *file, char __user *buffer,			   size_t count, loff_t *ptr){	struct f_hidg *hidg = file->private_data; 	if (hidg->use_out_ep)		return f_hidg_intout_read(file, buffer, count, ptr);	else		return f_hidg_ssreport_read(file, buffer, count, ptr);} static void f_hidg_req_complete(struct usb_ep *ep, struct usb_request *req){	struct f_hidg *hidg = (struct f_hidg *)ep->driver_data;	unsigned long flags; 	if (req->status != 0) {		ERROR(hidg->func.config->cdev,			"End Point Request ERROR: %d\n", req->status);	} 	spin_lock_irqsave(&hidg->write_spinlock, flags);	hidg->write_pending = 0;	spin_unlock_irqrestore(&hidg->write_spinlock, flags);	wake_up(&hidg->write_queue);} static ssize_t f_hidg_write(struct file *file, const char __user *buffer,			    size_t count, loff_t *offp){	struct f_hidg *hidg  = file->private_data;	struct usb_request *req;	unsigned long flags;	ssize_t status = -ENOMEM; 	spin_lock_irqsave(&hidg->write_spinlock, flags); 	if (!hidg->req) {		spin_unlock_irqrestore(&hidg->write_spinlock, flags);		return -ESHUTDOWN;	} #define WRITE_COND (!hidg->write_pending)try_again:	/* write queue */	while (!WRITE_COND) {		spin_unlock_irqrestore(&hidg->write_spinlock, flags);		if (file->f_flags & O_NONBLOCK)			return -EAGAIN; 		if (wait_event_interruptible_exclusive(				hidg->write_queue, WRITE_COND))			return -ERESTARTSYS; 		spin_lock_irqsave(&hidg->write_spinlock, flags);	} 	hidg->write_pending = 1;	req = hidg->req;	count  = min_t(unsigned, count, hidg->report_length); 	spin_unlock_irqrestore(&hidg->write_spinlock, flags); 	if (!req) {		ERROR(hidg->func.config->cdev, "hidg->req is NULL\n");		status = -ESHUTDOWN;		goto release_write_pending;	} 	status = copy_from_user(req->buf, buffer, count);	if (status != 0) {		ERROR(hidg->func.config->cdev,			"copy_from_user error\n");		status = -EINVAL;		goto release_write_pending;	} 

Lines 1–500 of 1700.