1
0
Files
Greg Kroah-Hartman fee50d6688 Merge 4.9.298 into android-4.9-q
Changes in 4.9.298
	Bluetooth: bfusb: fix division by zero in send path
	USB: core: Fix bug in resuming hub's handling of wakeup requests
	USB: Fix "slab-out-of-bounds Write" bug in usb_hcd_poll_rh_status
	mfd: intel-lpss: Fix too early PM enablement in the ACPI ->probe()
	can: gs_usb: fix use of uninitialized variable, detach device on reception of invalid USB data
	can: gs_usb: gs_can_start_xmit(): zero-initialize hf->{flags,reserved}
	random: fix data race on crng_node_pool
	random: fix data race on crng init time
	staging: wlan-ng: Avoid bitwise vs logical OR warning in hfa384x_usb_throttlefn()
	drm/i915: Avoid bitwise vs logical OR warning in snb_wm_latency_quirk()
	media: uvcvideo: fix division by zero at stream start
	rtlwifi: rtl8192cu: Fix WARNING when calling local_irq_restore() with interrupts enabled
	HID: uhid: Fix worker destroying device without any protection
	HID: wacom: Avoid using stale array indicies to read contact count
	nfc: llcp: fix NULL error pointer dereference on sendmsg() after failed bind()
	rtc: cmos: take rtc_lock while reading from CMOS
	media: flexcop-usb: fix control-message timeouts
	media: mceusb: fix control-message timeouts
	media: em28xx: fix control-message timeouts
	media: cpia2: fix control-message timeouts
	media: s2255: fix control-message timeouts
	media: dib0700: fix undefined behavior in tuner shutdown
	media: redrat3: fix control-message timeouts
	media: pvrusb2: fix control-message timeouts
	media: stk1160: fix control-message timeouts
	can: softing_cs: softingcs_probe(): fix memleak on registration failure
	PCI: Add function 1 DMA alias quirk for Marvell 88SE9125 SATA controller
	shmem: fix a race between shmem_unused_huge_shrink and shmem_evict_inode
	Bluetooth: cmtp: fix possible panic when cmtp_init_sockets() fails
	wcn36xx: Indicate beacon not connection loss on MISSED_BEACON_IND
	Bluetooth: stop proccessing malicious adv data
	media: dmxdev: fix UAF when dvb_register_device() fails
	crypto: qce - fix uaf on qce_ahash_register_one
	tty: serial: atmel: Check return code of dmaengine_submit()
	tty: serial: atmel: Call dma_async_issue_pending()
	netfilter: bridge: add support for pppoe filtering
	arm64: dts: qcom: msm8916: fix MMC controller aliases
	drm/amdgpu: Fix a NULL pointer dereference in amdgpu_connector_lcd_native_mode()
	drm/radeon/radeon_kms: Fix a NULL pointer dereference in radeon_driver_open_kms()
	serial: amba-pl011: do not request memory region twice
	floppy: Fix hang in watchdog when disk is ejected
	media: dib8000: Fix a memleak in dib8000_init()
	media: saa7146: mxb: Fix a NULL pointer dereference in mxb_attach()
	media: si2157: Fix "warm" tuner state detection
	media: msi001: fix possible null-ptr-deref in msi001_probe()
	usb: ftdi-elan: fix memory leak on device disconnect
	pcmcia: rsrc_nonstatic: Fix a NULL pointer dereference in __nonstatic_find_io_region()
	pcmcia: rsrc_nonstatic: Fix a NULL pointer dereference in nonstatic_find_mem_region()
	ppp: ensure minimum packet size in ppp_write()
	fsl/fman: Check for null pointer after calling devm_ioremap
	spi: spi-meson-spifc: Add missing pm_runtime_disable() in meson_spifc_probe
	can: softing: softing_startstop(): fix set but not used variable warning
	can: xilinx_can: xcan_probe(): check for error irq
	pcmcia: fix setting of kthread task states
	net: mcs7830: handle usb read errors properly
	ext4: avoid trim error on fs with small groups
	ALSA: jack: Add missing rwsem around snd_ctl_remove() calls
	ALSA: PCM: Add missing rwsem around snd_ctl_remove() calls
	ALSA: hda: Add missing rwsem around snd_ctl_remove() calls
	RDMA/hns: Validate the pkey index
	powerpc/prom_init: Fix improper check of prom_getprop()
	ALSA: oss: fix compile error when OSS_DEBUG is enabled
	char/mwave: Adjust io port register size
	scsi: ufs: Fix race conditions related to driver data
	RDMA/core: Let ib_find_gid() continue search even after empty entry
	dmaengine: pxa/mmp: stop referencing config->slave_id
	ASoC: samsung: idma: Check of ioremap return value
	misc: lattice-ecp3-config: Fix task hung when firmware load failed
	mips: lantiq: add support for clk_set_parent()
	mips: bcm63xx: add support for clk_set_parent()
	RDMA/cxgb4: Set queue pair state when being queried
	Bluetooth: Fix debugfs entry leak in hci_register_dev()
	fs: dlm: filter user dlm messages for kernel locks
	ar5523: Fix null-ptr-deref with unexpected WDCMSG_TARGET_START reply
	usb: gadget: f_fs: Use stream_open() for endpoint files
	HID: apple: Do not reset quirks when the Fn key is not found
	media: b2c2: Add missing check in flexcop_pci_isr:
	gpiolib: acpi: Do not set the IRQ type if the IRQ is already in use
	HSI: core: Fix return freed object in hsi_new_client
	mwifiex: Fix skb_over_panic in mwifiex_usb_recv()
	floppy: Add max size check for user space request
	media: saa7146: hexium_orion: Fix a NULL pointer dereference in hexium_attach()
	media: m920x: don't use stack on USB reads
	iwlwifi: mvm: synchronize with FW after multicast commands
	ath10k: Fix tx hanging
	net: bonding: debug: avoid printing debug logs when bond is not notifying peers
	media: igorplugusb: receiver overflow should be reported
	media: saa7146: hexium_gemini: Fix a NULL pointer dereference in hexium_attach()
	usb: hub: Add delay for SuperSpeed hub resume to let links transit to U0
	ath9k: Fix out-of-bound memcpy in ath9k_hif_usb_rx_stream
	um: registers: Rename function names to avoid conflicts and build problems
	jffs2: GC deadlock reading a page that is used in jffs2_write_begin()
	ACPICA: Utilities: Avoid deleting the same object twice in a row
	ACPICA: Executer: Fix the REFCLASS_REFOF case in acpi_ex_opcode_1A_0T_1R()
	btrfs: remove BUG_ON() in find_parent_nodes()
	btrfs: remove BUG_ON(!eie) in find_parent_nodes
	net: mdio: Demote probed message to debug print
	dm btree: add a defensive bounds check to insert_at()
	dm space map common: add bounds check to sm_ll_lookup_bitmap()
	serial: pl010: Drop CR register reset on set_termios
	serial: core: Keep mctrl register state and cached copy in sync
	parisc: Avoid calling faulthandler_disabled() twice
	powerpc/6xx: add missing of_node_put
	powerpc/powernv: add missing of_node_put
	powerpc/cell: add missing of_node_put
	powerpc/btext: add missing of_node_put
	i2c: i801: Don't silently correct invalid transfer size
	powerpc/smp: Move setup_profiling_timer() under CONFIG_PROFILING
	i2c: mpc: Correct I2C reset procedure
	w1: Misuse of get_user()/put_user() reported by sparse
	ALSA: seq: Set upper limit of processed events
	i2c: designware-pci: Fix to change data types of hcnt and lcnt parameters
	MIPS: Octeon: Fix build errors using clang
	scsi: sr: Don't use GFP_DMA
	ASoC: mediatek: mt8173: fix device_node leak
	power: bq25890: Enable continuous conversion for ADC at charging
	ubifs: Error path in ubifs_remount_rw() seems to wrongly free write buffers
	iwlwifi: mvm: Increase the scan timeout guard to 30 seconds
	ext4: set csum seed in tmp inode while migrating to extents
	ext4: Fix BUG_ON in ext4_bread when write quota data
	ext4: don't use the orphan list when migrating an inode
	fuse: fix bad inode
	fuse: fix live lock in fuse_iget()
	drm/radeon: fix error handling in radeon_driver_open_kms
	RDMA/hns: Modify the mapping attribute of doorbell to device
	RDMA/rxe: Fix a typo in opcode name
	powerpc/fsl/dts: Enable WA for erratum A-009885 on fman3l MDIO buses
	net/fsl: xgmac_mdio: Fix incorrect iounmap when removing module
	parisc: pdc_stable: Fix memory leak in pdcs_register_pathentries
	af_unix: annote lockless accesses to unix_tot_inflight & gc_in_progress
	net: axienet: Wait for PhyRstCmplt after core reset
	net: axienet: fix number of TX ring slots for available check
	netns: add schedule point in ops_exit_list()
	libcxgb: Don't accidentally set RTO_ONLINK in cxgb_find_route()
	dmaengine: at_xdmac: Don't start transactions at tx_submit level
	dmaengine: at_xdmac: Print debug message after realeasing the lock
	dmaengine: at_xdmac: Fix lld view setting
	dmaengine: at_xdmac: Fix at_xdmac_lld struct definition
	net_sched: restore "mpu xxx" handling
	bcmgenet: add WOL IRQ check
	scripts/dtc: dtx_diff: remove broken example from help text
	lib82596: Fix IRQ check in sni_82596_probe
	Revert "gup: document and work around "COW can break either way" issue"
	gup: document and work around "COW can break either way" issue
	drm/ttm/nouveau: don't call tt destroy callback on alloc failure.
	gianfar: simplify FCS handling and fix memory leak
	gianfar: fix jumbo packets+napi+rx overrun crash
	cipso,calipso: resolve a number of problems with the DOI refcounts
	rbtree: cache leftmost node internally
	lib/timerqueue: Rely on rbtree semantics for next timer
	mm: add follow_pte_pmd()
	KVM: do not assume PTE is writable after follow_pfn
	KVM: Use kvm_pfn_t for local PFN variable in hva_to_pfn_remapped()
	KVM: do not allow mapping valid but non-reference-counted pages
	Linux 4.9.298

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: Ifcea82a702a0906d9090c89785363c2d5423f652
2022-01-31 17:10:19 +03:00

839 lines
20 KiB
C

/*
* User-space I/O driver support for HID subsystem
* Copyright (c) 2012 David Herrmann
*/
/*
* 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/atomic.h>
#include <linux/compat.h>
#include <linux/cred.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/hid.h>
#include <linux/input.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/uhid.h>
#include <linux/wait.h>
#include <linux/uaccess.h>
#include <linux/eventpoll.h>
#define UHID_NAME "uhid"
#define UHID_BUFSIZE 32
static DEFINE_MUTEX(uhid_open_mutex);
struct uhid_device {
struct mutex devlock;
/* This flag tracks whether the HID device is usable for commands from
* userspace. The flag is already set before hid_add_device(), which
* runs in workqueue context, to allow hid_add_device() to communicate
* with userspace.
* However, if hid_add_device() fails, the flag is cleared without
* holding devlock.
* We guarantee that if @running changes from true to false while you're
* holding @devlock, it's still fine to access @hid.
*/
bool running;
__u8 *rd_data;
uint rd_size;
/* When this is NULL, userspace may use UHID_CREATE/UHID_CREATE2. */
struct hid_device *hid;
struct uhid_event input_buf;
wait_queue_head_t waitq;
spinlock_t qlock;
__u8 head;
__u8 tail;
struct uhid_event *outq[UHID_BUFSIZE];
/* blocking GET_REPORT support; state changes protected by qlock */
struct mutex report_lock;
wait_queue_head_t report_wait;
bool report_running;
u32 report_id;
u32 report_type;
struct uhid_event report_buf;
struct work_struct worker;
};
static struct miscdevice uhid_misc;
static void uhid_device_add_worker(struct work_struct *work)
{
struct uhid_device *uhid = container_of(work, struct uhid_device, worker);
int ret;
ret = hid_add_device(uhid->hid);
if (ret) {
hid_err(uhid->hid, "Cannot register HID device: error %d\n", ret);
/* We used to call hid_destroy_device() here, but that's really
* messy to get right because we have to coordinate with
* concurrent writes from userspace that might be in the middle
* of using uhid->hid.
* Just leave uhid->hid as-is for now, and clean it up when
* userspace tries to close or reinitialize the uhid instance.
*
* However, we do have to clear the ->running flag and do a
* wakeup to make sure userspace knows that the device is gone.
*/
uhid->running = false;
wake_up_interruptible(&uhid->report_wait);
}
}
static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
{
__u8 newhead;
newhead = (uhid->head + 1) % UHID_BUFSIZE;
if (newhead != uhid->tail) {
uhid->outq[uhid->head] = ev;
uhid->head = newhead;
wake_up_interruptible(&uhid->waitq);
} else {
hid_warn(uhid->hid, "Output queue is full\n");
kfree(ev);
}
}
static int uhid_queue_event(struct uhid_device *uhid, __u32 event)
{
unsigned long flags;
struct uhid_event *ev;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = event;
spin_lock_irqsave(&uhid->qlock, flags);
uhid_queue(uhid, ev);
spin_unlock_irqrestore(&uhid->qlock, flags);
return 0;
}
static int uhid_hid_start(struct hid_device *hid)
{
struct uhid_device *uhid = hid->driver_data;
struct uhid_event *ev;
unsigned long flags;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = UHID_START;
if (hid->report_enum[HID_FEATURE_REPORT].numbered)
ev->u.start.dev_flags |= UHID_DEV_NUMBERED_FEATURE_REPORTS;
if (hid->report_enum[HID_OUTPUT_REPORT].numbered)
ev->u.start.dev_flags |= UHID_DEV_NUMBERED_OUTPUT_REPORTS;
if (hid->report_enum[HID_INPUT_REPORT].numbered)
ev->u.start.dev_flags |= UHID_DEV_NUMBERED_INPUT_REPORTS;
spin_lock_irqsave(&uhid->qlock, flags);
uhid_queue(uhid, ev);
spin_unlock_irqrestore(&uhid->qlock, flags);
return 0;
}
static void uhid_hid_stop(struct hid_device *hid)
{
struct uhid_device *uhid = hid->driver_data;
hid->claimed = 0;
uhid_queue_event(uhid, UHID_STOP);
}
static int uhid_hid_open(struct hid_device *hid)
{
struct uhid_device *uhid = hid->driver_data;
int retval = 0;
mutex_lock(&uhid_open_mutex);
if (!hid->open++) {
retval = uhid_queue_event(uhid, UHID_OPEN);
if (retval)
hid->open--;
}
mutex_unlock(&uhid_open_mutex);
return retval;
}
static void uhid_hid_close(struct hid_device *hid)
{
struct uhid_device *uhid = hid->driver_data;
mutex_lock(&uhid_open_mutex);
if (!--hid->open)
uhid_queue_event(uhid, UHID_CLOSE);
mutex_unlock(&uhid_open_mutex);
}
static int uhid_hid_parse(struct hid_device *hid)
{
struct uhid_device *uhid = hid->driver_data;
return hid_parse_report(hid, uhid->rd_data, uhid->rd_size);
}
/* must be called with report_lock held */
static int __uhid_report_queue_and_wait(struct uhid_device *uhid,
struct uhid_event *ev,
__u32 *report_id)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&uhid->qlock, flags);
*report_id = ++uhid->report_id;
uhid->report_type = ev->type + 1;
uhid->report_running = true;
uhid_queue(uhid, ev);
spin_unlock_irqrestore(&uhid->qlock, flags);
ret = wait_event_interruptible_timeout(uhid->report_wait,
!uhid->report_running || !uhid->running,
5 * HZ);
if (!ret || !uhid->running || uhid->report_running)
ret = -EIO;
else if (ret < 0)
ret = -ERESTARTSYS;
else
ret = 0;
uhid->report_running = false;
return ret;
}
static void uhid_report_wake_up(struct uhid_device *uhid, u32 id,
const struct uhid_event *ev)
{
unsigned long flags;
spin_lock_irqsave(&uhid->qlock, flags);
/* id for old report; drop it silently */
if (uhid->report_type != ev->type || uhid->report_id != id)
goto unlock;
if (!uhid->report_running)
goto unlock;
memcpy(&uhid->report_buf, ev, sizeof(*ev));
uhid->report_running = false;
wake_up_interruptible(&uhid->report_wait);
unlock:
spin_unlock_irqrestore(&uhid->qlock, flags);
}
static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum,
u8 *buf, size_t count, u8 rtype)
{
struct uhid_device *uhid = hid->driver_data;
struct uhid_get_report_reply_req *req;
struct uhid_event *ev;
int ret;
if (!uhid->running)
return -EIO;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = UHID_GET_REPORT;
ev->u.get_report.rnum = rnum;
ev->u.get_report.rtype = rtype;
ret = mutex_lock_interruptible(&uhid->report_lock);
if (ret) {
kfree(ev);
return ret;
}
/* this _always_ takes ownership of @ev */
ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.get_report.id);
if (ret)
goto unlock;
req = &uhid->report_buf.u.get_report_reply;
if (req->err) {
ret = -EIO;
} else {
ret = min3(count, (size_t)req->size, (size_t)UHID_DATA_MAX);
memcpy(buf, req->data, ret);
}
unlock:
mutex_unlock(&uhid->report_lock);
return ret;
}
static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum,
const u8 *buf, size_t count, u8 rtype)
{
struct uhid_device *uhid = hid->driver_data;
struct uhid_event *ev;
int ret;
if (!uhid->running || count > UHID_DATA_MAX)
return -EIO;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = UHID_SET_REPORT;
ev->u.set_report.rnum = rnum;
ev->u.set_report.rtype = rtype;
ev->u.set_report.size = count;
memcpy(ev->u.set_report.data, buf, count);
ret = mutex_lock_interruptible(&uhid->report_lock);
if (ret) {
kfree(ev);
return ret;
}
/* this _always_ takes ownership of @ev */
ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.set_report.id);
if (ret)
goto unlock;
if (uhid->report_buf.u.set_report_reply.err)
ret = -EIO;
else
ret = count;
unlock:
mutex_unlock(&uhid->report_lock);
return ret;
}
static int uhid_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
__u8 *buf, size_t len, unsigned char rtype,
int reqtype)
{
u8 u_rtype;
switch (rtype) {
case HID_FEATURE_REPORT:
u_rtype = UHID_FEATURE_REPORT;
break;
case HID_OUTPUT_REPORT:
u_rtype = UHID_OUTPUT_REPORT;
break;
case HID_INPUT_REPORT:
u_rtype = UHID_INPUT_REPORT;
break;
default:
return -EINVAL;
}
switch (reqtype) {
case HID_REQ_GET_REPORT:
return uhid_hid_get_report(hid, reportnum, buf, len, u_rtype);
case HID_REQ_SET_REPORT:
return uhid_hid_set_report(hid, reportnum, buf, len, u_rtype);
default:
return -EIO;
}
}
static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,
unsigned char report_type)
{
struct uhid_device *uhid = hid->driver_data;
__u8 rtype;
unsigned long flags;
struct uhid_event *ev;
switch (report_type) {
case HID_FEATURE_REPORT:
rtype = UHID_FEATURE_REPORT;
break;
case HID_OUTPUT_REPORT:
rtype = UHID_OUTPUT_REPORT;
break;
default:
return -EINVAL;
}
if (count < 1 || count > UHID_DATA_MAX)
return -EINVAL;
ev = kzalloc(sizeof(*ev), GFP_KERNEL);
if (!ev)
return -ENOMEM;
ev->type = UHID_OUTPUT;
ev->u.output.size = count;
ev->u.output.rtype = rtype;
memcpy(ev->u.output.data, buf, count);
spin_lock_irqsave(&uhid->qlock, flags);
uhid_queue(uhid, ev);
spin_unlock_irqrestore(&uhid->qlock, flags);
return count;
}
static int uhid_hid_output_report(struct hid_device *hid, __u8 *buf,
size_t count)
{
return uhid_hid_output_raw(hid, buf, count, HID_OUTPUT_REPORT);
}
struct hid_ll_driver uhid_hid_driver = {
.start = uhid_hid_start,
.stop = uhid_hid_stop,
.open = uhid_hid_open,
.close = uhid_hid_close,
.parse = uhid_hid_parse,
.raw_request = uhid_hid_raw_request,
.output_report = uhid_hid_output_report,
};
EXPORT_SYMBOL_GPL(uhid_hid_driver);
#ifdef CONFIG_COMPAT
/* Apparently we haven't stepped on these rakes enough times yet. */
struct uhid_create_req_compat {
__u8 name[128];
__u8 phys[64];
__u8 uniq[64];
compat_uptr_t rd_data;
__u16 rd_size;
__u16 bus;
__u32 vendor;
__u32 product;
__u32 version;
__u32 country;
} __attribute__((__packed__));
static int uhid_event_from_user(const char __user *buffer, size_t len,
struct uhid_event *event)
{
if (in_compat_syscall()) {
u32 type;
if (get_user(type, buffer))
return -EFAULT;
if (type == UHID_CREATE) {
/*
* This is our messed up request with compat pointer.
* It is largish (more than 256 bytes) so we better
* allocate it from the heap.
*/
struct uhid_create_req_compat *compat;
compat = kzalloc(sizeof(*compat), GFP_KERNEL);
if (!compat)
return -ENOMEM;
buffer += sizeof(type);
len -= sizeof(type);
if (copy_from_user(compat, buffer,
min(len, sizeof(*compat)))) {
kfree(compat);
return -EFAULT;
}
/* Shuffle the data over to proper structure */
event->type = type;
memcpy(event->u.create.name, compat->name,
sizeof(compat->name));
memcpy(event->u.create.phys, compat->phys,
sizeof(compat->phys));
memcpy(event->u.create.uniq, compat->uniq,
sizeof(compat->uniq));
event->u.create.rd_data = compat_ptr(compat->rd_data);
event->u.create.rd_size = compat->rd_size;
event->u.create.bus = compat->bus;
event->u.create.vendor = compat->vendor;
event->u.create.product = compat->product;
event->u.create.version = compat->version;
event->u.create.country = compat->country;
kfree(compat);
return 0;
}
/* All others can be copied directly */
}
if (copy_from_user(event, buffer, min(len, sizeof(*event))))
return -EFAULT;
return 0;
}
#else
static int uhid_event_from_user(const char __user *buffer, size_t len,
struct uhid_event *event)
{
if (copy_from_user(event, buffer, min(len, sizeof(*event))))
return -EFAULT;
return 0;
}
#endif
static int uhid_dev_create2(struct uhid_device *uhid,
const struct uhid_event *ev)
{
struct hid_device *hid;
size_t rd_size, len;
void *rd_data;
int ret;
if (uhid->hid)
return -EALREADY;
rd_size = ev->u.create2.rd_size;
if (rd_size <= 0 || rd_size > HID_MAX_DESCRIPTOR_SIZE)
return -EINVAL;
rd_data = kmemdup(ev->u.create2.rd_data, rd_size, GFP_KERNEL);
if (!rd_data)
return -ENOMEM;
uhid->rd_size = rd_size;
uhid->rd_data = rd_data;
hid = hid_allocate_device();
if (IS_ERR(hid)) {
ret = PTR_ERR(hid);
goto err_free;
}
len = min(sizeof(hid->name), sizeof(ev->u.create2.name)) - 1;
strncpy(hid->name, ev->u.create2.name, len);
len = min(sizeof(hid->phys), sizeof(ev->u.create2.phys)) - 1;
strncpy(hid->phys, ev->u.create2.phys, len);
len = min(sizeof(hid->uniq), sizeof(ev->u.create2.uniq)) - 1;
strncpy(hid->uniq, ev->u.create2.uniq, len);
hid->ll_driver = &uhid_hid_driver;
hid->bus = ev->u.create2.bus;
hid->vendor = ev->u.create2.vendor;
hid->product = ev->u.create2.product;
hid->version = ev->u.create2.version;
hid->country = ev->u.create2.country;
hid->driver_data = uhid;
hid->dev.parent = uhid_misc.this_device;
uhid->hid = hid;
uhid->running = true;
/* Adding of a HID device is done through a worker, to allow HID drivers
* which use feature requests during .probe to work, without they would
* be blocked on devlock, which is held by uhid_char_write.
*/
schedule_work(&uhid->worker);
return 0;
err_free:
kfree(uhid->rd_data);
uhid->rd_data = NULL;
uhid->rd_size = 0;
return ret;
}
static int uhid_dev_create(struct uhid_device *uhid,
struct uhid_event *ev)
{
struct uhid_create_req orig;
orig = ev->u.create;
if (orig.rd_size <= 0 || orig.rd_size > HID_MAX_DESCRIPTOR_SIZE)
return -EINVAL;
if (copy_from_user(&ev->u.create2.rd_data, orig.rd_data, orig.rd_size))
return -EFAULT;
memcpy(ev->u.create2.name, orig.name, sizeof(orig.name));
memcpy(ev->u.create2.phys, orig.phys, sizeof(orig.phys));
memcpy(ev->u.create2.uniq, orig.uniq, sizeof(orig.uniq));
ev->u.create2.rd_size = orig.rd_size;
ev->u.create2.bus = orig.bus;
ev->u.create2.vendor = orig.vendor;
ev->u.create2.product = orig.product;
ev->u.create2.version = orig.version;
ev->u.create2.country = orig.country;
return uhid_dev_create2(uhid, ev);
}
static int uhid_dev_destroy(struct uhid_device *uhid)
{
if (!uhid->hid)
return -EINVAL;
uhid->running = false;
wake_up_interruptible(&uhid->report_wait);
cancel_work_sync(&uhid->worker);
hid_destroy_device(uhid->hid);
uhid->hid = NULL;
kfree(uhid->rd_data);
return 0;
}
static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
{
if (!uhid->running)
return -EINVAL;
hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data,
min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0);
return 0;
}
static int uhid_dev_input2(struct uhid_device *uhid, struct uhid_event *ev)
{
if (!uhid->running)
return -EINVAL;
hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data,
min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0);
return 0;
}
static int uhid_dev_get_report_reply(struct uhid_device *uhid,
struct uhid_event *ev)
{
if (!uhid->running)
return -EINVAL;
uhid_report_wake_up(uhid, ev->u.get_report_reply.id, ev);
return 0;
}
static int uhid_dev_set_report_reply(struct uhid_device *uhid,
struct uhid_event *ev)
{
if (!uhid->running)
return -EINVAL;
uhid_report_wake_up(uhid, ev->u.set_report_reply.id, ev);
return 0;
}
static int uhid_char_open(struct inode *inode, struct file *file)
{
struct uhid_device *uhid;
uhid = kzalloc(sizeof(*uhid), GFP_KERNEL);
if (!uhid)
return -ENOMEM;
mutex_init(&uhid->devlock);
mutex_init(&uhid->report_lock);
spin_lock_init(&uhid->qlock);
init_waitqueue_head(&uhid->waitq);
init_waitqueue_head(&uhid->report_wait);
uhid->running = false;
INIT_WORK(&uhid->worker, uhid_device_add_worker);
file->private_data = uhid;
nonseekable_open(inode, file);
return 0;
}
static int uhid_char_release(struct inode *inode, struct file *file)
{
struct uhid_device *uhid = file->private_data;
unsigned int i;
uhid_dev_destroy(uhid);
for (i = 0; i < UHID_BUFSIZE; ++i)
kfree(uhid->outq[i]);
kfree(uhid);
return 0;
}
static ssize_t uhid_char_read(struct file *file, char __user *buffer,
size_t count, loff_t *ppos)
{
struct uhid_device *uhid = file->private_data;
int ret;
unsigned long flags;
size_t len;
/* they need at least the "type" member of uhid_event */
if (count < sizeof(__u32))
return -EINVAL;
try_again:
if (file->f_flags & O_NONBLOCK) {
if (uhid->head == uhid->tail)
return -EAGAIN;
} else {
ret = wait_event_interruptible(uhid->waitq,
uhid->head != uhid->tail);
if (ret)
return ret;
}
ret = mutex_lock_interruptible(&uhid->devlock);
if (ret)
return ret;
if (uhid->head == uhid->tail) {
mutex_unlock(&uhid->devlock);
goto try_again;
} else {
len = min(count, sizeof(**uhid->outq));
if (copy_to_user(buffer, uhid->outq[uhid->tail], len)) {
ret = -EFAULT;
} else {
kfree(uhid->outq[uhid->tail]);
uhid->outq[uhid->tail] = NULL;
spin_lock_irqsave(&uhid->qlock, flags);
uhid->tail = (uhid->tail + 1) % UHID_BUFSIZE;
spin_unlock_irqrestore(&uhid->qlock, flags);
}
}
mutex_unlock(&uhid->devlock);
return ret ? ret : len;
}
static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
struct uhid_device *uhid = file->private_data;
int ret;
size_t len;
/* we need at least the "type" member of uhid_event */
if (count < sizeof(__u32))
return -EINVAL;
ret = mutex_lock_interruptible(&uhid->devlock);
if (ret)
return ret;
memset(&uhid->input_buf, 0, sizeof(uhid->input_buf));
len = min(count, sizeof(uhid->input_buf));
ret = uhid_event_from_user(buffer, len, &uhid->input_buf);
if (ret)
goto unlock;
switch (uhid->input_buf.type) {
case UHID_CREATE:
/*
* 'struct uhid_create_req' contains a __user pointer which is
* copied from, so it's unsafe to allow this with elevated
* privileges (e.g. from a setuid binary) or via kernel_write().
*/
if (file->f_cred != current_cred() || uaccess_kernel()) {
pr_err_once("UHID_CREATE from different security context by process %d (%s), this is not allowed.\n",
task_tgid_vnr(current), current->comm);
ret = -EACCES;
goto unlock;
}
ret = uhid_dev_create(uhid, &uhid->input_buf);
break;
case UHID_CREATE2:
ret = uhid_dev_create2(uhid, &uhid->input_buf);
break;
case UHID_DESTROY:
ret = uhid_dev_destroy(uhid);
break;
case UHID_INPUT:
ret = uhid_dev_input(uhid, &uhid->input_buf);
break;
case UHID_INPUT2:
ret = uhid_dev_input2(uhid, &uhid->input_buf);
break;
case UHID_GET_REPORT_REPLY:
ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf);
break;
case UHID_SET_REPORT_REPLY:
ret = uhid_dev_set_report_reply(uhid, &uhid->input_buf);
break;
default:
ret = -EOPNOTSUPP;
}
unlock:
mutex_unlock(&uhid->devlock);
/* return "count" not "len" to not confuse the caller */
return ret ? ret : count;
}
static unsigned int uhid_char_poll(struct file *file, poll_table *wait)
{
struct uhid_device *uhid = file->private_data;
unsigned int mask = POLLOUT | POLLWRNORM; /* uhid is always writable */
poll_wait(file, &uhid->waitq, wait);
if (uhid->head != uhid->tail)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static const struct file_operations uhid_fops = {
.owner = THIS_MODULE,
.open = uhid_char_open,
.release = uhid_char_release,
.read = uhid_char_read,
.write = uhid_char_write,
.poll = uhid_char_poll,
.llseek = no_llseek,
};
static struct miscdevice uhid_misc = {
.fops = &uhid_fops,
.minor = UHID_MINOR,
.name = UHID_NAME,
};
module_misc_device(uhid_misc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem");
MODULE_ALIAS_MISCDEV(UHID_MINOR);
MODULE_ALIAS("devname:" UHID_NAME);