diff options
| -rw-r--r-- | Documentation/hid/hid-transport.txt | 3 | ||||
| -rw-r--r-- | drivers/hid/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/hid/Makefile | 1 | ||||
| -rw-r--r-- | drivers/hid/hid-core.c | 51 | ||||
| -rw-r--r-- | drivers/hid/hid-cp2112.c | 1073 | ||||
| -rw-r--r-- | drivers/hid/hid-hyperv.c | 10 | ||||
| -rw-r--r-- | drivers/hid/hid-ids.h | 1 | ||||
| -rw-r--r-- | drivers/hid/hid-input.c | 10 | ||||
| -rw-r--r-- | drivers/hid/hid-lg.c | 8 | ||||
| -rw-r--r-- | drivers/hid/hid-logitech-dj.c | 21 | ||||
| -rw-r--r-- | drivers/hid/hid-magicmouse.c | 4 | ||||
| -rw-r--r-- | drivers/hid/hid-sony.c | 64 | ||||
| -rw-r--r-- | drivers/hid/hid-thingm.c | 4 | ||||
| -rw-r--r-- | drivers/hid/hid-wacom.c | 26 | ||||
| -rw-r--r-- | drivers/hid/hid-wiimote-core.c | 4 | ||||
| -rw-r--r-- | drivers/hid/hidraw.c | 20 | ||||
| -rw-r--r-- | drivers/hid/i2c-hid/i2c-hid.c | 70 | ||||
| -rw-r--r-- | drivers/hid/uhid.c | 37 | ||||
| -rw-r--r-- | drivers/hid/usbhid/hid-core.c | 21 | ||||
| -rw-r--r-- | include/linux/hid.h | 37 | ||||
| -rw-r--r-- | net/bluetooth/hidp/core.c | 14 | 
21 files changed, 1288 insertions, 200 deletions
| diff --git a/Documentation/hid/hid-transport.txt b/Documentation/hid/hid-transport.txt index 9dbbceaef4f3..3dcba9fd4a3a 100644 --- a/Documentation/hid/hid-transport.txt +++ b/Documentation/hid/hid-transport.txt @@ -283,7 +283,8 @@ The available HID callbacks are:                         int reqtype)     Same as ->request() but provides the report as raw buffer. This request shall     be synchronous. A transport driver must not use ->wait() to complete such -   requests. +   requests. This request is mandatory and hid core will reject the device if +   it is missing.   - int (*output_report) (struct hid_device *hdev, __u8 *buf, size_t len)     Send raw output report via intr channel. Used by some HID device drivers diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 0c3de7ae6946..7af9d0b5dea1 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -175,6 +175,15 @@ config HID_PRODIKEYS  	  multimedia keyboard, but will lack support for the musical keyboard  	  and some additional multimedia keys. +config HID_CP2112 +	tristate "Silicon Labs CP2112 HID USB-to-SMBus Bridge support" +	depends on USB_HID && I2C && GPIOLIB +	---help--- +	Support for Silicon Labs CP2112 HID USB to SMBus Master Bridge. +	This is a HID device driver which registers as an i2c adapter +	and gpiochip to expose these functions of the CP2112. The +	customizable USB descriptor fields are exposed as sysfs attributes. +  config HID_CYPRESS  	tristate "Cypress mouse and barcode readers" if EXPERT  	depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 30e44318f87f..fc712dde02a4 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_HID_AUREAL)        += hid-aureal.o  obj-$(CONFIG_HID_BELKIN)	+= hid-belkin.o  obj-$(CONFIG_HID_CHERRY)	+= hid-cherry.o  obj-$(CONFIG_HID_CHICONY)	+= hid-chicony.o +obj-$(CONFIG_HID_CP2112)	+= hid-cp2112.o  obj-$(CONFIG_HID_CYPRESS)	+= hid-cypress.o  obj-$(CONFIG_HID_DRAGONRISE)	+= hid-dr.o  obj-$(CONFIG_HID_EMS_FF)	+= hid-emsff.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 026ab0fc06f7..ddb981db08bd 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1248,6 +1248,11 @@ void hid_output_report(struct hid_report *report, __u8 *data)  }  EXPORT_SYMBOL_GPL(hid_output_report); +static int hid_report_len(struct hid_report *report) +{ +	return ((report->size - 1) >> 3) + 1 + (report->id > 0) + 7; +} +  /*   * Allocator for buffer that is going to be passed to hid_output_report()   */ @@ -1258,7 +1263,7 @@ u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags)  	 * of implement() working on 8 byte chunks  	 */ -	int len = ((report->size - 1) >> 3) + 1 + (report->id > 0) + 7; +	int len = hid_report_len(report);  	return kmalloc(len, flags);  } @@ -1314,6 +1319,41 @@ static struct hid_report *hid_get_report(struct hid_report_enum *report_enum,  	return report;  } +/* + * Implement a generic .request() callback, using .raw_request() + * DO NOT USE in hid drivers directly, but through hid_hw_request instead. + */ +void __hid_request(struct hid_device *hid, struct hid_report *report, +		int reqtype) +{ +	char *buf; +	int ret; +	int len; + +	buf = hid_alloc_report_buf(report, GFP_KERNEL); +	if (!buf) +		return; + +	len = hid_report_len(report); + +	if (reqtype == HID_REQ_SET_REPORT) +		hid_output_report(report, buf); + +	ret = hid->ll_driver->raw_request(hid, report->id, buf, len, +					  report->type, reqtype); +	if (ret < 0) { +		dbg_hid("unable to complete request: %d\n", ret); +		goto out; +	} + +	if (reqtype == HID_REQ_GET_REPORT) +		hid_input_report(hid, report->type, buf, ret, 0); + +out: +	kfree(buf); +} +EXPORT_SYMBOL_GPL(__hid_request); +  int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size,  		int interrupt)  { @@ -1692,6 +1732,7 @@ static const struct hid_device_id hid_have_special_driver[] = {  	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },  	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },  	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) }, +	{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) },  	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) },  	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) },  	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) }, @@ -2429,6 +2470,14 @@ int hid_add_device(struct hid_device *hdev)  		return -ENODEV;  	/* +	 * Check for the mandatory transport channel. +	 */ +	 if (!hdev->ll_driver->raw_request) { +		hid_err(hdev, "transport driver missing .raw_request()\n"); +		return -EINVAL; +	 } + +	/*  	 * Read the device report descriptor once and use as template  	 * for the driver-specific modifications.  	 */ diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c new file mode 100644 index 000000000000..56be85a9a77c --- /dev/null +++ b/drivers/hid/hid-cp2112.c @@ -0,0 +1,1073 @@ +/* + * hid-cp2112.c - Silicon Labs HID USB to SMBus master bridge + * Copyright (c) 2013,2014 Uplogix, Inc. + * David Barksdale <dbarksdale@uplogix.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + */ + +/* + * The Silicon Labs CP2112 chip is a USB HID device which provides an + * SMBus controller for talking to slave devices and 8 GPIO pins. The + * host communicates with the CP2112 via raw HID reports. + * + * Data Sheet: + *   http://www.silabs.com/Support%20Documents/TechnicalDocs/CP2112.pdf + * Programming Interface Specification: + *   http://www.silabs.com/Support%20Documents/TechnicalDocs/AN495.pdf + */ + +#include <linux/gpio.h> +#include <linux/hid.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/nls.h> +#include <linux/usb/ch9.h> +#include "hid-ids.h" + +enum { +	CP2112_GPIO_CONFIG		= 0x02, +	CP2112_GPIO_GET			= 0x03, +	CP2112_GPIO_SET			= 0x04, +	CP2112_GET_VERSION_INFO		= 0x05, +	CP2112_SMBUS_CONFIG		= 0x06, +	CP2112_DATA_READ_REQUEST	= 0x10, +	CP2112_DATA_WRITE_READ_REQUEST	= 0x11, +	CP2112_DATA_READ_FORCE_SEND	= 0x12, +	CP2112_DATA_READ_RESPONSE	= 0x13, +	CP2112_DATA_WRITE_REQUEST	= 0x14, +	CP2112_TRANSFER_STATUS_REQUEST	= 0x15, +	CP2112_TRANSFER_STATUS_RESPONSE	= 0x16, +	CP2112_CANCEL_TRANSFER		= 0x17, +	CP2112_LOCK_BYTE		= 0x20, +	CP2112_USB_CONFIG		= 0x21, +	CP2112_MANUFACTURER_STRING	= 0x22, +	CP2112_PRODUCT_STRING		= 0x23, +	CP2112_SERIAL_STRING		= 0x24, +}; + +enum { +	STATUS0_IDLE		= 0x00, +	STATUS0_BUSY		= 0x01, +	STATUS0_COMPLETE	= 0x02, +	STATUS0_ERROR		= 0x03, +}; + +enum { +	STATUS1_TIMEOUT_NACK		= 0x00, +	STATUS1_TIMEOUT_BUS		= 0x01, +	STATUS1_ARBITRATION_LOST	= 0x02, +	STATUS1_READ_INCOMPLETE		= 0x03, +	STATUS1_WRITE_INCOMPLETE	= 0x04, +	STATUS1_SUCCESS			= 0x05, +}; + +struct cp2112_smbus_config_report { +	u8 report;		/* CP2112_SMBUS_CONFIG */ +	__be32 clock_speed;	/* Hz */ +	u8 device_address;	/* Stored in the upper 7 bits */ +	u8 auto_send_read;	/* 1 = enabled, 0 = disabled */ +	__be16 write_timeout;	/* ms, 0 = no timeout */ +	__be16 read_timeout;	/* ms, 0 = no timeout */ +	u8 scl_low_timeout;	/* 1 = enabled, 0 = disabled */ +	__be16 retry_time;	/* # of retries, 0 = no limit */ +} __packed; + +struct cp2112_usb_config_report { +	u8 report;	/* CP2112_USB_CONFIG */ +	__le16 vid;	/* Vendor ID */ +	__le16 pid;	/* Product ID */ +	u8 max_power;	/* Power requested in 2mA units */ +	u8 power_mode;	/* 0x00 = bus powered +			   0x01 = self powered & regulator off +			   0x02 = self powered & regulator on */ +	u8 release_major; +	u8 release_minor; +	u8 mask;	/* What fields to program */ +} __packed; + +struct cp2112_read_req_report { +	u8 report;	/* CP2112_DATA_READ_REQUEST */ +	u8 slave_address; +	__be16 length; +} __packed; + +struct cp2112_write_read_req_report { +	u8 report;	/* CP2112_DATA_WRITE_READ_REQUEST */ +	u8 slave_address; +	__be16 length; +	u8 target_address_length; +	u8 target_address[16]; +} __packed; + +struct cp2112_write_req_report { +	u8 report;	/* CP2112_DATA_WRITE_REQUEST */ +	u8 slave_address; +	u8 length; +	u8 data[61]; +} __packed; + +struct cp2112_force_read_report { +	u8 report;	/* CP2112_DATA_READ_FORCE_SEND */ +	__be16 length; +} __packed; + +struct cp2112_xfer_status_report { +	u8 report;	/* CP2112_TRANSFER_STATUS_RESPONSE */ +	u8 status0;	/* STATUS0_* */ +	u8 status1;	/* STATUS1_* */ +	__be16 retries; +	__be16 length; +} __packed; + +struct cp2112_string_report { +	u8 dummy;		/* force .string to be aligned */ +	u8 report;		/* CP2112_*_STRING */ +	u8 length;		/* length in bytes of everyting after .report */ +	u8 type;		/* USB_DT_STRING */ +	wchar_t string[30];	/* UTF16_LITTLE_ENDIAN string */ +} __packed; + +/* Number of times to request transfer status before giving up waiting for a +   transfer to complete. This may need to be changed if SMBUS clock, retries, +   or read/write/scl_low timeout settings are changed. */ +static const int XFER_STATUS_RETRIES = 10; + +/* Time in ms to wait for a CP2112_DATA_READ_RESPONSE or +   CP2112_TRANSFER_STATUS_RESPONSE. */ +static const int RESPONSE_TIMEOUT = 50; + +static const struct hid_device_id cp2112_devices[] = { +	{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) }, +	{ } +}; +MODULE_DEVICE_TABLE(hid, cp2112_devices); + +struct cp2112_device { +	struct i2c_adapter adap; +	struct hid_device *hdev; +	wait_queue_head_t wait; +	u8 read_data[61]; +	u8 read_length; +	int xfer_status; +	atomic_t read_avail; +	atomic_t xfer_avail; +	struct gpio_chip gc; +}; + +static int gpio_push_pull = 0xFF; +module_param(gpio_push_pull, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask"); + +static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ +	struct cp2112_device *dev = container_of(chip, struct cp2112_device, +						 gc); +	struct hid_device *hdev = dev->hdev; +	u8 buf[5]; +	int ret; + +	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, +				       sizeof(buf), HID_FEATURE_REPORT, +				       HID_REQ_GET_REPORT); +	if (ret != sizeof(buf)) { +		hid_err(hdev, "error requesting GPIO config: %d\n", ret); +		return ret; +	} + +	buf[1] &= ~(1 << offset); +	buf[2] = gpio_push_pull; + +	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, sizeof(buf), +				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT); +	if (ret < 0) { +		hid_err(hdev, "error setting GPIO config: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ +	struct cp2112_device *dev = container_of(chip, struct cp2112_device, +						 gc); +	struct hid_device *hdev = dev->hdev; +	u8 buf[3]; +	int ret; + +	buf[0] = CP2112_GPIO_SET; +	buf[1] = value ? 0xff : 0; +	buf[2] = 1 << offset; + +	ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf, sizeof(buf), +				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT); +	if (ret < 0) +		hid_err(hdev, "error setting GPIO values: %d\n", ret); +} + +static int cp2112_gpio_get(struct gpio_chip *chip, unsigned offset) +{ +	struct cp2112_device *dev = container_of(chip, struct cp2112_device, +						 gc); +	struct hid_device *hdev = dev->hdev; +	u8 buf[2]; +	int ret; + +	ret = hid_hw_raw_request(hdev, CP2112_GPIO_GET, buf, sizeof(buf), +				       HID_FEATURE_REPORT, HID_REQ_GET_REPORT); +	if (ret != sizeof(buf)) { +		hid_err(hdev, "error requesting GPIO values: %d\n", ret); +		return ret; +	} + +	return (buf[1] >> offset) & 1; +} + +static int cp2112_gpio_direction_output(struct gpio_chip *chip, +					unsigned offset, int value) +{ +	struct cp2112_device *dev = container_of(chip, struct cp2112_device, +						 gc); +	struct hid_device *hdev = dev->hdev; +	u8 buf[5]; +	int ret; + +	cp2112_gpio_set(chip, offset, value); + +	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, +				       sizeof(buf), HID_FEATURE_REPORT, +				       HID_REQ_GET_REPORT); +	if (ret != sizeof(buf)) { +		hid_err(hdev, "error requesting GPIO config: %d\n", ret); +		return ret; +	} + +	buf[1] |= 1 << offset; +	buf[2] = gpio_push_pull; + +	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, sizeof(buf), +				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT); +	if (ret < 0) { +		hid_err(hdev, "error setting GPIO config: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static int cp2112_hid_get(struct hid_device *hdev, unsigned char report_number, +			  u8 *data, size_t count, unsigned char report_type) +{ +	u8 *buf; +	int ret; + +	buf = kmalloc(count, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	ret = hid_hw_raw_request(hdev, report_number, buf, count, +				       report_type, HID_REQ_GET_REPORT); +	memcpy(data, buf, count); +	kfree(buf); +	return ret; +} + +static int cp2112_hid_output(struct hid_device *hdev, u8 *data, size_t count, +			     unsigned char report_type) +{ +	u8 *buf; +	int ret; + +	buf = kmemdup(data, count, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	if (report_type == HID_OUTPUT_REPORT) +		ret = hid_hw_output_report(hdev, buf, count); +	else +		ret = hid_hw_raw_request(hdev, buf[0], buf, count, report_type, +				HID_REQ_SET_REPORT); + +	kfree(buf); +	return ret; +} + +static int cp2112_wait(struct cp2112_device *dev, atomic_t *avail) +{ +	int ret = 0; + +	/* We have sent either a CP2112_TRANSFER_STATUS_REQUEST or a +	 * CP2112_DATA_READ_FORCE_SEND and we are waiting for the response to +	 * come in cp2112_raw_event or timeout. There will only be one of these +	 * in flight at any one time. The timeout is extremely large and is a +	 * last resort if the CP2112 has died. If we do timeout we don't expect +	 * to receive the response which would cause data races, it's not like +	 * we can do anything about it anyway. +	 */ +	ret = wait_event_interruptible_timeout(dev->wait, +		atomic_read(avail), msecs_to_jiffies(RESPONSE_TIMEOUT)); +	if (-ERESTARTSYS == ret) +		return ret; +	if (!ret) +		return -ETIMEDOUT; + +	atomic_set(avail, 0); +	return 0; +} + +static int cp2112_xfer_status(struct cp2112_device *dev) +{ +	struct hid_device *hdev = dev->hdev; +	u8 buf[2]; +	int ret; + +	buf[0] = CP2112_TRANSFER_STATUS_REQUEST; +	buf[1] = 0x01; +	atomic_set(&dev->xfer_avail, 0); + +	ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT); +	if (ret < 0) { +		hid_warn(hdev, "Error requesting status: %d\n", ret); +		return ret; +	} + +	ret = cp2112_wait(dev, &dev->xfer_avail); +	if (ret) +		return ret; + +	return dev->xfer_status; +} + +static int cp2112_read(struct cp2112_device *dev, u8 *data, size_t size) +{ +	struct hid_device *hdev = dev->hdev; +	struct cp2112_force_read_report report; +	int ret; + +	report.report = CP2112_DATA_READ_FORCE_SEND; +	report.length = cpu_to_be16(size); + +	atomic_set(&dev->read_avail, 0); + +	ret = cp2112_hid_output(hdev, &report.report, sizeof(report), +				HID_OUTPUT_REPORT); +	if (ret < 0) { +		hid_warn(hdev, "Error requesting data: %d\n", ret); +		return ret; +	} + +	ret = cp2112_wait(dev, &dev->read_avail); +	if (ret) +		return ret; + +	hid_dbg(hdev, "read %d of %zd bytes requested\n", +		dev->read_length, size); + +	if (size > dev->read_length) +		size = dev->read_length; + +	memcpy(data, dev->read_data, size); +	return dev->read_length; +} + +static int cp2112_read_req(void *buf, u8 slave_address, u16 length) +{ +	struct cp2112_read_req_report *report = buf; + +	if (length < 1 || length > 512) +		return -EINVAL; + +	report->report = CP2112_DATA_READ_REQUEST; +	report->slave_address = slave_address << 1; +	report->length = cpu_to_be16(length); +	return sizeof(*report); +} + +static int cp2112_write_read_req(void *buf, u8 slave_address, u16 length, +				 u8 command, u8 *data, u8 data_length) +{ +	struct cp2112_write_read_req_report *report = buf; + +	if (length < 1 || length > 512 +	    || data_length > sizeof(report->target_address) - 1) +		return -EINVAL; + +	report->report = CP2112_DATA_WRITE_READ_REQUEST; +	report->slave_address = slave_address << 1; +	report->length = cpu_to_be16(length); +	report->target_address_length = data_length + 1; +	report->target_address[0] = command; +	memcpy(&report->target_address[1], data, data_length); +	return data_length + 6; +} + +static int cp2112_write_req(void *buf, u8 slave_address, u8 command, u8 *data, +			    u8 data_length) +{ +	struct cp2112_write_req_report *report = buf; + +	if (data_length > sizeof(report->data) - 1) +		return -EINVAL; + +	report->report = CP2112_DATA_WRITE_REQUEST; +	report->slave_address = slave_address << 1; +	report->length = data_length + 1; +	report->data[0] = command; +	memcpy(&report->data[1], data, data_length); +	return data_length + 4; +} + +static int cp2112_xfer(struct i2c_adapter *adap, u16 addr, +		       unsigned short flags, char read_write, u8 command, +		       int size, union i2c_smbus_data *data) +{ +	struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data; +	struct hid_device *hdev = dev->hdev; +	u8 buf[64]; +	__be16 word; +	ssize_t count; +	size_t read_length = 0; +	unsigned int retries; +	int ret; + +	hid_dbg(hdev, "%s addr 0x%x flags 0x%x cmd 0x%x size %d\n", +		read_write == I2C_SMBUS_WRITE ? "write" : "read", +		addr, flags, command, size); + +	switch (size) { +	case I2C_SMBUS_BYTE: +		read_length = 1; + +		if (I2C_SMBUS_READ == read_write) +			count = cp2112_read_req(buf, addr, read_length); +		else +			count = cp2112_write_req(buf, addr, data->byte, NULL, +						 0); +		break; +	case I2C_SMBUS_BYTE_DATA: +		read_length = 1; + +		if (I2C_SMBUS_READ == read_write) +			count = cp2112_write_read_req(buf, addr, read_length, +						      command, NULL, 0); +		else +			count = cp2112_write_req(buf, addr, command, +						 &data->byte, 1); +		break; +	case I2C_SMBUS_WORD_DATA: +		read_length = 2; +		word = cpu_to_be16(data->word); + +		if (I2C_SMBUS_READ == read_write) +			count = cp2112_write_read_req(buf, addr, read_length, +						      command, NULL, 0); +		else +			count = cp2112_write_req(buf, addr, command, +						 (u8 *)&word, 2); +		break; +	case I2C_SMBUS_PROC_CALL: +		size = I2C_SMBUS_WORD_DATA; +		read_write = I2C_SMBUS_READ; +		read_length = 2; +		word = cpu_to_be16(data->word); + +		count = cp2112_write_read_req(buf, addr, read_length, command, +					      (u8 *)&word, 2); +		break; +	case I2C_SMBUS_I2C_BLOCK_DATA: +		size = I2C_SMBUS_BLOCK_DATA; +		/* fallthrough */ +	case I2C_SMBUS_BLOCK_DATA: +		if (I2C_SMBUS_READ == read_write) { +			count = cp2112_write_read_req(buf, addr, +						      I2C_SMBUS_BLOCK_MAX, +						      command, NULL, 0); +		} else { +			count = cp2112_write_req(buf, addr, command, +						 data->block, +						 data->block[0] + 1); +		} +		break; +	case I2C_SMBUS_BLOCK_PROC_CALL: +		size = I2C_SMBUS_BLOCK_DATA; +		read_write = I2C_SMBUS_READ; + +		count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX, +					      command, data->block, +					      data->block[0] + 1); +		break; +	default: +		hid_warn(hdev, "Unsupported transaction %d\n", size); +		return -EOPNOTSUPP; +	} + +	if (count < 0) +		return count; + +	ret = hid_hw_power(hdev, PM_HINT_FULLON); +	if (ret < 0) { +		hid_err(hdev, "power management error: %d\n", ret); +		return ret; +	} + +	ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT); +	if (ret < 0) { +		hid_warn(hdev, "Error starting transaction: %d\n", ret); +		goto power_normal; +	} + +	for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) { +		ret = cp2112_xfer_status(dev); +		if (-EBUSY == ret) +			continue; +		if (ret < 0) +			goto power_normal; +		break; +	} + +	if (XFER_STATUS_RETRIES <= retries) { +		hid_warn(hdev, "Transfer timed out, cancelling.\n"); +		buf[0] = CP2112_CANCEL_TRANSFER; +		buf[1] = 0x01; + +		ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT); +		if (ret < 0) +			hid_warn(hdev, "Error cancelling transaction: %d\n", +				 ret); + +		ret = -ETIMEDOUT; +		goto power_normal; +	} + +	if (I2C_SMBUS_WRITE == read_write) { +		ret = 0; +		goto power_normal; +	} + +	if (I2C_SMBUS_BLOCK_DATA == size) +		read_length = ret; + +	ret = cp2112_read(dev, buf, read_length); +	if (ret < 0) +		goto power_normal; +	if (ret != read_length) { +		hid_warn(hdev, "short read: %d < %zd\n", ret, read_length); +		ret = -EIO; +		goto power_normal; +	} + +	switch (size) { +	case I2C_SMBUS_BYTE: +	case I2C_SMBUS_BYTE_DATA: +		data->byte = buf[0]; +		break; +	case I2C_SMBUS_WORD_DATA: +		data->word = be16_to_cpup((__be16 *)buf); +		break; +	case I2C_SMBUS_BLOCK_DATA: +		if (read_length > I2C_SMBUS_BLOCK_MAX) { +			ret = -EPROTO; +			goto power_normal; +		} + +		memcpy(data->block, buf, read_length); +		break; +	} + +	ret = 0; +power_normal: +	hid_hw_power(hdev, PM_HINT_NORMAL); +	hid_dbg(hdev, "transfer finished: %d\n", ret); +	return ret; +} + +static u32 cp2112_functionality(struct i2c_adapter *adap) +{ +	return I2C_FUNC_SMBUS_BYTE | +		I2C_FUNC_SMBUS_BYTE_DATA | +		I2C_FUNC_SMBUS_WORD_DATA | +		I2C_FUNC_SMBUS_BLOCK_DATA | +		I2C_FUNC_SMBUS_I2C_BLOCK | +		I2C_FUNC_SMBUS_PROC_CALL | +		I2C_FUNC_SMBUS_BLOCK_PROC_CALL; +} + +static const struct i2c_algorithm smbus_algorithm = { +	.smbus_xfer	= cp2112_xfer, +	.functionality	= cp2112_functionality, +}; + +static int cp2112_get_usb_config(struct hid_device *hdev, +				 struct cp2112_usb_config_report *cfg) +{ +	int ret; + +	ret = cp2112_hid_get(hdev, CP2112_USB_CONFIG, (u8 *)cfg, sizeof(*cfg), +			     HID_FEATURE_REPORT); +	if (ret != sizeof(*cfg)) { +		hid_err(hdev, "error reading usb config: %d\n", ret); +		if (ret < 0) +			return ret; +		return -EIO; +	} + +	return 0; +} + +static int cp2112_set_usb_config(struct hid_device *hdev, +				 struct cp2112_usb_config_report *cfg) +{ +	int ret; + +	BUG_ON(cfg->report != CP2112_USB_CONFIG); + +	ret = cp2112_hid_output(hdev, (u8 *)cfg, sizeof(*cfg), +				HID_FEATURE_REPORT); +	if (ret != sizeof(*cfg)) { +		hid_err(hdev, "error writing usb config: %d\n", ret); +		if (ret < 0) +			return ret; +		return -EIO; +	} + +	return 0; +} + +static void chmod_sysfs_attrs(struct hid_device *hdev); + +#define CP2112_CONFIG_ATTR(name, store, format, ...) \ +static ssize_t name##_store(struct device *kdev, \ +			    struct device_attribute *attr, const char *buf, \ +			    size_t count) \ +{ \ +	struct hid_device *hdev = container_of(kdev, struct hid_device, dev); \ +	struct cp2112_usb_config_report cfg; \ +	int ret = cp2112_get_usb_config(hdev, &cfg); \ +	if (ret) \ +		return ret; \ +	store; \ +	ret = cp2112_set_usb_config(hdev, &cfg); \ +	if (ret) \ +		return ret; \ +	chmod_sysfs_attrs(hdev); \ +	return count; \ +} \ +static ssize_t name##_show(struct device *kdev, \ +			   struct device_attribute *attr, char *buf) \ +{ \ +	struct hid_device *hdev = container_of(kdev, struct hid_device, dev); \ +	struct cp2112_usb_config_report cfg; \ +	int ret = cp2112_get_usb_config(hdev, &cfg); \ +	if (ret) \ +		return ret; \ +	return scnprintf(buf, PAGE_SIZE, format, ##__VA_ARGS__); \ +} \ +static DEVICE_ATTR_RW(name); + +CP2112_CONFIG_ATTR(vendor_id, ({ +	u16 vid; + +	if (sscanf(buf, "%hi", &vid) != 1) +		return -EINVAL; + +	cfg.vid = cpu_to_le16(vid); +	cfg.mask = 0x01; +}), "0x%04x\n", le16_to_cpu(cfg.vid)); + +CP2112_CONFIG_ATTR(product_id, ({ +	u16 pid; + +	if (sscanf(buf, "%hi", &pid) != 1) +		return -EINVAL; + +	cfg.pid = cpu_to_le16(pid); +	cfg.mask = 0x02; +}), "0x%04x\n", le16_to_cpu(cfg.pid)); + +CP2112_CONFIG_ATTR(max_power, ({ +	int mA; + +	if (sscanf(buf, "%i", &mA) != 1) +		return -EINVAL; + +	cfg.max_power = (mA + 1) / 2; +	cfg.mask = 0x04; +}), "%u mA\n", cfg.max_power * 2); + +CP2112_CONFIG_ATTR(power_mode, ({ +	if (sscanf(buf, "%hhi", &cfg.power_mode) != 1) +		return -EINVAL; + +	cfg.mask = 0x08; +}), "%u\n", cfg.power_mode); + +CP2112_CONFIG_ATTR(release_version, ({ +	if (sscanf(buf, "%hhi.%hhi", &cfg.release_major, &cfg.release_minor) +	    != 2) +		return -EINVAL; + +	cfg.mask = 0x10; +}), "%u.%u\n", cfg.release_major, cfg.release_minor); + +#undef CP2112_CONFIG_ATTR + +struct cp2112_pstring_attribute { +	struct device_attribute attr; +	unsigned char report; +}; + +static ssize_t pstr_store(struct device *kdev, +			  struct device_attribute *kattr, const char *buf, +			  size_t count) +{ +	struct hid_device *hdev = container_of(kdev, struct hid_device, dev); +	struct cp2112_pstring_attribute *attr = +		container_of(kattr, struct cp2112_pstring_attribute, attr); +	struct cp2112_string_report report; +	int ret; + +	memset(&report, 0, sizeof(report)); + +	ret = utf8s_to_utf16s(buf, count, UTF16_LITTLE_ENDIAN, +			      report.string, ARRAY_SIZE(report.string)); +	report.report = attr->report; +	report.length = ret * sizeof(report.string[0]) + 2; +	report.type = USB_DT_STRING; + +	ret = cp2112_hid_output(hdev, &report.report, report.length + 1, +				HID_FEATURE_REPORT); +	if (ret != report.length + 1) { +		hid_err(hdev, "error writing %s string: %d\n", kattr->attr.name, +			ret); +		if (ret < 0) +			return ret; +		return -EIO; +	} + +	chmod_sysfs_attrs(hdev); +	return count; +} + +static ssize_t pstr_show(struct device *kdev, +			 struct device_attribute *kattr, char *buf) +{ +	struct hid_device *hdev = container_of(kdev, struct hid_device, dev); +	struct cp2112_pstring_attribute *attr = +		container_of(kattr, struct cp2112_pstring_attribute, attr); +	struct cp2112_string_report report; +	u8 length; +	int ret; + +	ret = cp2112_hid_get(hdev, attr->report, &report.report, +			     sizeof(report) - 1, HID_FEATURE_REPORT); +	if (ret < 3) { +		hid_err(hdev, "error reading %s string: %d\n", kattr->attr.name, +			ret); +		if (ret < 0) +			return ret; +		return -EIO; +	} + +	if (report.length < 2) { +		hid_err(hdev, "invalid %s string length: %d\n", +			kattr->attr.name, report.length); +		return -EIO; +	} + +	length = report.length > ret - 1 ? ret - 1 : report.length; +	length = (length - 2) / sizeof(report.string[0]); +	ret = utf16s_to_utf8s(report.string, length, UTF16_LITTLE_ENDIAN, buf, +			      PAGE_SIZE - 1); +	buf[ret++] = '\n'; +	return ret; +} + +#define CP2112_PSTR_ATTR(name, _report) \ +static struct cp2112_pstring_attribute dev_attr_##name = { \ +	.attr = __ATTR(name, (S_IWUSR | S_IRUGO), pstr_show, pstr_store), \ +	.report = _report, \ +}; + +CP2112_PSTR_ATTR(manufacturer,	CP2112_MANUFACTURER_STRING); +CP2112_PSTR_ATTR(product,	CP2112_PRODUCT_STRING); +CP2112_PSTR_ATTR(serial,	CP2112_SERIAL_STRING); + +#undef CP2112_PSTR_ATTR + +static const struct attribute_group cp2112_attr_group = { +	.attrs = (struct attribute *[]){ +		&dev_attr_vendor_id.attr, +		&dev_attr_product_id.attr, +		&dev_attr_max_power.attr, +		&dev_attr_power_mode.attr, +		&dev_attr_release_version.attr, +		&dev_attr_manufacturer.attr.attr, +		&dev_attr_product.attr.attr, +		&dev_attr_serial.attr.attr, +		NULL +	} +}; + +/* Chmoding our sysfs attributes is simply a way to expose which fields in the + * PROM have already been programmed. We do not depend on this preventing + * writing to these attributes since the CP2112 will simply ignore writes to + * already-programmed fields. This is why there is no sense in fixing this + * racy behaviour. + */ +static void chmod_sysfs_attrs(struct hid_device *hdev) +{ +	struct attribute **attr; +	u8 buf[2]; +	int ret; + +	ret = cp2112_hid_get(hdev, CP2112_LOCK_BYTE, buf, sizeof(buf), +			     HID_FEATURE_REPORT); +	if (ret != sizeof(buf)) { +		hid_err(hdev, "error reading lock byte: %d\n", ret); +		return; +	} + +	for (attr = cp2112_attr_group.attrs; *attr; ++attr) { +		umode_t mode = (buf[1] & 1) ? S_IWUSR | S_IRUGO : S_IRUGO; +		ret = sysfs_chmod_file(&hdev->dev.kobj, *attr, mode); +		if (ret < 0) +			hid_err(hdev, "error chmoding sysfs file %s\n", +				(*attr)->name); +		buf[1] >>= 1; +	} +} + +static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ +	struct cp2112_device *dev; +	u8 buf[3]; +	struct cp2112_smbus_config_report config; +	int ret; + +	ret = hid_parse(hdev); +	if (ret) { +		hid_err(hdev, "parse failed\n"); +		return ret; +	} + +	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); +	if (ret) { +		hid_err(hdev, "hw start failed\n"); +		return ret; +	} + +	ret = hid_hw_open(hdev); +	if (ret) { +		hid_err(hdev, "hw open failed\n"); +		goto err_hid_stop; +	} + +	ret = hid_hw_power(hdev, PM_HINT_FULLON); +	if (ret < 0) { +		hid_err(hdev, "power management error: %d\n", ret); +		goto err_hid_close; +	} + +	ret = cp2112_hid_get(hdev, CP2112_GET_VERSION_INFO, buf, sizeof(buf), +			     HID_FEATURE_REPORT); +	if (ret != sizeof(buf)) { +		hid_err(hdev, "error requesting version\n"); +		if (ret >= 0) +			ret = -EIO; +		goto err_power_normal; +	} + +	hid_info(hdev, "Part Number: 0x%02X Device Version: 0x%02X\n", +		 buf[1], buf[2]); + +	ret = cp2112_hid_get(hdev, CP2112_SMBUS_CONFIG, (u8 *)&config, +			     sizeof(config), HID_FEATURE_REPORT); +	if (ret != sizeof(config)) { +		hid_err(hdev, "error requesting SMBus config\n"); +		if (ret >= 0) +			ret = -EIO; +		goto err_power_normal; +	} + +	config.retry_time = cpu_to_be16(1); + +	ret = cp2112_hid_output(hdev, (u8 *)&config, sizeof(config), +				HID_FEATURE_REPORT); +	if (ret != sizeof(config)) { +		hid_err(hdev, "error setting SMBus config\n"); +		if (ret >= 0) +			ret = -EIO; +		goto err_power_normal; +	} + +	dev = kzalloc(sizeof(*dev), GFP_KERNEL); +	if (!dev) { +		ret = -ENOMEM; +		goto err_power_normal; +	} + +	hid_set_drvdata(hdev, (void *)dev); +	dev->hdev		= hdev; +	dev->adap.owner		= THIS_MODULE; +	dev->adap.class		= I2C_CLASS_HWMON; +	dev->adap.algo		= &smbus_algorithm; +	dev->adap.algo_data	= dev; +	dev->adap.dev.parent	= &hdev->dev; +	snprintf(dev->adap.name, sizeof(dev->adap.name), +		 "CP2112 SMBus Bridge on hiddev%d", hdev->minor); +	init_waitqueue_head(&dev->wait); + +	hid_device_io_start(hdev); +	ret = i2c_add_adapter(&dev->adap); +	hid_device_io_stop(hdev); + +	if (ret) { +		hid_err(hdev, "error registering i2c adapter\n"); +		goto err_free_dev; +	} + +	hid_dbg(hdev, "adapter registered\n"); + +	dev->gc.label			= "cp2112_gpio"; +	dev->gc.direction_input		= cp2112_gpio_direction_input; +	dev->gc.direction_output	= cp2112_gpio_direction_output; +	dev->gc.set			= cp2112_gpio_set; +	dev->gc.get			= cp2112_gpio_get; +	dev->gc.base			= -1; +	dev->gc.ngpio			= 8; +	dev->gc.can_sleep		= 1; +	dev->gc.dev			= &hdev->dev; + +	ret = gpiochip_add(&dev->gc); +	if (ret < 0) { +		hid_err(hdev, "error registering gpio chip\n"); +		goto err_free_i2c; +	} + +	ret = sysfs_create_group(&hdev->dev.kobj, &cp2112_attr_group); +	if (ret < 0) { +		hid_err(hdev, "error creating sysfs attrs\n"); +		goto err_gpiochip_remove; +	} + +	chmod_sysfs_attrs(hdev); +	hid_hw_power(hdev, PM_HINT_NORMAL); + +	return ret; + +err_gpiochip_remove: +	if (gpiochip_remove(&dev->gc) < 0) +		hid_err(hdev, "error removing gpio chip\n"); +err_free_i2c: +	i2c_del_adapter(&dev->adap); +err_free_dev: +	kfree(dev); +err_power_normal: +	hid_hw_power(hdev, PM_HINT_NORMAL); +err_hid_close: +	hid_hw_close(hdev); +err_hid_stop: +	hid_hw_stop(hdev); +	return ret; +} + +static void cp2112_remove(struct hid_device *hdev) +{ +	struct cp2112_device *dev = hid_get_drvdata(hdev); + +	sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group); +	if (gpiochip_remove(&dev->gc)) +		hid_err(hdev, "unable to remove gpio chip\n"); +	i2c_del_adapter(&dev->adap); +	/* i2c_del_adapter has finished removing all i2c devices from our +	 * adapter. Well behaved devices should no longer call our cp2112_xfer +	 * and should have waited for any pending calls to finish. It has also +	 * waited for device_unregister(&adap->dev) to complete. Therefore we +	 * can safely free our struct cp2112_device. +	 */ +	hid_hw_close(hdev); +	hid_hw_stop(hdev); +	kfree(dev); +} + +static int cp2112_raw_event(struct hid_device *hdev, struct hid_report *report, +			    u8 *data, int size) +{ +	struct cp2112_device *dev = hid_get_drvdata(hdev); +	struct cp2112_xfer_status_report *xfer = (void *)data; + +	switch (data[0]) { +	case CP2112_TRANSFER_STATUS_RESPONSE: +		hid_dbg(hdev, "xfer status: %02x %02x %04x %04x\n", +			xfer->status0, xfer->status1, +			be16_to_cpu(xfer->retries), be16_to_cpu(xfer->length)); + +		switch (xfer->status0) { +		case STATUS0_IDLE: +			dev->xfer_status = -EAGAIN; +			break; +		case STATUS0_BUSY: +			dev->xfer_status = -EBUSY; +			break; +		case STATUS0_COMPLETE: +			dev->xfer_status = be16_to_cpu(xfer->length); +			break; +		case STATUS0_ERROR: +			switch (xfer->status1) { +			case STATUS1_TIMEOUT_NACK: +			case STATUS1_TIMEOUT_BUS: +				dev->xfer_status = -ETIMEDOUT; +				break; +			default: +				dev->xfer_status = -EIO; +				break; +			} +			break; +		default: +			dev->xfer_status = -EINVAL; +			break; +		} + +		atomic_set(&dev->xfer_avail, 1); +		break; +	case CP2112_DATA_READ_RESPONSE: +		hid_dbg(hdev, "read response: %02x %02x\n", data[1], data[2]); + +		dev->read_length = data[2]; +		if (dev->read_length > sizeof(dev->read_data)) +			dev->read_length = sizeof(dev->read_data); + +		memcpy(dev->read_data, &data[3], dev->read_length); +		atomic_set(&dev->read_avail, 1); +		break; +	default: +		hid_err(hdev, "unknown report\n"); + +		return 0; +	} + +	wake_up_interruptible(&dev->wait); +	return 1; +} + +static struct hid_driver cp2112_driver = { +	.name		= "cp2112", +	.id_table	= cp2112_devices, +	.probe		= cp2112_probe, +	.remove		= cp2112_remove, +	.raw_event	= cp2112_raw_event, +}; + +module_hid_driver(cp2112_driver); +MODULE_DESCRIPTION("Silicon Labs HID USB to SMBus master bridge"); +MODULE_AUTHOR("David Barksdale <dbarksdale@uplogix.com>"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 8fae6d1414cc..0658cc4578fe 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -455,12 +455,22 @@ static void mousevsc_hid_stop(struct hid_device *hid)  {  } +static int mousevsc_hid_raw_request(struct hid_device *hid, +				    unsigned char report_num, +				    __u8 *buf, size_t len, +				    unsigned char rtype, +				    int reqtype) +{ +	return 0; +} +  static struct hid_ll_driver mousevsc_ll_driver = {  	.parse = mousevsc_hid_parse,  	.open = mousevsc_hid_open,  	.close = mousevsc_hid_close,  	.start = mousevsc_hid_start,  	.stop = mousevsc_hid_stop, +	.raw_request = mousevsc_hid_raw_request,  };  static struct hid_driver mousevsc_hid_driver; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 92b40c09d917..239f29c1c85c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -240,6 +240,7 @@  #define USB_VENDOR_ID_CYGNAL		0x10c4  #define USB_DEVICE_ID_CYGNAL_RADIO_SI470X	0x818a +#define USB_DEVICE_ID_CYGNAL_CP2112	0xea90  #define USB_VENDOR_ID_CYPRESS		0x04b4  #define USB_DEVICE_ID_CYPRESS_MOUSE	0x0001 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 15959fbae268..f5aef792f13b 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1150,7 +1150,7 @@ static void hidinput_led_worker(struct work_struct *work)  					      led_work);  	struct hid_field *field;  	struct hid_report *report; -	int len; +	int len, ret;  	__u8 *buf;  	field = hidinput_get_led_field(hid); @@ -1184,7 +1184,10 @@ static void hidinput_led_worker(struct work_struct *work)  	hid_output_report(report, buf);  	/* synchronous output report */ -	hid_output_raw_report(hid, buf, len, HID_OUTPUT_REPORT); +	ret = hid_hw_output_report(hid, buf, len); +	if (ret == -ENOSYS) +		hid_hw_raw_request(hid, report->id, buf, len, HID_OUTPUT_REPORT, +				HID_REQ_SET_REPORT);  	kfree(buf);  } @@ -1263,8 +1266,7 @@ static struct hid_input *hidinput_allocate(struct hid_device *hid)  	}  	input_set_drvdata(input_dev, hid); -	if (hid->ll_driver->request || hid->hid_output_raw_report) -		input_dev->event = hidinput_input_event; +	input_dev->event = hidinput_input_event;  	input_dev->open = hidinput_open;  	input_dev->close = hidinput_close;  	input_dev->setkeycode = hidinput_setkeycode; diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c index 76ed7e512dcf..a976f48263f6 100644 --- a/drivers/hid/hid-lg.c +++ b/drivers/hid/hid-lg.c @@ -692,8 +692,8 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)  	if (hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {  		unsigned char buf[] = { 0x00, 0xAF,  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -		ret = hid_output_raw_report(hdev, buf, sizeof(buf), -					    HID_FEATURE_REPORT); +		ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf), +					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  		if (ret >= 0) {  			/* insert a little delay of 10 jiffies ~ 40ms */ @@ -705,8 +705,8 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)  			buf[1] = 0xB2;  			get_random_bytes(&buf[2], 2); -			ret = hid_output_raw_report(hdev, buf, sizeof(buf), -						    HID_FEATURE_REPORT); +			ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf), +					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  		}  	} diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 980ede54782d..486dbde2ba2d 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -193,9 +193,6 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {  static struct hid_ll_driver logi_dj_ll_driver; -static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, -					size_t count, -					unsigned char report_type);  static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev);  static void logi_dj_recv_destroy_djhid_device(struct dj_receiver_dev *djrcv_dev, @@ -262,7 +259,6 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev,  	}  	dj_hiddev->ll_driver = &logi_dj_ll_driver; -	dj_hiddev->hid_output_raw_report = logi_dj_output_hidraw_report;  	dj_hiddev->dev.parent = &djrcv_hdev->dev;  	dj_hiddev->bus = BUS_USB; @@ -544,9 +540,10 @@ static void logi_dj_ll_close(struct hid_device *hid)  	dbg_hid("%s:%s\n", __func__, hid->phys);  } -static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf, -					size_t count, -					unsigned char report_type) +static int logi_dj_ll_raw_request(struct hid_device *hid, +				  unsigned char reportnum, __u8 *buf, +				  size_t count, unsigned char report_type, +				  int reqtype)  {  	struct dj_device *djdev = hid->driver_data;  	struct dj_receiver_dev *djrcv_dev = djdev->dj_receiver_dev; @@ -567,15 +564,8 @@ static int logi_dj_output_hidraw_report(struct hid_device *hid, u8 * buf,  	out_buf[1] = djdev->device_index;  	memcpy(out_buf + 2, buf, count); -	/* -	 * hid-generic calls us with hid_output_raw_report(), but the LEDs -	 * are set through a SET_REPORT command. It works for USB-HID devices -	 * because usbhid either calls a SET_REPORT or directly send the output -	 * report depending if the device presents an urbout. -	 * Let be simple, send a SET_REPORT request. -	 */  	ret = hid_hw_raw_request(djrcv_dev->hdev, out_buf[0], out_buf, -		DJREPORT_SHORT_LENGTH, report_type, HID_REQ_SET_REPORT); +		DJREPORT_SHORT_LENGTH, report_type, reqtype);  	kfree(out_buf);  	return ret; @@ -662,6 +652,7 @@ static struct hid_ll_driver logi_dj_ll_driver = {  	.stop = logi_dj_ll_stop,  	.open = logi_dj_ll_open,  	.close = logi_dj_ll_close, +	.raw_request = logi_dj_ll_raw_request,  }; diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index cb5db3afc690..ecc2cbf300cc 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -538,8 +538,8 @@ static int magicmouse_probe(struct hid_device *hdev,  	 * but there seems to be no other way of switching the mode.  	 * Thus the super-ugly hacky success check below.  	 */ -	ret = hid_output_raw_report(hdev, feature, sizeof(feature), -			HID_FEATURE_REPORT); +	ret = hid_hw_raw_request(hdev, feature[0], feature, sizeof(feature), +				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  	if (ret != -EIO && ret != sizeof(feature)) {  		hid_err(hdev, "unable to request touch data (%d)\n", ret);  		goto err_stop_hw; diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index c364be158ae6..908de2789219 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -29,7 +29,6 @@  #include <linux/hid.h>  #include <linux/module.h>  #include <linux/slab.h> -#include <linux/usb.h>  #include <linux/leds.h>  #include <linux/power_supply.h>  #include <linux/spinlock.h> @@ -1007,45 +1006,6 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,  }  /* - * The Sony Sixaxis does not handle HID Output Reports on the Interrupt EP - * like it should according to usbhid/hid-core.c::usbhid_output_raw_report() - * so we need to override that forcing HID Output Reports on the Control EP. - * - * There is also another issue about HID Output Reports via USB, the Sixaxis - * does not want the report_id as part of the data packet, so we have to - * discard buf[0] when sending the actual control message, even for numbered - * reports, humpf! - */ -static int sixaxis_usb_output_raw_report(struct hid_device *hid, __u8 *buf, -		size_t count, unsigned char report_type) -{ -	struct usb_interface *intf = to_usb_interface(hid->dev.parent); -	struct usb_device *dev = interface_to_usbdev(intf); -	struct usb_host_interface *interface = intf->cur_altsetting; -	int report_id = buf[0]; -	int ret; - -	if (report_type == HID_OUTPUT_REPORT) { -		/* Don't send the Report ID */ -		buf++; -		count--; -	} - -	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), -		HID_REQ_SET_REPORT, -		USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, -		((report_type + 1) << 8) | report_id, -		interface->desc.bInterfaceNumber, buf, count, -		USB_CTRL_SET_TIMEOUT); - -	/* Count also the Report ID, in case of an Output report. */ -	if (ret > 0 && report_type == HID_OUTPUT_REPORT) -		ret++; - -	return ret; -} - -/*   * Sending HID_REQ_GET_REPORT changes the operation mode of the ps3 controller   * to "operational".  Without this, the ps3 controller will not report any   * events. @@ -1072,8 +1032,8 @@ static int sixaxis_set_operational_usb(struct hid_device *hdev)  static int sixaxis_set_operational_bt(struct hid_device *hdev)  {  	unsigned char buf[] = { 0xf4,  0x42, 0x03, 0x00, 0x00 }; -	return hid_output_raw_report(hdev, buf, sizeof(buf), -				     HID_FEATURE_REPORT); +	return hid_hw_raw_request(hdev, buf[0], buf, sizeof(buf), +				  HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  }  /* @@ -1305,11 +1265,8 @@ static void sixaxis_state_worker(struct work_struct *work)  	buf[10] |= sc->led_state[2] << 3;  	buf[10] |= sc->led_state[3] << 4; -	if (sc->quirks & SIXAXIS_CONTROLLER_USB) -		hid_output_raw_report(sc->hdev, buf, sizeof(buf), HID_OUTPUT_REPORT); -	else -		hid_hw_raw_request(sc->hdev, 0x01, buf, sizeof(buf), -				HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); +	hid_hw_raw_request(sc->hdev, 0x01, buf, sizeof(buf), HID_OUTPUT_REPORT, +			HID_REQ_SET_REPORT);  }  static void dualshock4_state_worker(struct work_struct *work) @@ -1659,7 +1616,18 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)  	}  	if (sc->quirks & SIXAXIS_CONTROLLER_USB) { -		hdev->hid_output_raw_report = sixaxis_usb_output_raw_report; +		/* +		 * The Sony Sixaxis does not handle HID Output Reports on the +		 * Interrupt EP like it could, so we need to force HID Output +		 * Reports to use HID_REQ_SET_REPORT on the Control EP. +		 * +		 * There is also another issue about HID Output Reports via USB, +		 * the Sixaxis does not want the report_id as part of the data +		 * packet, so we have to discard buf[0] when sending the actual +		 * control message, even for numbered reports, humpf! +		 */ +		hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP; +		hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;  		ret = sixaxis_set_operational_usb(hdev);  		sc->worker_initialized = 1;  		INIT_WORK(&sc->state_worker, sixaxis_state_worker); diff --git a/drivers/hid/hid-thingm.c b/drivers/hid/hid-thingm.c index 7dd3197f3b3e..a97c78845f7b 100644 --- a/drivers/hid/hid-thingm.c +++ b/drivers/hid/hid-thingm.c @@ -48,8 +48,8 @@ static int blink1_send_command(struct blink1_data *data,  			buf[0], buf[1], buf[2], buf[3], buf[4],  			buf[5], buf[6], buf[7], buf[8]); -	ret = hid_output_raw_report(data->hdev, buf, BLINK1_CMD_SIZE, -				    HID_FEATURE_REPORT); +	ret = hid_hw_raw_request(data->hdev, buf[0], buf, BLINK1_CMD_SIZE, +				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  	return ret < 0 ? ret : 0;  } diff --git a/drivers/hid/hid-wacom.c b/drivers/hid/hid-wacom.c index c720db912edb..902013ec041b 100644 --- a/drivers/hid/hid-wacom.c +++ b/drivers/hid/hid-wacom.c @@ -128,7 +128,8 @@ static void wacom_set_image(struct hid_device *hdev, const char *image,  	rep_data[0] = WAC_CMD_ICON_START_STOP;  	rep_data[1] = 0; -	ret = hid_output_raw_report(hdev, rep_data, 2, HID_FEATURE_REPORT); +	ret = hid_hw_raw_request(hdev, rep_data[0], rep_data, 2, +				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  	if (ret < 0)  		goto err; @@ -142,14 +143,15 @@ static void wacom_set_image(struct hid_device *hdev, const char *image,  			rep_data[j + 3] = p[(i << 6) + j];  		rep_data[2] = i; -		ret = hid_output_raw_report(hdev, rep_data, 67, -					HID_FEATURE_REPORT); +		ret = hid_hw_raw_request(hdev, rep_data[0], rep_data, 67, +					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  	}  	rep_data[0] = WAC_CMD_ICON_START_STOP;  	rep_data[1] = 0; -	ret = hid_output_raw_report(hdev, rep_data, 2, HID_FEATURE_REPORT); +	ret = hid_hw_raw_request(hdev, rep_data[0], rep_data, 2, +				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  err:  	return; @@ -181,7 +183,8 @@ static void wacom_leds_set_brightness(struct led_classdev *led_dev,  		buf[3] = value;  		/* use fixed brightness for OLEDs */  		buf[4] = 0x08; -		hid_output_raw_report(hdev, buf, 9, HID_FEATURE_REPORT); +		hid_hw_raw_request(hdev, buf[0], buf, 9, HID_FEATURE_REPORT, +				   HID_REQ_SET_REPORT);  		kfree(buf);  	} @@ -337,8 +340,8 @@ static void wacom_set_features(struct hid_device *hdev, u8 speed)  		rep_data[0] = 0x03 ; rep_data[1] = 0x00;  		limit = 3;  		do { -			ret = hid_output_raw_report(hdev, rep_data, 2, -					HID_FEATURE_REPORT); +			ret = hid_hw_raw_request(hdev, rep_data[0], rep_data, 2, +					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  		} while (ret < 0 && limit-- > 0);  		if (ret >= 0) { @@ -350,8 +353,9 @@ static void wacom_set_features(struct hid_device *hdev, u8 speed)  			rep_data[1] = 0x00;  			limit = 3;  			do { -				ret = hid_output_raw_report(hdev, -					rep_data, 2, HID_FEATURE_REPORT); +				ret = hid_hw_raw_request(hdev, rep_data[0], +					rep_data, 2, HID_FEATURE_REPORT, +					HID_REQ_SET_REPORT);  			} while (ret < 0 && limit-- > 0);  			if (ret >= 0) { @@ -376,8 +380,8 @@ static void wacom_set_features(struct hid_device *hdev, u8 speed)  		rep_data[0] = 0x03;  		rep_data[1] = wdata->features; -		ret = hid_output_raw_report(hdev, rep_data, 2, -					HID_FEATURE_REPORT); +		ret = hid_hw_raw_request(hdev, rep_data[0], rep_data, 2, +				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);  		if (ret >= 0)  			wdata->high_speed = speed;  		break; diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index d7dc6c5bc244..d00391418d1a 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -28,14 +28,14 @@ static int wiimote_hid_send(struct hid_device *hdev, __u8 *buffer,  	__u8 *buf;  	int ret; -	if (!hdev->hid_output_raw_report) +	if (!hdev->ll_driver->output_report)  		return -ENODEV;  	buf = kmemdup(buffer, count, GFP_KERNEL);  	if (!buf)  		return -ENOMEM; -	ret = hid_output_raw_report(hdev, buf, count, HID_OUTPUT_REPORT); +	ret = hid_hw_output_report(hdev, buf, count);  	kfree(buf);  	return ret; diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index f8708c93f85c..ffa648ce002e 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -123,10 +123,6 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,  	dev = hidraw_table[minor]->hid; -	if (!dev->hid_output_raw_report) { -		ret = -ENODEV; -		goto out; -	}  	if (count > HID_MAX_BUFFER_SIZE) {  		hid_warn(dev, "pid %d passed too large report\n", @@ -153,7 +149,21 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,  		goto out_free;  	} -	ret = hid_output_raw_report(dev, buf, count, report_type); +	if ((report_type == HID_OUTPUT_REPORT) && +	    !(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) { +		ret = hid_hw_output_report(dev, buf, count); +		/* +		 * compatibility with old implementation of USB-HID and I2C-HID: +		 * if the device does not support receiving output reports, +		 * on an interrupt endpoint, fallback to SET_REPORT HID command. +		 */ +		if (ret != -ENOSYS) +			goto out_free; +	} + +	ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type, +				HID_REQ_SET_REPORT); +  out_free:  	kfree(buf);  out: diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c index f4ea7343e823..2de2b8e22462 100644 --- a/drivers/hid/i2c-hid/i2c-hid.c +++ b/drivers/hid/i2c-hid/i2c-hid.c @@ -256,18 +256,27 @@ static int i2c_hid_get_report(struct i2c_client *client, u8 reportType,  	return 0;  } -static int i2c_hid_set_report(struct i2c_client *client, u8 reportType, -		u8 reportID, unsigned char *buf, size_t data_len) +/** + * i2c_hid_set_or_send_report: forward an incoming report to the device + * @client: the i2c_client of the device + * @reportType: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT + * @reportID: the report ID + * @buf: the actual data to transfer, without the report ID + * @len: size of buf + * @use_data: true: use SET_REPORT HID command, false: send plain OUTPUT report + */ +static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType, +		u8 reportID, unsigned char *buf, size_t data_len, bool use_data)  {  	struct i2c_hid *ihid = i2c_get_clientdata(client);  	u8 *args = ihid->argsbuf; -	const struct i2c_hid_cmd * hidcmd = &hid_set_report_cmd; +	const struct i2c_hid_cmd *hidcmd;  	int ret;  	u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister);  	u16 outputRegister = le16_to_cpu(ihid->hdesc.wOutputRegister);  	u16 maxOutputLength = le16_to_cpu(ihid->hdesc.wMaxOutputLength); -	/* hidraw already checked that data_len < HID_MAX_BUFFER_SIZE */ +	/* hid_hw_* already checked that data_len < HID_MAX_BUFFER_SIZE */  	u16 size =	2			/* size */ +  			(reportID ? 1 : 0)	/* reportID */ +  			data_len		/* buf */; @@ -278,6 +287,9 @@ static int i2c_hid_set_report(struct i2c_client *client, u8 reportType,  	i2c_hid_dbg(ihid, "%s\n", __func__); +	if (!use_data && maxOutputLength == 0) +		return -ENOSYS; +  	if (reportID >= 0x0F) {  		args[index++] = reportID;  		reportID = 0x0F; @@ -287,9 +299,10 @@ static int i2c_hid_set_report(struct i2c_client *client, u8 reportType,  	 * use the data register for feature reports or if the device does not  	 * support the output register  	 */ -	if (reportType == 0x03 || maxOutputLength == 0) { +	if (use_data) {  		args[index++] = dataRegister & 0xFF;  		args[index++] = dataRegister >> 8; +		hidcmd = &hid_set_report_cmd;  	} else {  		args[index++] = outputRegister & 0xFF;  		args[index++] = outputRegister >> 8; @@ -550,7 +563,7 @@ static int i2c_hid_get_raw_report(struct hid_device *hid,  }  static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, -		size_t count, unsigned char report_type) +		size_t count, unsigned char report_type, bool use_data)  {  	struct i2c_client *client = hid->driver_data;  	int report_id = buf[0]; @@ -564,9 +577,9 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,  		count--;  	} -	ret = i2c_hid_set_report(client, +	ret = i2c_hid_set_or_send_report(client,  				report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, -				report_id, buf, count); +				report_id, buf, count, use_data);  	if (report_id && ret >= 0)  		ret++; /* add report_id to the number of transfered bytes */ @@ -574,34 +587,27 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,  	return ret;  } -static void i2c_hid_request(struct hid_device *hid, struct hid_report *rep, -		int reqtype) +static int i2c_hid_output_report(struct hid_device *hid, __u8 *buf, +		size_t count)  { -	struct i2c_client *client = hid->driver_data; -	char *buf; -	int ret; -	int len = i2c_hid_get_report_length(rep) - 2; - -	buf = kzalloc(len, GFP_KERNEL); -	if (!buf) -		return; +	return i2c_hid_output_raw_report(hid, buf, count, HID_OUTPUT_REPORT, +			false); +} +static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum, +			       __u8 *buf, size_t len, unsigned char rtype, +			       int reqtype) +{  	switch (reqtype) {  	case HID_REQ_GET_REPORT: -		ret = i2c_hid_get_raw_report(hid, rep->id, buf, len, rep->type); -		if (ret < 0) -			dev_err(&client->dev, "%s: unable to get report: %d\n", -				__func__, ret); -		else -			hid_input_report(hid, rep->type, buf, ret, 0); -		break; +		return i2c_hid_get_raw_report(hid, reportnum, buf, len, rtype);  	case HID_REQ_SET_REPORT: -		hid_output_report(rep, buf); -		i2c_hid_output_raw_report(hid, buf, len, rep->type); -		break; +		if (buf[0] != reportnum) +			return -EINVAL; +		return i2c_hid_output_raw_report(hid, buf, len, rtype, true); +	default: +		return -EIO;  	} - -	kfree(buf);  }  static int i2c_hid_parse(struct hid_device *hid) @@ -760,7 +766,8 @@ static struct hid_ll_driver i2c_hid_ll_driver = {  	.open = i2c_hid_open,  	.close = i2c_hid_close,  	.power = i2c_hid_power, -	.request = i2c_hid_request, +	.output_report = i2c_hid_output_report, +	.raw_request = i2c_hid_raw_request,  };  static int i2c_hid_init_irq(struct i2c_client *client) @@ -1005,7 +1012,6 @@ static int i2c_hid_probe(struct i2c_client *client,  	hid->driver_data = client;  	hid->ll_driver = &i2c_hid_ll_driver; -	hid->hid_output_raw_report = i2c_hid_output_raw_report;  	hid->dev.parent = &client->dev;  	ACPI_COMPANION_SET(&hid->dev, ACPI_COMPANION(&client->dev));  	hid->bus = BUS_I2C; diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c index 12439e1eeae2..7ed79be2686a 100644 --- a/drivers/hid/uhid.c +++ b/drivers/hid/uhid.c @@ -247,27 +247,22 @@ static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,  static int uhid_hid_output_report(struct hid_device *hid, __u8 *buf,  				  size_t count)  { -	struct uhid_device *uhid = hid->driver_data; -	unsigned long flags; -	struct uhid_event *ev; - -	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 = UHID_OUTPUT_REPORT; -	memcpy(ev->u.output.data, buf, count); - -	spin_lock_irqsave(&uhid->qlock, flags); -	uhid_queue(uhid, ev); -	spin_unlock_irqrestore(&uhid->qlock, flags); +	return uhid_hid_output_raw(hid, buf, count, HID_OUTPUT_REPORT); +} -	return count; +static int uhid_raw_request(struct hid_device *hid, unsigned char reportnum, +			    __u8 *buf, size_t len, unsigned char rtype, +			    int reqtype) +{ +	switch (reqtype) { +	case HID_REQ_GET_REPORT: +		return uhid_hid_get_raw(hid, reportnum, buf, len, rtype); +	case HID_REQ_SET_REPORT: +		/* TODO: implement proper SET_REPORT functionality */ +		return -ENOSYS; +	default: +		return -EIO; +	}  }  static struct hid_ll_driver uhid_hid_driver = { @@ -277,6 +272,7 @@ static struct hid_ll_driver uhid_hid_driver = {  	.close = uhid_hid_close,  	.parse = uhid_hid_parse,  	.output_report = uhid_hid_output_report, +	.raw_request = uhid_raw_request,  };  #ifdef CONFIG_COMPAT @@ -404,7 +400,6 @@ static int uhid_dev_create(struct uhid_device *uhid,  	hid->uniq[63] = 0;  	hid->ll_driver = &uhid_hid_driver; -	hid->hid_output_raw_report = uhid_hid_output_raw;  	hid->bus = ev->u.create.bus;  	hid->vendor = ev->u.create.vendor;  	hid->product = ev->u.create.product; diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index b9a770f4d7ae..7b88f4cb9902 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -894,7 +894,12 @@ static int usbhid_set_raw_report(struct hid_device *hid, unsigned int reportnum,  	int ret, skipped_report_id = 0;  	/* Byte 0 is the report number. Report data starts at byte 1.*/ -	buf[0] = reportnum; +	if ((rtype == HID_OUTPUT_REPORT) && +	    (hid->quirks & HID_QUIRK_SKIP_OUTPUT_REPORT_ID)) +		buf[0] = 0; +	else +		buf[0] = reportnum; +  	if (buf[0] == 0x0) {  		/* Don't send the Report ID */  		buf++; @@ -922,7 +927,7 @@ static int usbhid_output_report(struct hid_device *hid, __u8 *buf, size_t count)  	int actual_length, skipped_report_id = 0, ret;  	if (!usbhid->urbout) -		return -EIO; +		return -ENOSYS;  	if (buf[0] == 0x0) {  		/* Don't send the Report ID */ @@ -945,17 +950,6 @@ static int usbhid_output_report(struct hid_device *hid, __u8 *buf, size_t count)  	return ret;  } -static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, -		size_t count, unsigned char report_type) -{ -	struct usbhid_device *usbhid = hid->driver_data; - -	if (usbhid->urbout && report_type != HID_FEATURE_REPORT) -		return usbhid_output_report(hid, buf, count); - -	return usbhid_set_raw_report(hid, buf[0], buf, count, report_type); -} -  static void usbhid_restart_queues(struct usbhid_device *usbhid)  {  	if (usbhid->urbout && !test_bit(HID_OUT_RUNNING, &usbhid->iofl)) @@ -1289,7 +1283,6 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *  	usb_set_intfdata(intf, hid);  	hid->ll_driver = &usb_hid_driver; -	hid->hid_output_raw_report = usbhid_output_raw_report;  	hid->ff_init = hid_pidff_init;  #ifdef CONFIG_USB_HIDDEV  	hid->hiddev_connect = hiddev_connect; diff --git a/include/linux/hid.h b/include/linux/hid.h index a837ede65ec6..01a90b8d53bb 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -287,6 +287,8 @@ struct hid_item {  #define HID_QUIRK_NO_EMPTY_INPUT		0x00000100  #define HID_QUIRK_NO_INIT_INPUT_REPORTS		0x00000200  #define HID_QUIRK_SKIP_OUTPUT_REPORTS		0x00010000 +#define HID_QUIRK_SKIP_OUTPUT_REPORT_ID		0x00020000 +#define HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP	0x00040000  #define HID_QUIRK_FULLSPEED_INTERVAL		0x10000000  #define HID_QUIRK_NO_INIT_REPORTS		0x20000000  #define HID_QUIRK_NO_IGNORE			0x40000000 @@ -508,9 +510,6 @@ struct hid_device {							/* device report descriptor */  				  struct hid_usage *, __s32);  	void (*hiddev_report_event) (struct hid_device *, struct hid_report *); -	/* handler for raw output data, used by hidraw */ -	int (*hid_output_raw_report) (struct hid_device *, __u8 *, size_t, unsigned char); -  	/* debugging support via debugfs */  	unsigned short debug;  	struct dentry *debug_dir; @@ -753,6 +752,7 @@ struct hid_field *hidinput_get_led_field(struct hid_device *hid);  unsigned int hidinput_count_leds(struct hid_device *hid);  __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code);  void hid_output_report(struct hid_report *report, __u8 *data); +void __hid_request(struct hid_device *hid, struct hid_report *rep, int reqtype);  u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags);  struct hid_device *hid_allocate_device(void);  struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); @@ -965,7 +965,9 @@ static inline void hid_hw_request(struct hid_device *hdev,  				  struct hid_report *report, int reqtype)  {  	if (hdev->ll_driver->request) -		hdev->ll_driver->request(hdev, report, reqtype); +		return hdev->ll_driver->request(hdev, report, reqtype); + +	__hid_request(hdev, report, reqtype);  }  /** @@ -986,11 +988,11 @@ static inline int hid_hw_raw_request(struct hid_device *hdev,  				  unsigned char reportnum, __u8 *buf,  				  size_t len, unsigned char rtype, int reqtype)  { -	if (hdev->ll_driver->raw_request) -		return hdev->ll_driver->raw_request(hdev, reportnum, buf, len, -						    rtype, reqtype); +	if (len < 1 || len > HID_MAX_BUFFER_SIZE || !buf) +		return -EINVAL; -	return -ENOSYS; +	return hdev->ll_driver->raw_request(hdev, reportnum, buf, len, +						    rtype, reqtype);  }  /** @@ -1005,6 +1007,9 @@ static inline int hid_hw_raw_request(struct hid_device *hdev,  static inline int hid_hw_output_report(struct hid_device *hdev, __u8 *buf,  					size_t len)  { +	if (len < 1 || len > HID_MAX_BUFFER_SIZE || !buf) +		return -EINVAL; +  	if (hdev->ll_driver->output_report)  		return hdev->ll_driver->output_report(hdev, buf, len); @@ -1012,22 +1017,6 @@ static inline int hid_hw_output_report(struct hid_device *hdev, __u8 *buf,  }  /** - * hid_output_raw_report - send an output or a feature report to the device - * - * @hdev: hid device - * @buf: raw data to transfer - * @len: length of buf - * @report_type: HID_FEATURE_REPORT or HID_OUTPUT_REPORT - * - * @return: count of data transfered, negative if error - */ -static inline int hid_output_raw_report(struct hid_device *hdev, __u8 *buf, -					size_t len, unsigned char report_type) -{ -	return hdev->hid_output_raw_report(hdev, buf, len, report_type); -} - -/**   * hid_hw_idle - send idle request to device   *   * @hdev: hid device diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 98e4840935e2..514ddb5aef96 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -382,18 +382,6 @@ static int hidp_output_report(struct hid_device *hid, __u8 *data, size_t count)  				      data, count);  } -static int hidp_output_raw_report(struct hid_device *hid, unsigned char *data, -		size_t count, unsigned char report_type) -{ -	if (report_type == HID_OUTPUT_REPORT) { -		return hidp_output_report(hid, data, count); -	} else if (report_type != HID_FEATURE_REPORT) { -		return -EINVAL; -	} - -	return hidp_set_raw_report(hid, data[0], data, count, report_type); -} -  static int hidp_raw_request(struct hid_device *hid, unsigned char reportnum,  			    __u8 *buf, size_t len, unsigned char rtype,  			    int reqtype) @@ -776,8 +764,6 @@ static int hidp_setup_hid(struct hidp_session *session,  	hid->dev.parent = &session->conn->hcon->dev;  	hid->ll_driver = &hidp_hid_driver; -	hid->hid_output_raw_report = hidp_output_raw_report; -  	/* True if device is blacklisted in drivers/hid/hid-core.c */  	if (hid_ignore(hid)) {  		hid_destroy_device(session->hid); | 
