Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
8dd5c0c
Documentation
arch
block
certs
crypto
drivers
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cdrom
char
clk
clocksource
connector
cpufreq
cpuidle
crypto
dax
dca
devfreq
dio
dma-buf
dma
edac
eisa
extcon
firewire
firmware
fmc
fpga
gpio
gpu
hid
hsi
hv
hwmon
hwspinlock
hwtracing
i2c
ide
idle
iio
infiniband
input
iommu
ipack
irqchip
isdn
leds
lguest
lightnvm
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
mtd
net
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
oprofile
parisc
parport
pci
pcmcia
perf
phy
pinctrl
platform
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sfi
sh
sn
soc
spi
spmi
ssb
staging
target
tc
thermal
thunderbolt
tty
uio
usb
atm
c67x00
chipidea
class
common
core
dwc2
dwc3
early
gadget
function
Makefile
f_acm.c
f_ecm.c
f_eem.c
f_fs.c
f_hid.c
f_loopback.c
f_mass_storage.c
f_mass_storage.h
f_midi.c
f_ncm.c
f_obex.c
f_phonet.c
f_printer.c
f_rndis.c
f_serial.c
f_sourcesink.c
f_subset.c
f_tcm.c
f_uac1.c
f_uac2.c
f_uvc.c
f_uvc.h
g_zero.h
ndis.h
rndis.c
rndis.h
storage_common.c
storage_common.h
tcm.h
u_ecm.h
u_eem.h
u_ether.c
u_ether.h
u_ether_configfs.h
u_fs.h
u_gether.h
u_hid.h
u_midi.h
u_ncm.h
u_phonet.h
u_printer.h
u_rndis.h
u_serial.c
u_serial.h
u_tcm.h
u_uac1.c
u_uac1.h
u_uac2.h
u_uvc.h
uvc.h
uvc_configfs.c
uvc_configfs.h
uvc_queue.c
uvc_queue.h
uvc_v4l2.c
uvc_v4l2.h
uvc_video.c
uvc_video.h
legacy
udc
Kconfig
Makefile
composite.c
config.c
configfs.c
configfs.h
epautoconf.c
functions.c
u_f.c
u_f.h
u_os_desc.h
usbstring.c
host
image
isp1760
misc
mon
musb
phy
renesas_usbhs
serial
storage
usbip
wusbcore
Kconfig
Makefile
README
usb-skeleton.c
uwb
vfio
vhost
video
virt
virtio
vlynq
vme
w1
watchdog
xen
zorro
Kconfig
Makefile
firmware
fs
include
init
ipc
kernel
lib
mm
net
samples
scripts
security
sound
tools
usr
virt
.cocciconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
REPORTING-BUGS
Breadcrumbs
linux
/
drivers
/
usb
/
gadget
/
function
/
f_hid.c
Copy path
Blame
Blame
Latest commit
History
History
1094 lines (908 loc) · 26.6 KB
Breadcrumbs
linux
/
drivers
/
usb
/
gadget
/
function
/
f_hid.c
Top
File metadata and controls
Code
Blame
1094 lines (908 loc) · 26.6 KB
Raw
/* * f_hid.c -- USB HID function driver * * Copyright (C) 2010 Fabien Chouteau <fabien.chouteau@barco.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #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/usb/g_hid.h> #include "u_f.h" #include "u_hid.h" #define HIDG_MINORS 4 static int major, minors; static struct class *hidg_class; static DEFINE_IDA(hidg_ida); static DEFINE_MUTEX(hidg_ida_lock); /* protects access to hidg_ida */ /*-------------------------------------------------------------------------*/ /* 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 short report_desc_length; char *report_desc; unsigned short report_length; /* recv report */ struct list_head completed_out_req; spinlock_t read_spinlock; wait_queue_head_t read_queue; unsigned int qlen; /* send report */ spinlock_t write_spinlock; bool write_pending; wait_queue_head_t write_queue; struct usb_request *req; int minor; 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 descriptors */ static struct usb_interface_descriptor hidg_interface_desc = { .bLength = sizeof hidg_interface_desc, .bDescriptorType = USB_DT_INTERFACE, /* .bInterfaceNumber = DYNAMIC */ .bAlternateSetting = 0, .bNumEndpoints = 2, .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 = 0x0101, .bCountryCode = 0x00, .bNumDescriptors = 0x1, /*.desc[0].bDescriptorType = DYNAMIC */ /*.desc[0].wDescriptorLenght = DYNAMIC */ }; /* 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 = 4, /* FIXME: Add this field in the * HID gadget configuration? * (struct hidg_func_descriptor) */ }; 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 = 4, /* FIXME: Add this field in the * HID gadget configuration? * (struct hidg_func_descriptor) */ }; static struct usb_descriptor_header *hidg_hs_descriptors[] = { (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, }; /* 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 = 10, /* FIXME: Add this field in the * HID gadget configuration? * (struct hidg_func_descriptor) */ }; 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 = 10, /* FIXME: Add this field in the * HID gadget configuration? * (struct hidg_func_descriptor) */ }; static struct usb_descriptor_header *hidg_fs_descriptors[] = { (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, }; /*-------------------------------------------------------------------------*/ /* 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_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; if (!access_ok(VERIFY_WRITE, buffer, count)) return -EFAULT; spin_lock_irqsave(&hidg->read_spinlock, flags); #define READ_COND (!list_empty(&hidg->completed_out_req)) /* wait for at least one buffer to complete */ while (!READ_COND) { spin_unlock_irqrestore(&hidg->read_spinlock, flags); if (file->f_flags & O_NONBLOCK) return -EAGAIN; if (wait_event_interruptible(hidg->read_queue, READ_COND)) return -ERESTARTSYS; spin_lock_irqsave(&hidg->read_spinlock, flags); } /* 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; } 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; if (!access_ok(VERIFY_READ, buffer, count)) return -EFAULT; spin_lock_irqsave(&hidg->write_spinlock, flags); #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); status = copy_from_user(hidg->req->buf, buffer, count); if (status != 0) { ERROR(hidg->func.config->cdev, "copy_from_user error\n"); status = -EINVAL; goto release_write_pending; } spin_lock_irqsave(&hidg->write_spinlock, flags); /* we our function has been disabled by host */ if (!hidg->req) { free_ep_req(hidg->in_ep, hidg->req); /* * TODO * Should we fail with error here? */ goto try_again; } req->status = 0; req->zero = 0; req->length = count; req->complete = f_hidg_req_complete; req->context = hidg; status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC); if (status < 0) { ERROR(hidg->func.config->cdev, "usb_ep_queue error on int endpoint %zd\n", status); goto release_write_pending_unlocked; } else { status = count; } spin_unlock_irqrestore(&hidg->write_spinlock, flags); return status; release_write_pending: spin_lock_irqsave(&hidg->write_spinlock, flags); release_write_pending_unlocked: hidg->write_pending = 0; spin_unlock_irqrestore(&hidg->write_spinlock, flags); wake_up(&hidg->write_queue); return status; } static unsigned int f_hidg_poll(struct file *file, poll_table *wait) { struct f_hidg *hidg = file->private_data; unsigned int ret = 0; poll_wait(file, &hidg->read_queue, wait); poll_wait(file, &hidg->write_queue, wait); if (WRITE_COND) ret |= POLLOUT | POLLWRNORM; if (READ_COND) ret |= POLLIN | POLLRDNORM; return ret; } #undef WRITE_COND #undef READ_COND static int f_hidg_release(struct inode *inode, struct file *fd) { fd->private_data = NULL; return 0; } static int f_hidg_open(struct inode *inode, struct file *fd) { struct f_hidg *hidg = container_of(inode->i_cdev, struct f_hidg, cdev); fd->private_data = hidg; return 0; } /*-------------------------------------------------------------------------*/ /* usb_function */ static inline struct usb_request *hidg_alloc_ep_req(struct usb_ep *ep, unsigned length) { return alloc_ep_req(ep, length); } static void hidg_set_report_complete(struct usb_ep *ep, struct usb_request *req) { struct f_hidg *hidg = (struct f_hidg *) req->context; struct usb_composite_dev *cdev = hidg->func.config->cdev; struct f_hidg_req_list *req_list; unsigned long flags; switch (req->status) { case 0: req_list = kzalloc(sizeof(*req_list), GFP_ATOMIC); if (!req_list) { ERROR(cdev, "Unable to allocate mem for req_list\n"); goto free_req; } req_list->req = req; spin_lock_irqsave(&hidg->read_spinlock, flags); list_add_tail(&req_list->list, &hidg->completed_out_req); spin_unlock_irqrestore(&hidg->read_spinlock, flags); wake_up(&hidg->read_queue); break; default: ERROR(cdev, "Set report failed %d\n", req->status); /* FALLTHROUGH */ case -ECONNABORTED: /* hardware forced ep reset */ case -ECONNRESET: /* request dequeued */ case -ESHUTDOWN: /* disconnect from host */ free_req: free_ep_req(ep, req); return; } } static int hidg_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { struct f_hidg *hidg = func_to_hidg(f); struct usb_composite_dev *cdev = f->config->cdev; struct usb_request *req = cdev->req; int status = 0; __u16 value, length; value = __le16_to_cpu(ctrl->wValue); length = __le16_to_cpu(ctrl->wLength); VDBG(cdev, "%s crtl_request : bRequestType:0x%x bRequest:0x%x Value:0x%x\n", __func__, ctrl->bRequestType, ctrl->bRequest, value); switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_GET_REPORT): VDBG(cdev, "get_report\n"); /* send an empty report */ length = min_t(unsigned, length, hidg->report_length); memset(req->buf, 0x0, length); goto respond; break; case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_GET_PROTOCOL): VDBG(cdev, "get_protocol\n"); goto stall; break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_SET_REPORT): VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength); goto stall; break; case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 | HID_REQ_SET_PROTOCOL): VDBG(cdev, "set_protocol\n"); goto stall; break; case ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8 | USB_REQ_GET_DESCRIPTOR): switch (value >> 8) { case HID_DT_HID: { struct hid_descriptor hidg_desc_copy = hidg_desc; VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: HID\n"); hidg_desc_copy.desc[0].bDescriptorType = HID_DT_REPORT; hidg_desc_copy.desc[0].wDescriptorLength = cpu_to_le16(hidg->report_desc_length); length = min_t(unsigned short, length, hidg_desc_copy.bLength); memcpy(req->buf, &hidg_desc_copy, length); goto respond; break; } case HID_DT_REPORT: VDBG(cdev, "USB_REQ_GET_DESCRIPTOR: REPORT\n"); length = min_t(unsigned short, length, hidg->report_desc_length); memcpy(req->buf, hidg->report_desc, length); goto respond; break; default: VDBG(cdev, "Unknown descriptor request 0x%x\n", value >> 8); goto stall; break; } break; default: VDBG(cdev, "Unknown request 0x%x\n", ctrl->bRequest); goto stall; break; } stall: return -EOPNOTSUPP; respond: req->zero = 0; req->length = length; status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (status < 0) ERROR(cdev, "usb_ep_queue error on ep0 %d\n", value); return status; } static void hidg_disable(struct usb_function *f) { struct f_hidg *hidg = func_to_hidg(f); struct f_hidg_req_list *list, *next; unsigned long flags; usb_ep_disable(hidg->in_ep); usb_ep_disable(hidg->out_ep); spin_lock_irqsave(&hidg->read_spinlock, flags); list_for_each_entry_safe(list, next, &hidg->completed_out_req, list) { free_ep_req(hidg->out_ep, list->req); list_del(&list->list); kfree(list); } spin_unlock_irqrestore(&hidg->read_spinlock, flags); spin_lock_irqsave(&hidg->write_spinlock, flags); if (!hidg->write_pending) { free_ep_req(hidg->in_ep, hidg->req); hidg->write_pending = 1; } hidg->req = NULL; spin_unlock_irqrestore(&hidg->write_spinlock, flags); } static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = f->config->cdev; struct f_hidg *hidg = func_to_hidg(f); struct usb_request *req_in = NULL; unsigned long flags; int i, status = 0; VDBG(cdev, "hidg_set_alt intf:%d alt:%d\n", intf, alt); if (hidg->in_ep != NULL) { /* restart endpoint */ usb_ep_disable(hidg->in_ep); status = config_ep_by_speed(f->config->cdev->gadget, f, hidg->in_ep); if (status) { ERROR(cdev, "config_ep_by_speed FAILED!\n"); goto fail; } status = usb_ep_enable(hidg->in_ep); if (status < 0) { ERROR(cdev, "Enable IN endpoint FAILED!\n"); goto fail; } hidg->in_ep->driver_data = hidg; req_in = hidg_alloc_ep_req(hidg->in_ep, hidg->report_length); if (!req_in) { status = -ENOMEM; goto disable_ep_in; } } if (hidg->out_ep != NULL) { /* restart endpoint */ usb_ep_disable(hidg->out_ep); status = config_ep_by_speed(f->config->cdev->gadget, f, hidg->out_ep); if (status) { ERROR(cdev, "config_ep_by_speed FAILED!\n"); goto free_req_in; } status = usb_ep_enable(hidg->out_ep); if (status < 0) { ERROR(cdev, "Enable OUT endpoint FAILED!\n"); goto free_req_in; } hidg->out_ep->driver_data = hidg; /* * allocate a bunch of read buffers and queue them all at once. */ for (i = 0; i < hidg->qlen && status == 0; i++) { struct usb_request *req = hidg_alloc_ep_req(hidg->out_ep, hidg->report_length); if (req) { req->complete = hidg_set_report_complete; req->context = hidg; status = usb_ep_queue(hidg->out_ep, req, GFP_ATOMIC); if (status) { ERROR(cdev, "%s queue req --> %d\n", hidg->out_ep->name, status); free_ep_req(hidg->out_ep, req); } } else { status = -ENOMEM; goto disable_out_ep; } } } if (hidg->in_ep != NULL) { spin_lock_irqsave(&hidg->write_spinlock, flags); hidg->req = req_in; hidg->write_pending = 0; spin_unlock_irqrestore(&hidg->write_spinlock, flags); wake_up(&hidg->write_queue); } return 0; disable_out_ep: usb_ep_disable(hidg->out_ep); free_req_in: if (req_in) free_ep_req(hidg->in_ep, req_in); disable_ep_in: if (hidg->in_ep) usb_ep_disable(hidg->in_ep); fail: return status; } static const struct file_operations f_hidg_fops = { .owner = THIS_MODULE, .open = f_hidg_open, .release = f_hidg_release, .write = f_hidg_write, .read = f_hidg_read, .poll = f_hidg_poll, .llseek = noop_llseek, }; static int hidg_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_ep *ep; struct f_hidg *hidg = func_to_hidg(f); struct usb_string *us; struct device *device; int status; dev_t dev; /* maybe allocate device-global string IDs, and patch descriptors */ us = usb_gstrings_attach(c->cdev, ct_func_strings, ARRAY_SIZE(ct_func_string_defs)); if (IS_ERR(us)) return PTR_ERR(us); hidg_interface_desc.iInterface = us[CT_FUNC_HID_IDX].id; /* allocate instance-specific interface IDs, and patch descriptors */ status = usb_interface_id(c, f); if (status < 0) goto fail; hidg_interface_desc.bInterfaceNumber = status; /* allocate instance-specific endpoints */ status = -ENODEV; ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_in_ep_desc); if (!ep) goto fail; hidg->in_ep = ep; ep = usb_ep_autoconfig(c->cdev->gadget, &hidg_fs_out_ep_desc); if (!ep) goto fail; hidg->out_ep = ep; /* set descriptor dynamic values */ hidg_interface_desc.bInterfaceSubClass = hidg->bInterfaceSubClass; hidg_interface_desc.bInterfaceProtocol = hidg->bInterfaceProtocol; hidg_hs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_in_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_hs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); hidg_fs_out_ep_desc.wMaxPacketSize = cpu_to_le16(hidg->report_length); /* * We can use hidg_desc struct here but we should not relay * that its content won't change after returning from this function. */ hidg_desc.desc[0].bDescriptorType = HID_DT_REPORT; hidg_desc.desc[0].wDescriptorLength = cpu_to_le16(hidg->report_desc_length); hidg_hs_in_ep_desc.bEndpointAddress = hidg_fs_in_ep_desc.bEndpointAddress; hidg_hs_out_ep_desc.bEndpointAddress = hidg_fs_out_ep_desc.bEndpointAddress; status = usb_assign_descriptors(f, hidg_fs_descriptors, hidg_hs_descriptors, NULL, NULL); if (status) goto fail; spin_lock_init(&hidg->write_spinlock); hidg->write_pending = 1; hidg->req = NULL; spin_lock_init(&hidg->read_spinlock); init_waitqueue_head(&hidg->write_queue); init_waitqueue_head(&hidg->read_queue); INIT_LIST_HEAD(&hidg->completed_out_req); /* create char device */ cdev_init(&hidg->cdev, &f_hidg_fops); dev = MKDEV(major, hidg->minor); status = cdev_add(&hidg->cdev, dev, 1); if (status) goto fail_free_descs; device = device_create(hidg_class, NULL, dev, NULL, "%s%d", "hidg", hidg->minor); if (IS_ERR(device)) { status = PTR_ERR(device); goto del; } return 0; del: cdev_del(&hidg->cdev); fail_free_descs: usb_free_all_descriptors(f); fail: ERROR(f->config->cdev, "hidg_bind FAILED\n"); if (hidg->req != NULL) free_ep_req(hidg->in_ep, hidg->req); return status; } static inline int hidg_get_minor(void) { int ret; ret = ida_simple_get(&hidg_ida, 0, 0, GFP_KERNEL); if (ret >= HIDG_MINORS) { ida_simple_remove(&hidg_ida, ret); ret = -ENODEV; } return ret; } static inline struct f_hid_opts *to_f_hid_opts(struct config_item *item) { return container_of(to_config_group(item), struct f_hid_opts, func_inst.group); } static void hid_attr_release(struct config_item *item) { struct f_hid_opts *opts = to_f_hid_opts(item); usb_put_function_instance(&opts->func_inst); } static struct configfs_item_operations hidg_item_ops = { .release = hid_attr_release, }; #define F_HID_OPT(name, prec, limit) \ static ssize_t f_hid_opts_##name##_show(struct config_item *item, char *page)\ { \ struct f_hid_opts *opts = to_f_hid_opts(item); \ int result; \ \ mutex_lock(&opts->lock); \ result = sprintf(page, "%d\n", opts->name); \ mutex_unlock(&opts->lock); \ \ return result; \ } \ \ static ssize_t f_hid_opts_##name##_store(struct config_item *item, \ const char *page, size_t len) \ { \ struct f_hid_opts *opts = to_f_hid_opts(item); \ int ret; \ u##prec num; \ \ mutex_lock(&opts->lock); \ if (opts->refcnt) { \ ret = -EBUSY; \ goto end; \ } \ \ ret = kstrtou##prec(page, 0, &num); \ if (ret) \ goto end; \ \ if (num > limit) { \ ret = -EINVAL; \ goto end; \ } \ opts->name = num; \ ret = len; \ \ end: \ mutex_unlock(&opts->lock); \ return ret; \ } \ \ CONFIGFS_ATTR(f_hid_opts_, name) F_HID_OPT(subclass, 8, 255); F_HID_OPT(protocol, 8, 255); F_HID_OPT(report_length, 16, 65535); static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page) { struct f_hid_opts *opts = to_f_hid_opts(item); int result; mutex_lock(&opts->lock); result = opts->report_desc_length; memcpy(page, opts->report_desc, opts->report_desc_length); mutex_unlock(&opts->lock); return result; } static ssize_t f_hid_opts_report_desc_store(struct config_item *item, const char *page, size_t len) { struct f_hid_opts *opts = to_f_hid_opts(item); int ret = -EBUSY; char *d; mutex_lock(&opts->lock); if (opts->refcnt) goto end; if (len > PAGE_SIZE) { ret = -ENOSPC; goto end; } d = kmemdup(page, len, GFP_KERNEL); if (!d) { ret = -ENOMEM; goto end; } kfree(opts->report_desc); opts->report_desc = d; opts->report_desc_length = len; opts->report_desc_alloc = true; ret = len; end: mutex_unlock(&opts->lock); return ret; } CONFIGFS_ATTR(f_hid_opts_, report_desc); static ssize_t f_hid_opts_dev_show(struct config_item *item, char *page) { struct f_hid_opts *opts = to_f_hid_opts(item); return sprintf(page, "%d:%d\n", major, opts->minor); } CONFIGFS_ATTR_RO(f_hid_opts_, dev); static struct configfs_attribute *hid_attrs[] = { &f_hid_opts_attr_subclass, &f_hid_opts_attr_protocol, &f_hid_opts_attr_report_length, &f_hid_opts_attr_report_desc, &f_hid_opts_attr_dev, NULL, }; static struct config_item_type hid_func_type = { .ct_item_ops = &hidg_item_ops, .ct_attrs = hid_attrs, .ct_owner = THIS_MODULE, }; static inline void hidg_put_minor(int minor) { ida_simple_remove(&hidg_ida, minor); } static void hidg_free_inst(struct usb_function_instance *f) { struct f_hid_opts *opts; opts = container_of(f, struct f_hid_opts, func_inst); mutex_lock(&hidg_ida_lock); hidg_put_minor(opts->minor); if (idr_is_empty(&hidg_ida.idr)) ghid_cleanup(); mutex_unlock(&hidg_ida_lock); if (opts->report_desc_alloc) kfree(opts->report_desc); kfree(opts); } static struct usb_function_instance *hidg_alloc_inst(void) { struct f_hid_opts *opts; struct usb_function_instance *ret; int status = 0; opts = kzalloc(sizeof(*opts), GFP_KERNEL); if (!opts) return ERR_PTR(-ENOMEM); mutex_init(&opts->lock); opts->func_inst.free_func_inst = hidg_free_inst; ret = &opts->func_inst; mutex_lock(&hidg_ida_lock); if (idr_is_empty(&hidg_ida.idr)) { status = ghid_setup(NULL, HIDG_MINORS); if (status) { ret = ERR_PTR(status); kfree(opts); goto unlock; } } opts->minor = hidg_get_minor(); if (opts->minor < 0) { ret = ERR_PTR(opts->minor); kfree(opts); if (idr_is_empty(&hidg_ida.idr)) ghid_cleanup(); goto unlock; } config_group_init_type_name(&opts->func_inst.group, "", &hid_func_type); unlock: mutex_unlock(&hidg_ida_lock); return ret; } static void hidg_free(struct usb_function *f) { struct f_hidg *hidg; struct f_hid_opts *opts; hidg = func_to_hidg(f); opts = container_of(f->fi, struct f_hid_opts, func_inst); kfree(hidg->report_desc); kfree(hidg); mutex_lock(&opts->lock); --opts->refcnt; mutex_unlock(&opts->lock); } static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) { struct f_hidg *hidg = func_to_hidg(f); device_destroy(hidg_class, MKDEV(major, hidg->minor)); cdev_del(&hidg->cdev); usb_free_all_descriptors(f); } static struct usb_function *hidg_alloc(struct usb_function_instance *fi) { struct f_hidg *hidg; struct f_hid_opts *opts; /* allocate and initialize one new instance */ hidg = kzalloc(sizeof(*hidg), GFP_KERNEL); if (!hidg) return ERR_PTR(-ENOMEM); opts = container_of(fi, struct f_hid_opts, func_inst); mutex_lock(&opts->lock); ++opts->refcnt; hidg->minor = opts->minor; hidg->bInterfaceSubClass = opts->subclass; hidg->bInterfaceProtocol = opts->protocol; hidg->report_length = opts->report_length; hidg->report_desc_length = opts->report_desc_length; if (opts->report_desc) { hidg->report_desc = kmemdup(opts->report_desc, opts->report_desc_length, GFP_KERNEL); if (!hidg->report_desc) { kfree(hidg); mutex_unlock(&opts->lock); return ERR_PTR(-ENOMEM); } } mutex_unlock(&opts->lock); hidg->func.name = "hid"; hidg->func.bind = hidg_bind; hidg->func.unbind = hidg_unbind; hidg->func.set_alt = hidg_set_alt; hidg->func.disable = hidg_disable; hidg->func.setup = hidg_setup; hidg->func.free_func = hidg_free; /* this could me made configurable at some point */ hidg->qlen = 4; return &hidg->func; } DECLARE_USB_FUNCTION_INIT(hid, hidg_alloc_inst, hidg_alloc); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Fabien Chouteau"); int ghid_setup(struct usb_gadget *g, int count) { int status; dev_t dev; hidg_class = class_create(THIS_MODULE, "hidg"); if (IS_ERR(hidg_class)) { status = PTR_ERR(hidg_class); hidg_class = NULL; return status; } status = alloc_chrdev_region(&dev, 0, count, "hidg"); if (status) { class_destroy(hidg_class); hidg_class = NULL; return status; } major = MAJOR(dev); minors = count; return 0; } void ghid_cleanup(void) { if (major) { unregister_chrdev_region(MKDEV(major, 0), minors); major = minors = 0; } class_destroy(hidg_class); hidg_class = NULL; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
You can’t perform that action at this time.