diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-07-24 13:30:14 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-07-24 13:30:14 -0700 |
commit | e8ff13b0bf88b5e696323a1eec877783d965b3c6 (patch) | |
tree | aa55093da5ee435f0d2fa8ffede8973f426dbd67 | |
parent | 0cd5ff591ab6473355d5a6a47f7694def28e451d (diff) | |
parent | c062c4d1de57789bf15f7641a24c429eeb8a1c6a (diff) | |
download | linux-3.10-e8ff13b0bf88b5e696323a1eec877783d965b3c6.tar.gz linux-3.10-e8ff13b0bf88b5e696323a1eec877783d965b3c6.tar.bz2 linux-3.10-e8ff13b0bf88b5e696323a1eec877783d965b3c6.zip |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina:
"The list of changes worth pointing out explicitly:
- We are getting 'UHID', which is a new framework for implementing HID
transport drivers in userspace (this is different from HIDRAW, which
is transport-independent and provides report parsing facilities;
uhid is for the other (transport) part of the pipeline).
It's needed for (and currently being used by) Bluetooth-LowEnergy,
as its specification mandates things we don't want in the kernel.
Written by David Herrmann.
- there have been quite a few bugs in runtime suspend/resume paths
(probably never reported to actually happen in the wild, but still).
Alan Stern fixed those.
- a few other driver updates and fixes and random new device support."
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (45 commits)
HID: add ASUS AIO keyboard model AK1D
HID: add support for Cypress barcode scanner 04B4:ED81
HID: Allow drivers to be their own listener
HID: usbhid: fix error paths in suspend
HID: usbhid: check for suspend or reset before restarting
HID: usbhid: replace HID_REPORTED_IDLE with HID_SUSPENDED
HID: usbhid: inline some simple routines
HID: usbhid: fix autosuspend calls
HID: usbhid: fix use-after-free bug
HID: hid-core: optimize in case of hidraw
HID: hidraw: fix list->buffer memleak
HID: uhid: Fix sending events with invalid data
HID: roccat: added sensor sysfs attribute for Savu
HID: Add driver for Holtek based keyboards with broken HID
HID: Add suport for the brightness control keys on HP keyboards
HID: magicmouse: Implement Multi-touch Protocol B (MT-B)
HID: magicmouse: Removing report_touches switch
HID: roccat: rename roccat_common functions to roccat_common2
HID: roccat: fix wrong hid_err usage on struct usb_device
HID: roccat: move functionality to roccat-common
...
40 files changed, 2983 insertions, 554 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd new file mode 100644 index 00000000000..57b92cbdcea --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd @@ -0,0 +1,38 @@ +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_to_select +Date: July 2011 +Contact: linux-input@vger.kernel.org +Description: This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be + is being controlled by press_speed. + Values are 0 or 1. + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging +Date: July 2011 +Contact: linux-input@vger.kernel.org +Description: If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled. + Values are 0 or 1. + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select +Date: July 2011 +Contact: linux-input@vger.kernel.org +Description: For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html + Values are 0 or 1. + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right +Date: July 2011 +Contact: linux-input@vger.kernel.org +Description: This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate + a left or right mouse button click. + Values are 0 or 1. + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity +Date: July 2011 +Contact: linux-input@vger.kernel.org +Description: This file contains the trackpoint sensitivity. + Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity). + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed +Date: July 2011 +Contact: linux-input@vger.kernel.org +Description: This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled. + Values are decimal integers from 1 (slowest) to 255 (fastest). + diff --git a/Documentation/ABI/testing/sysfs-driver-hid-roccat-savu b/Documentation/ABI/testing/sysfs-driver-hid-roccat-savu new file mode 100644 index 00000000000..b42922cf6b1 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-hid-roccat-savu @@ -0,0 +1,77 @@ +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/buttons +Date: Mai 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile is split into general settings and + button settings. buttons holds informations about button layout. + When written, this file lets one write the respective profile + buttons to the mouse. The data has to be 47 bytes long. + The mouse will reject invalid data. + Which profile to write is determined by the profile number + contained in the data. + Before reading this file, control has to be written to select + which profile to read. +Users: http://roccat.sourceforge.net + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/control +Date: Mai 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: When written, this file lets one select which data from which + profile will be read next. The data has to be 3 bytes long. + This file is writeonly. +Users: http://roccat.sourceforge.net + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/general +Date: Mai 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: The mouse can store 5 profiles which can be switched by the + press of a button. A profile is split into general settings and + button settings. profile holds informations like resolution, sensitivity + and light effects. + When written, this file lets one write the respective profile + settings back to the mouse. The data has to be 43 bytes long. + The mouse will reject invalid data. + Which profile to write is determined by the profile number + contained in the data. + This file is writeonly. +Users: http://roccat.sourceforge.net + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/info +Date: Mai 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: When read, this file returns general data like firmware version. + The data is 8 bytes long. + This file is readonly. +Users: http://roccat.sourceforge.net + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/macro +Date: Mai 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: When written, this file lets one store macros with max 500 + keystrokes for a specific button for a specific profile. + Button and profile numbers are included in written data. + The data has to be 2083 bytes long. + Before reading this file, control has to be written to select + which profile and key to read. +Users: http://roccat.sourceforge.net + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/profile +Date: Mai 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: The mouse can store 5 profiles which can be switched by the + press of a button. profile holds number of actual profile. + This value is persistent, so its value determines the profile + that's active when the mouse is powered on next time. + When written, the mouse activates the set profile immediately. + The data has to be 3 bytes long. + The mouse will reject invalid data. +Users: http://roccat.sourceforge.net + +What: /sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/sensor +Date: July 2012 +Contact: Stefan Achatz <erazor_de@users.sourceforge.net> +Description: The mouse has a Avago ADNS-3090 sensor. + This file allows reading and writing of the mouse sensors registers. + The data has to be 4 bytes long. +Users: http://roccat.sourceforge.net + diff --git a/Documentation/hid/uhid.txt b/Documentation/hid/uhid.txt new file mode 100644 index 00000000000..4627c4241ec --- /dev/null +++ b/Documentation/hid/uhid.txt @@ -0,0 +1,169 @@ + UHID - User-space I/O driver support for HID subsystem + ======================================================== + +The HID subsystem needs two kinds of drivers. In this document we call them: + + 1. The "HID I/O Driver" is the driver that performs raw data I/O to the + low-level device. Internally, they register an hid_ll_driver structure with + the HID core. They perform device setup, read raw data from the device and + push it into the HID subsystem and they provide a callback so the HID + subsystem can send data to the device. + + 2. The "HID Device Driver" is the driver that parses HID reports and reacts on + them. There are generic drivers like "generic-usb" and "generic-bluetooth" + which adhere to the HID specification and provide the standardizes features. + But there may be special drivers and quirks for each non-standard device out + there. Internally, they use the hid_driver structure. + +Historically, the USB stack was the first subsystem to provide an HID I/O +Driver. However, other standards like Bluetooth have adopted the HID specs and +may provide HID I/O Drivers, too. The UHID driver allows to implement HID I/O +Drivers in user-space and feed the data into the kernel HID-subsystem. + +This allows user-space to operate on the same level as USB-HID, Bluetooth-HID +and similar. It does not provide a way to write HID Device Drivers, though. Use +hidraw for this purpose. + +There is an example user-space application in ./samples/uhid/uhid-example.c + +The UHID API +------------ + +UHID is accessed through a character misc-device. The minor-number is allocated +dynamically so you need to rely on udev (or similar) to create the device node. +This is /dev/uhid by default. + +If a new device is detected by your HID I/O Driver and you want to register this +device with the HID subsystem, then you need to open /dev/uhid once for each +device you want to register. All further communication is done by read()'ing or +write()'ing "struct uhid_event" objects. Non-blocking operations are supported +by setting O_NONBLOCK. + +struct uhid_event { + __u32 type; + union { + struct uhid_create_req create; + struct uhid_data_req data; + ... + } u; +}; + +The "type" field contains the ID of the event. Depending on the ID different +payloads are sent. You must not split a single event across multiple read()'s or +multiple write()'s. A single event must always be sent as a whole. Furthermore, +only a single event can be sent per read() or write(). Pending data is ignored. +If you want to handle multiple events in a single syscall, then use vectored +I/O with readv()/writev(). + +The first thing you should do is sending an UHID_CREATE event. This will +register the device. UHID will respond with an UHID_START event. You can now +start sending data to and reading data from UHID. However, unless UHID sends the +UHID_OPEN event, the internally attached HID Device Driver has no user attached. +That is, you might put your device asleep unless you receive the UHID_OPEN +event. If you receive the UHID_OPEN event, you should start I/O. If the last +user closes the HID device, you will receive an UHID_CLOSE event. This may be +followed by an UHID_OPEN event again and so on. There is no need to perform +reference-counting in user-space. That is, you will never receive multiple +UHID_OPEN events without an UHID_CLOSE event. The HID subsystem performs +ref-counting for you. +You may decide to ignore UHID_OPEN/UHID_CLOSE, though. I/O is allowed even +though the device may have no users. + +If you want to send data to the HID subsystem, you send an HID_INPUT event with +your raw data payload. If the kernel wants to send data to the device, you will +read an UHID_OUTPUT or UHID_OUTPUT_EV event. + +If your device disconnects, you should send an UHID_DESTROY event. This will +unregister the device. You can now send UHID_CREATE again to register a new +device. +If you close() the fd, the device is automatically unregistered and destroyed +internally. + +write() +------- +write() allows you to modify the state of the device and feed input data into +the kernel. The following types are supported: UHID_CREATE, UHID_DESTROY and +UHID_INPUT. The kernel will parse the event immediately and if the event ID is +not supported, it will return -EOPNOTSUPP. If the payload is invalid, then +-EINVAL is returned, otherwise, the amount of data that was read is returned and +the request was handled successfully. + + UHID_CREATE: + This creates the internal HID device. No I/O is possible until you send this + event to the kernel. The payload is of type struct uhid_create_req and + contains information about your device. You can start I/O now. + + UHID_DESTROY: + This destroys the internal HID device. No further I/O will be accepted. There + may still be pending messages that you can receive with read() but no further + UHID_INPUT events can be sent to the kernel. + You can create a new device by sending UHID_CREATE again. There is no need to + reopen the character device. + + UHID_INPUT: + You must send UHID_CREATE before sending input to the kernel! This event + contains a data-payload. This is the raw data that you read from your device. + The kernel will parse the HID reports and react on it. + + UHID_FEATURE_ANSWER: + If you receive a UHID_FEATURE request you must answer with this request. You + must copy the "id" field from the request into the answer. Set the "err" field + to 0 if no error occured or to EIO if an I/O error occurred. + If "err" is 0 then you should fill the buffer of the answer with the results + of the feature request and set "size" correspondingly. + +read() +------ +read() will return a queued ouput report. These output reports can be of type +UHID_START, UHID_STOP, UHID_OPEN, UHID_CLOSE, UHID_OUTPUT or UHID_OUTPUT_EV. No +reaction is required to any of them but you should handle them according to your +needs. Only UHID_OUTPUT and UHID_OUTPUT_EV have payloads. + + UHID_START: + This is sent when the HID device is started. Consider this as an answer to + UHID_CREATE. This is always the first event that is sent. + + UHID_STOP: + This is sent when the HID device is stopped. Consider this as an answer to + UHID_DESTROY. + If the kernel HID device driver closes the device manually (that is, you + didn't send UHID_DESTROY) then you should consider this device closed and send + an UHID_DESTROY event. You may want to reregister your device, though. This is + always the last message that is sent to you unless you reopen the device with + UHID_CREATE. + + UHID_OPEN: + This is sent when the HID device is opened. That is, the data that the HID + device provides is read by some other process. You may ignore this event but + it is useful for power-management. As long as you haven't received this event + there is actually no other process that reads your data so there is no need to + send UHID_INPUT events to the kernel. + + UHID_CLOSE: + This is sent when there are no more processes which read the HID data. It is + the counterpart of UHID_OPEN and you may as well ignore this event. + + UHID_OUTPUT: + This is sent if the HID device driver wants to send raw data to the I/O + device. You should read the payload and forward it to the device. The payload + is of type "struct uhid_data_req". + This may be received even though you haven't received UHID_OPEN, yet. + + UHID_OUTPUT_EV: + Same as UHID_OUTPUT but this contains a "struct input_event" as payload. This + is called for force-feedback, LED or similar events which are received through + an input device by the HID subsystem. You should convert this into raw reports + and send them to your device similar to events of type UHID_OUTPUT. + + UHID_FEATURE: + This event is sent if the kernel driver wants to perform a feature request as + described in the HID specs. The report-type and report-number are available in + the payload. + The kernel serializes feature requests so there will never be two in parallel. + However, if you fail to respond with a UHID_FEATURE_ANSWER in a time-span of 5 + seconds, then the requests will be dropped and a new one might be sent. + Therefore, the payload also contains an "id" field that identifies every + request. + +Document by: + David Herrmann <dh.herrmann@googlemail.com> diff --git a/MAINTAINERS b/MAINTAINERS index cda045337a9..04ea1f23756 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6965,6 +6965,13 @@ S: Maintained F: Documentation/filesystems/ufs.txt F: fs/ufs/ +UHID USERSPACE HID IO DRIVER: +M: David Herrmann <dh.herrmann@googlemail.com> +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/uhid.c +F: include/linux/uhid.h + ULTRA-WIDEBAND (UWB) SUBSYSTEM: L: linux-usb@vger.kernel.org S: Orphan diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 3fda8c87f02..fbf49503508 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -53,6 +53,27 @@ config HIDRAW If unsure, say Y. +config UHID + tristate "User-space I/O driver support for HID subsystem" + depends on HID + default n + ---help--- + Say Y here if you want to provide HID I/O Drivers from user-space. + This allows to write I/O drivers in user-space and feed the data from + the device into the kernel. The kernel parses the HID reports, loads the + corresponding HID Device Driver or provides input devices on top of your + user-space device. + + This driver cannot be used to parse HID-reports in user-space and write + special HID-drivers. You should use hidraw for that. + Instead, this driver allows to write the transport-layer driver in + user-space like USB-HID and Bluetooth-HID do in kernel-space. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called uhid. + config HID_GENERIC tristate "Generic HID driver" depends on HID @@ -193,10 +214,12 @@ config HID_EZKEY Support for Ezkey BTC 8193 keyboard. config HID_HOLTEK - tristate "Holtek On Line Grip based game controller support" + tristate "Holtek HID devices" depends on USB_HID ---help--- - Say Y here if you have a Holtek On Line Grip based game controller. + Support for Holtek based devices: + - Holtek On Line Grip based game controller + - Trust GXT 18 Gaming Keyboard config HOLTEK_FF bool "Holtek On Line Grip force feedback support" @@ -261,6 +284,19 @@ config HID_LCPOWER ---help--- Support for LC-Power RC1000MCE RF remote control. +config HID_LENOVO_TPKBD + tristate "Lenovo ThinkPad USB Keyboard with TrackPoint" + depends on USB_HID + select NEW_LEDS + select LEDS_CLASS + ---help--- + Support for the Lenovo ThinkPad USB Keyboard with TrackPoint. + + Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint + and would like to use device-specific features like changing the + sensitivity of the trackpoint, using the microphone mute button or + controlling the mute and microphone mute LEDs. + config HID_LOGITECH tristate "Logitech devices" if EXPERT depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index ca6cc9f0485..f975485f88b 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -8,6 +8,7 @@ ifdef CONFIG_DEBUG_FS endif obj-$(CONFIG_HID) += hid.o +obj-$(CONFIG_UHID) += uhid.o obj-$(CONFIG_HID_GENERIC) += hid-generic.o @@ -48,12 +49,14 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o obj-$(CONFIG_HID_ELECOM) += hid-elecom.o obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o +obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o obj-$(CONFIG_HID_KYE) += hid-kye.o obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o +obj-$(CONFIG_HID_LENOVO_TPKBD) += hid-lenovo-tpkbd.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o @@ -69,7 +72,8 @@ obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \ - hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o + hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o \ + hid-roccat-savu.o obj-$(CONFIG_HID_SAITEK) += hid-saitek.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o diff --git a/drivers/hid/hid-chicony.c b/drivers/hid/hid-chicony.c index b99af346fdf..a2abb8e1572 100644 --- a/drivers/hid/hid-chicony.c +++ b/drivers/hid/hid-chicony.c @@ -60,6 +60,7 @@ static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi, static const struct hid_device_id ch_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) }, { } }; MODULE_DEVICE_TABLE(hid, ch_devices); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 4c87276c8dd..500844f04f9 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1194,8 +1194,10 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size, goto out; } - for (a = 0; a < report->maxfield; a++) - hid_input_field(hid, report->field[a], cdata, interrupt); + if (hid->claimed != HID_CLAIMED_HIDRAW) { + for (a = 0; a < report->maxfield; a++) + hid_input_field(hid, report->field[a], cdata, interrupt); + } if (hid->claimed & HID_CLAIMED_INPUT) hidinput_report_event(hid, report); @@ -1243,6 +1245,10 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i goto unlock; } + /* Avoid unnecessary overhead if debugfs is disabled */ + if (list_empty(&hid->debug_list)) + goto nomem; + buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC); if (!buf) @@ -1373,8 +1379,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev)) hdev->claimed |= HID_CLAIMED_HIDRAW; - if (!hdev->claimed) { - hid_err(hdev, "claimed by neither input, hiddev nor hidraw\n"); + /* Drivers with the ->raw_event callback set are not required to connect + * to any other listener. */ + if (!hdev->claimed && !hdev->driver->raw_event) { + hid_err(hdev, "device has no listeners, quitting\n"); return -ENODEV; } @@ -1521,10 +1529,12 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) }, { HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) }, { 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_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) }, + { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4) }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) }, { HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) }, @@ -1539,6 +1549,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) }, { HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) }, { HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) }, @@ -1547,6 +1558,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) }, { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) }, @@ -1620,6 +1632,7 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) }, diff --git a/drivers/hid/hid-cypress.c b/drivers/hid/hid-cypress.c index 2f0be4c66af..9e43aaca977 100644 --- a/drivers/hid/hid-cypress.c +++ b/drivers/hid/hid-cypress.c @@ -129,6 +129,8 @@ static const struct hid_device_id cp_devices[] = { .driver_data = CP_RDESC_SWAPPED_MIN_MAX }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3), .driver_data = CP_RDESC_SWAPPED_MIN_MAX }, + { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4), + .driver_data = CP_RDESC_SWAPPED_MIN_MAX }, { HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE), .driver_data = CP_2WHEEL_MOUSE_HACK }, { } diff --git a/drivers/hid/hid-holtek-kbd.c b/drivers/hid/hid-holtek-kbd.c new file mode 100644 index 00000000000..e0a5d1739fc --- /dev/null +++ b/drivers/hid/hid-holtek-kbd.c @@ -0,0 +1,183 @@ +/* + * HID driver for Holtek keyboard + * Copyright (c) 2012 Tom Harwood +*/ + +/* + * 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/device.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/usb.h> + +#include "hid-ids.h" +#include "usbhid/usbhid.h" + +/* Holtek based keyboards (USB ID 04d9:a055) have the following issues: + * - The report descriptor specifies an excessively large number of consumer + * usages (2^15), which is more than HID_MAX_USAGES. This prevents proper + * parsing of the report descriptor. + * - The report descriptor reports on caps/scroll/num lock key presses, but + * doesn't have an LED output usage block. + * + * The replacement descriptor below fixes the number of consumer usages, + * and provides an LED output usage block. LED output events are redirected + * to the boot interface. + */ + +static __u8 holtek_kbd_rdesc_fixed[] = { + /* Original report descriptor, with reduced number of consumer usages */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x80, /* Usage (Sys Control), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x01, /* Report ID (1), */ + 0x19, 0x81, /* Usage Minimum (Sys Power Down), */ + 0x29, 0x83, /* Usage Maximum (Sys Wake Up), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x75, 0x01, /* Report Size (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x05, /* Report Size (5), */ + 0x81, 0x01, /* Input (Constant), */ + 0xC0, /* End Collection, */ + 0x05, 0x0C, /* Usage Page (Consumer), */ + 0x09, 0x01, /* Usage (Consumer Control), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x02, /* Report ID (2), */ + 0x19, 0x00, /* Usage Minimum (00h), */ + 0x2A, 0xFF, 0x2F, /* Usage Maximum (0x2FFF), previously 0x7FFF */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x26, 0xFF, 0x2F, /* Logical Maximum (0x2FFF),previously 0x7FFF*/ + 0x95, 0x01, /* Report Count (1), */ + 0x75, 0x10, /* Report Size (16), */ + 0x81, 0x00, /* Input, */ + 0xC0, /* End Collection, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x06, /* Usage (Keyboard), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x03, /* Report ID (3), */ + 0x95, 0x38, /* Report Count (56), */ + 0x75, 0x01, /* Report Size (1), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x05, 0x07, /* Usage Page (Keyboard), */ + 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ + 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ + 0x19, 0x00, /* Usage Minimum (None), */ + 0x29, 0x2F, /* Usage Maximum (KB Lboxbracket And Lbrace),*/ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x06, /* Usage (Keyboard), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x04, /* Report ID (4), */ + 0x95, 0x38, /* Report Count (56), */ + 0x75, 0x01, /* Report Size (1), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x05, 0x07, /* Usage Page (Keyboard), */ + 0x19, 0x30, /* Usage Minimum (KB Rboxbracket And Rbrace),*/ + 0x29, 0x67, /* Usage Maximum (KP Equals), */ + 0x81, 0x02, /* Input (Variable), */ + 0xC0, /* End Collection */ + + /* LED usage for the boot protocol interface */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x09, 0x06, /* Usage (Keyboard), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x05, 0x08, /* Usage Page (LED), */ + 0x19, 0x01, /* Usage Minimum (01h), */ + 0x29, 0x03, /* Usage Maximum (03h), */ + 0x15, 0x00, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x95, 0x03, /* Report Count (3), */ + 0x91, 0x02, /* Output (Variable), */ + 0x95, 0x05, /* Report Count (5), */ + 0x91, 0x01, /* Output (Constant), */ + 0xC0, /* End Collection */ +}; + +static __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + + if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + rdesc = holtek_kbd_rdesc_fixed; + *rsize = sizeof(holtek_kbd_rdesc_fixed); + } + return rdesc; +} + +static int holtek_kbd_input_event(struct input_dev *dev, unsigned int type, + unsigned int code, + int value) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct usb_device *usb_dev = hid_to_usb_dev(hid); + + /* Locate the boot interface, to receive the LED change events */ + struct usb_interface *boot_interface = usb_ifnum_to_if(usb_dev, 0); + + struct hid_device *boot_hid = usb_get_intfdata(boot_interface); + struct hid_input *boot_hid_input = list_first_entry(&boot_hid->inputs, + struct hid_input, list); + + return boot_hid_input->input->event(boot_hid_input->input, type, code, + value); +} + +static int holtek_kbd_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + int ret = hid_parse(hdev); + + if (!ret) + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + + if (!ret && intf->cur_altsetting->desc.bInterfaceNumber == 1) { + struct hid_input *hidinput; + list_for_each_entry(hidinput, &hdev->inputs, list) { + hidinput->input->event = holtek_kbd_input_event; + } + } + + return ret; +} + +static const struct hid_device_id holtek_kbd_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, + USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) }, + { } +}; +MODULE_DEVICE_TABLE(hid, holtek_kbd_devices); + +static struct hid_driver holtek_kbd_driver = { + .name = "holtek_kbd", + .id_table = holtek_kbd_devices, + .report_fixup = holtek_kbd_report_fixup, + .probe = holtek_kbd_probe +}; + +static int __init holtek_kbd_init(void) +{ + return hid_register_driver(&holtek_kbd_driver); +} + +static void __exit holtek_kbd_exit(void) +{ + hid_unregister_driver(&holtek_kbd_driver); +} + +module_exit(holtek_kbd_exit); +module_init(holtek_kbd_init); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 32039235cfe..41c34f21bd0 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -208,6 +208,7 @@ #define USB_DEVICE_ID_CHICONY_MULTI_TOUCH 0xb19d #define USB_DEVICE_ID_CHICONY_WIRELESS 0x0618 #define USB_DEVICE_ID_CHICONY_WIRELESS2 0x1123 +#define USB_DEVICE_ID_CHICONY_AK1D 0x1125 #define USB_VENDOR_ID_CHUNGHWAT 0x2247 #define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001 @@ -237,6 +238,7 @@ #define USB_DEVICE_ID_CYPRESS_BARCODE_1 0xde61 #define USB_DEVICE_ID_CYPRESS_BARCODE_2 0xde64 #define USB_DEVICE_ID_CYPRESS_BARCODE_3 0xbca1 +#define USB_DEVICE_ID_CYPRESS_BARCODE_4 0xed81 #define USB_DEVICE_ID_CYPRESS_TRUETOUCH 0xc001 #define USB_VENDOR_ID_DEALEXTREAME 0x10c5 @@ -410,6 +412,9 @@ #define USB_VENDOR_ID_HOLTEK 0x1241 #define USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP 0x5015 +#define USB_VENDOR_ID_HOLTEK_ALT 0x04d9 +#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD 0xa055 + #define USB_VENDOR_ID_IMATION 0x0718 #define USB_DEVICE_ID_DISC_STAKKA 0xd000 @@ -479,6 +484,9 @@ #define USB_DEVICE_ID_LD_HYBRID 0x2090 #define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0 +#define USB_VENDOR_ID_LENOVO 0x17ef +#define USB_DEVICE_ID_LENOVO_TPKBD 0x6009 + #define USB_VENDOR_ID_LG 0x1fd2 #define USB_DEVICE_ID_LG_MULTITOUCH 0x0064 @@ -573,6 +581,9 @@ #define USB_VENDOR_ID_NINTENDO 0x057e #define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306 +#define USB_VENDOR_ID_NOVATEK 0x0603 +#define USB_DEVICE_ID_NOVATEK_PCT 0x0600 + #define USB_VENDOR_ID_NTRIG 0x1b96 #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN 0x0001 #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1 0x0003 @@ -650,6 +661,7 @@ #define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50 #define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24 #define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6 +#define USB_DEVICE_ID_ROCCAT_SAVU 0x2d5a #define USB_VENDOR_ID_SAITEK 0x06a3 #define USB_DEVICE_ID_SAITEK_RUMBLEPAD 0xff17 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 5301006f6c1..811bfad6460 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -837,6 +837,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel } break; + case HID_UP_HPVENDOR2: + set_bit(EV_REP, input->evbit); + switch (usage->hid & HID_USAGE) { + case 0x003: map_key_clear(KEY_BRIGHTNESSDOWN); break; + case 0x004: map_key_clear(KEY_BRIGHTNESSUP); break; + default: goto ignore; + } + break; + case HID_UP_MSVENDOR: goto ignore; diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c new file mode 100644 index 00000000000..77d2df04c97 --- /dev/null +++ b/drivers/hid/hid-lenovo-tpkbd.c @@ -0,0 +1,564 @@ +/* + * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint + * + * Copyright (c) 2012 Bernhard Seibold + */ + +/* + * 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/module.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/usb.h> +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/leds.h> +#include "usbhid/usbhid.h" + +#include "hid-ids.h" + +/* This is only used for the trackpoint part of the driver, hence _tp */ +struct tpkbd_data_pointer { + int led_state; + struct led_classdev led_mute; + struct led_classdev led_micmute; + int press_to_select; + int dragging; + int release_to_select; + int select_right; + int sensitivity; + int press_speed; +}; + +#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) + +static int tpkbd_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + struct usbhid_device *uhdev; + + uhdev = (struct usbhid_device *) hdev->driver_data; + if (uhdev->ifnum == 1 && usage->hid == (HID_UP_BUTTON | 0x0010)) { + map_key_clear(KEY_MICMUTE); + return 1; + } + return 0; +} + +#undef map_key_clear + +static int tpkbd_features_set(struct hid_device *hdev) +{ + struct hid_report *report; + struct tpkbd_data_pointer *data_pointer; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; + + report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; + report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; + report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; + report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; + report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver + report->field[2]->value[0] = data_pointer->sensitivity; + report->field[3]->value[0] = data_pointer->press_speed; + + usbhid_submit_report(hdev, report, USB_DIR_OUT); + return 0; +} + +static ssize_t pointer_press_to_select_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); +} + +static ssize_t pointer_press_to_select_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int value; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (kstrtoint(buf, 10, &value)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + data_pointer->press_to_select = value; + tpkbd_features_set(hdev); + + return count; +} + +static ssize_t pointer_dragging_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); +} + +static ssize_t pointer_dragging_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int value; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (kstrtoint(buf, 10, &value)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + data_pointer->dragging = value; + tpkbd_features_set(hdev); + + return count; +} + +static ssize_t pointer_release_to_select_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); +} + +static ssize_t pointer_release_to_select_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int value; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (kstrtoint(buf, 10, &value)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + data_pointer->release_to_select = value; + tpkbd_features_set(hdev); + + return count; +} + +static ssize_t pointer_select_right_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); +} + +static ssize_t pointer_select_right_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int value; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (kstrtoint(buf, 10, &value)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + data_pointer->select_right = value; + tpkbd_features_set(hdev); + + return count; +} + +static ssize_t pointer_sensitivity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + data_pointer->sensitivity); +} + +static ssize_t pointer_sensitivity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int value; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) + return -EINVAL; + + data_pointer->sensitivity = value; + tpkbd_features_set(hdev); + + return count; +} + +static ssize_t pointer_press_speed_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + return snprintf(buf, PAGE_SIZE, "%u\n", + data_pointer->press_speed); +} + +static ssize_t pointer_press_speed_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int value; + + hdev = container_of(dev, struct hid_device, dev); + if (hdev == NULL) + return -ENODEV; + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) + return -EINVAL; + + data_pointer->press_speed = value; + tpkbd_features_set(hdev); + + return count; +} + +static struct device_attribute dev_attr_pointer_press_to_select = + __ATTR(press_to_select, S_IWUSR | S_IRUGO, + pointer_press_to_select_show, + pointer_press_to_select_store); + +static struct device_attribute dev_attr_pointer_dragging = + __ATTR(dragging, S_IWUSR | S_IRUGO, + pointer_dragging_show, + pointer_dragging_store); + +static struct device_attribute dev_attr_pointer_release_to_select = + __ATTR(release_to_select, S_IWUSR | S_IRUGO, + pointer_release_to_select_show, + pointer_release_to_select_store); + +static struct device_attribute dev_attr_pointer_select_right = + __ATTR(select_right, S_IWUSR | S_IRUGO, + pointer_select_right_show, + pointer_select_right_store); + +static struct device_attribute dev_attr_pointer_sensitivity = + __ATTR(sensitivity, S_IWUSR | S_IRUGO, + pointer_sensitivity_show, + pointer_sensitivity_store); + +static struct device_attribute dev_attr_pointer_press_speed = + __ATTR(press_speed, S_IWUSR | S_IRUGO, + pointer_press_speed_show, + pointer_press_speed_store); + +static struct attribute *tpkbd_attributes_pointer[] = { + &dev_attr_pointer_press_to_select.attr, + &dev_attr_pointer_dragging.attr, + &dev_attr_pointer_release_to_select.attr, + &dev_attr_pointer_select_right.attr, + &dev_attr_pointer_sensitivity.attr, + &dev_attr_pointer_press_speed.attr, + NULL +}; + +static const struct attribute_group tpkbd_attr_group_pointer = { + .attrs = tpkbd_attributes_pointer, +}; + +static enum led_brightness tpkbd_led_brightness_get( + struct led_classdev *led_cdev) +{ + struct device *dev; + struct hid_device *hdev; + struct tpkbd_data_pointer *data_pointer; + int led_nr = 0; + + dev = led_cdev->dev->parent; + hdev = container_of(dev, struct hid_device, dev); + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (led_cdev == &data_pointer->led_micmute) + led_nr = 1; + + return data_pointer->led_state & (1 << led_nr) + ? LED_FULL + : LED_OFF; +} + +static void tpkbd_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct device *dev; + struct hid_device *hdev; + struct hid_report *report; + struct tpkbd_data_pointer *data_pointer; + int led_nr = 0; + + dev = led_cdev->dev->parent; + hdev = container_of(dev, struct hid_device, dev); + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + if (led_cdev == &data_pointer->led_micmute) + led_nr = 1; + + if (value == LED_OFF) + data_pointer->led_state &= ~(1 << led_nr); + else + data_pointer->led_state |= 1 << led_nr; + + report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; + report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; + report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; + usbhid_submit_report(hdev, report, USB_DIR_OUT); +} + +static int tpkbd_probe_tp(struct hid_device *hdev) +{ + struct device *dev = &hdev->dev; + struct tpkbd_data_pointer *data_pointer; + size_t name_sz = strlen(dev_name(dev)) + 16; + char *name_mute, *name_micmute; + int ret; + + if (sysfs_create_group(&hdev->dev.kobj, + &tpkbd_attr_group_pointer)) { + hid_warn(hdev, "Could not create sysfs group\n"); + } + + data_pointer = kzalloc(sizeof(struct tpkbd_data_pointer), GFP_KERNEL); + if (data_pointer == NULL) { + hid_err(hdev, "Could not allocate memory for driver data\n"); + return -ENOMEM; + } + + // set same default values as windows driver + data_pointer->sensitivity = 0xa0; + data_pointer->press_speed = 0x38; + + name_mute = kzalloc(name_sz, GFP_KERNEL); + if (name_mute == NULL) { + hid_err(hdev, "Could not allocate memory for led data\n"); + ret = -ENOMEM; + goto err; + } + snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); + + name_micmute = kzalloc(name_sz, GFP_KERNEL); + if (name_micmute == NULL) { + hid_err(hdev, "Could not allocate memory for led data\n"); + ret = -ENOMEM; + goto err2; + } + snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); + + hid_set_drvdata(hdev, data_pointer); + + data_pointer->led_mute.name = name_mute; + data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get; + data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set; + data_pointer->led_mute.dev = dev; + led_classdev_register(dev, &data_pointer->led_mute); + + data_pointer->led_micmute.name = name_micmute; + data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get; + data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set; + data_pointer->led_micmute.dev = dev; + led_classdev_register(dev, &data_pointer->led_micmute); + + tpkbd_features_set(hdev); + + return 0; + +err2: + kfree(name_mute); +err: + kfree(data_pointer); + return ret; +} + +static int tpkbd_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + struct usbhid_device *uhdev; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "hid_parse failed\n"); + goto err_free; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) { + hid_err(hdev, "hid_hw_start failed\n"); + goto err_free; + } + + uhdev = (struct usbhid_device *) hdev->driver_data; + + if (uhdev->ifnum == 1) + return tpkbd_probe_tp(hdev); + + return 0; +err_free: + return ret; +} + +static void tpkbd_remove_tp(struct hid_device *hdev) +{ + struct tpkbd_data_pointer *data_pointer; + + sysfs_remove_group(&hdev->dev.kobj, + &tpkbd_attr_group_pointer); + + data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); + + led_classdev_unregister(&data_pointer->led_micmute); + led_classdev_unregister(&data_pointer->led_mute); + + hid_set_drvdata(hdev, NULL); + kfree(data_pointer); +} + +static void tpkbd_remove(struct hid_device *hdev) +{ + struct usbhid_device *uhdev; + + uhdev = (struct usbhid_device *) hdev->driver_data; + if (uhdev->ifnum == 1) + tpkbd_remove_tp(hdev); + + hid_hw_stop(hdev); +} + +static const struct hid_device_id tpkbd_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, tpkbd_devices); + +static struct hid_driver tpkbd_driver = { + .name = "lenovo_tpkbd", + .id_table = tpkbd_devices, + .input_mapping = tpkbd_input_mapping, + .probe = tpkbd_probe, + .remove = tpkbd_remove, +}; + +static int __init tpkbd_init(void) +{ + return hid_register_driver(&tpkbd_driver); +} + +static void __exit tpkbd_exit(void) +{ + hid_unregister_driver(&tpkbd_driver); +} + +module_init(tpkbd_init); +module_exit(tpkbd_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index 40ac6654f1d..73647266daa 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -16,6 +16,7 @@ #include <linux/device.h> #include <linux/hid.h> +#include <linux/input/mt.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/usb.h> @@ -48,10 +49,6 @@ static bool scroll_acceleration = false; module_param(scroll_acceleration, bool, 0644); MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events"); -static bool report_touches = true; -module_param(report_touches, bool, 0644); -MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)"); - static bool report_undeciphered; module_param(report_undeciphered, bool, 0644); MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event"); @@ -72,15 +69,6 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie #define SCROLL_ACCEL_DEFAULT 7 -/* Single touch emulation should only begin when no touches are currently down. - * This is true when single_touch_id is equal to NO_TOUCHES. If multiple touches - * are down and the touch providing for single touch emulation is lifted, - * single_touch_id is equal to SINGLE_TOUCH_UP. While single touch emulation is - * occurring, single_touch_id corresponds with the tracking id of the touch used. - */ -#define NO_TOUCHES -1 -#define SINGLE_TOUCH_UP -2 - /* Touch surface information. Dimension is in hundredths of a mm, min and max * are in units. */ #define MOUSE_DIMENSION_X (float)9056 @@ -129,7 +117,6 @@ struct magicmouse_sc { u8 size; } touches[16]; int tracking_ids[16]; - int single_touch_id; }; static int magicmouse_firm_touch(struct magicmouse_sc *msc) @@ -268,16 +255,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda } } - if (down) { + if (down) msc->ntouches++; - if (msc->single_touch_id == NO_TOUCHES) - msc->single_touch_id = id; - } else if (msc->single_touch_id == id) - msc->single_touch_id = SINGLE_TOUCH_UP; + + input_mt_slot(input, id); + input_mt_report_slot_state(input, MT_TOOL_FINGER, down); /* Generate the input events for this touch. */ - if (report_touches && down) { - input_report_abs(input, ABS_MT_TRACKING_ID, id); + if (down) { input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major << 2); input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor << 2); input_report_abs(input, ABS_MT_ORIENTATION, -orientation); @@ -290,8 +275,6 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ input_event(input, EV_MSC, MSC_RAW, tdata[8]); } - - input_mt_sync(input); } } @@ -312,12 +295,6 @@ static int magicmouse_raw_event(struct hid_device *hdev, for (ii = 0; ii < npoints; ii++) magicmouse_emit_touch(msc, ii, data + ii * 9 + 4); - /* We don't need an MT sync here because trackpad emits a - * BTN_TOUCH event in a new frame when all touches are released. - */ - if (msc->ntouches == 0) - msc->single_touch_id = NO_TOUCHES; - clicks = data[1]; /* The following bits provide a device specific timestamp. They @@ -335,9 +312,6 @@ static int magicmouse_raw_event(struct hid_device *hdev, for (ii = 0; ii < npoints; ii++) magicmouse_emit_touch(msc, ii, data + ii * 8 + 6); - if (report_touches && msc->ntouches == 0) - input_mt_sync(input); - /* When emulating three-button mode, it is important * to have the current touch information before * generating a click event. @@ -370,25 +344,17 @@ static int magicmouse_raw_event(struct hid_device *hdev, input_report_rel(input, REL_Y, y); } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ input_report_key(input, BTN_MOUSE, clicks & 1); - input_report_key(input, BTN_TOUCH, msc->ntouches > 0); - input_report_key(input, BTN_TOOL_FINGER, msc->ntouches == 1); - input_report_key(input, BTN_TOOL_DOUBLETAP, msc->ntouches == 2); - input_report_key(input, BTN_TOOL_TRIPLETAP, msc->ntouches == 3); - input_report_key(input, BTN_TOOL_QUADTAP, msc->ntouches == 4); - if (msc->single_touch_id >= 0) { - input_report_abs(input, ABS_X, - msc->touches[msc->single_touch_id].x); - input_report_abs(input, ABS_Y, - msc->touches[msc->single_touch_id].y); - } + input_mt_report_pointer_emulation(input, true); } input_sync(input); return 1; } -static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) +static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev) { + int error; + __set_bit(EV_KEY, input->evbit); if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { @@ -417,62 +383,66 @@ static void magicmouse_setup_input(struct input_dev *input, struct hid_device *h __set_bit(BTN_TOOL_DOUBLETAP, input->keybit); __set_bit(BTN_TOOL_TRIPLETAP, input->keybit); __set_bit(BTN_TOOL_QUADTAP, input->keybit); + __set_bit(BTN_TOOL_QUINTTAP, input->keybit); __set_bit(BTN_TOUCH, input->keybit); __set_bit(INPUT_PROP_POINTER, input->propbit); __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); } - if (report_touches) { - __set_bit(EV_ABS, input->evbit); - - input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 15, 0, 0); - input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2, - 4, 0); - input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2, - 4, 0); - input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0); - - /* Note: Touch Y position from the device is inverted relative - * to how pointer motion is reported (and relative to how USB - * HID recommends the coordinates work). This driver keeps - * the origin at the same position, and just uses the additive - * inverse of the reported Y. - */ - if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { - input_set_abs_params(input, ABS_MT_POSITION_X, - MOUSE_MIN_X, MOUSE_MAX_X, 4, 0); - input_set_abs_params(input, ABS_MT_POSITION_Y, - MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0); - - input_abs_set_res(input, ABS_MT_POSITION_X, - MOUSE_RES_X); - input_abs_set_res(input, ABS_MT_POSITION_Y, - MOUSE_RES_Y); - } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ - input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X, - TRACKPAD_MAX_X, 4, 0); - input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y, - TRACKPAD_MAX_Y, 4, 0); - input_set_abs_params(input, ABS_MT_POSITION_X, - TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0); - input_set_abs_params(input, ABS_MT_POSITION_Y, - TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0); - - input_abs_set_res(input, ABS_X, TRACKPAD_RES_X); - input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y); - input_abs_set_res(input, ABS_MT_POSITION_X, - TRACKPAD_RES_X); - input_abs_set_res(input, ABS_MT_POSITION_Y, - TRACKPAD_RES_Y); - } - input_set_events_per_packet(input, 60); + __set_bit(EV_ABS, input->evbit); + + error = input_mt_init_slots(input, 16); + if (error) + return error; + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2, + 4, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2, + 4, 0); + input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0); + + /* Note: Touch Y position from the device is inverted relative + * to how pointer motion is reported (and relative to how USB + * HID recommends the coordinates work). This driver keeps + * the origin at the same position, and just uses the additive + * inverse of the reported Y. + */ + if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) { + input_set_abs_params(input, ABS_MT_POSITION_X, + MOUSE_MIN_X, MOUSE_MAX_X, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, + MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0); + + input_abs_set_res(input, ABS_MT_POSITION_X, + MOUSE_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, + MOUSE_RES_Y); + } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */ + input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X, + TRACKPAD_MAX_X, 4, 0); + input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y, + TRACKPAD_MAX_Y, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_X, + TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, + TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0); + + input_abs_set_res(input, ABS_X, TRACKPAD_RES_X); + input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y); + input_abs_set_res(input, ABS_MT_POSITION_X, + TRACKPAD_RES_X); + input_abs_set_res(input, ABS_MT_POSITION_Y, + TRACKPAD_RES_Y); } + input_set_events_per_packet(input, 60); + if (report_undeciphered) { __set_bit(EV_MSC, input->evbit); __set_bit(MSC_RAW, input->mscbit); } + + return 0; } static int magicmouse_input_mapping(struct hid_device *hdev, @@ -511,8 +481,6 @@ static int magicmouse_probe(struct hid_device *hdev, msc->quirks = id->driver_data; hid_set_drvdata(hdev, msc); - msc->single_touch_id = NO_TOUCHES; - ret = hid_parse(hdev); if (ret) { hid_err(hdev, "magicmouse hid parse failed\n"); @@ -528,8 +496,13 @@ static int magicmouse_probe(struct hid_device *hdev, /* We do this after hid-input is done parsing reports so that * hid-input uses the most natural button and axis IDs. */ - if (msc->input) - magicmouse_setup_input(msc->input, hdev); + if (msc->input) { + ret = magicmouse_setup_input(msc->input, hdev); + if (ret) { + hid_err(hdev, "magicmouse setup input failed (%d)\n", ret); + goto err_stop_hw; + } + } if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE) report = hid_register_report(hdev, HID_INPUT_REPORT, diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 76479246d4e..59c8b5c1d2d 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -83,6 +83,7 @@ struct mt_device { unsigned last_field_index; /* last field index of the report */ unsigned last_slot_field; /* the last field of a slot */ __s8 inputmode; /* InputMode HID feature, -1 if non-existent */ + __s8 inputmode_index; /* InputMode HID feature index in the report */ __s8 maxcontact_report_id; /* Maximum Contact Number HID feature, -1 if non-existent */ __u8 num_received; /* how many contacts we received */ @@ -260,10 +261,20 @@ static void mt_feature_mapping(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage) { struct mt_device *td = hid_get_drvdata(hdev); + int i; switch (usage->hid) { case HID_DG_INPUTMODE: td->inputmode = field->report->id; + td->inputmode_index = 0; /* has to be updated below */ + + for (i=0; i < field->maxusage; i++) { + if (field->usage[i].hid == usage->hid) { + td->inputmode_index = i; + break; + } + } + break; case HID_DG_CONTACTMAX: td->maxcontact_report_id = field->report->id; @@ -618,7 +629,7 @@ static void mt_set_input_mode(struct hid_device *hdev) re = &(hdev->report_enum[HID_FEATURE_REPORT]); r = re->report_id_hash[td->inputmode]; if (r) { - r->field[0]->value[0] = 0x02; + r->field[0]->value[td->inputmode_index] = 0x02; usbhid_submit_report(hdev, r, USB_DIR_OUT); } } @@ -951,6 +962,11 @@ static const struct hid_device_id mt_devices[] = { MT_USB_DEVICE(USB_VENDOR_ID_PANASONIC, USB_DEVICE_ID_PANABOARD_UBT880) }, + /* Novatek Panel */ + { .driver_data = MT_CLS_DEFAULT, + MT_USB_DEVICE(USB_VENDOR_ID_NOVATEK, + USB_DEVICE_ID_NOVATEK_PCT) }, + /* PenMount panels */ { .driver_data = MT_CLS_CONFIDENCE, MT_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c index 45c3433f798..27c8ebdfad0 100644 --- a/drivers/hid/hid-picolcd.c +++ b/drivers/hid/hid-picolcd.c @@ -1846,7 +1846,7 @@ static void picolcd_debug_out_report(struct picolcd_data *data, #define BUFF_SZ 256 /* Avoid unnecessary overhead if debugfs is disabled */ - if (!hdev->debug_events) + if (list_empty(&hdev->debug_list)) return; buff = kmalloc(BUFF_SZ, GFP_ATOMIC); @@ -2613,11 +2613,7 @@ static int picolcd_probe(struct hid_device *hdev, goto err_cleanup_data; } - /* We don't use hidinput but hid_hw_start() fails if nothing is - * claimed. So spoof claimed input. */ - hdev->claimed = HID_CLAIMED_INPUT; error = hid_hw_start(hdev, 0); - hdev->claimed = 0; if (error) { hid_err(hdev, "hardware start failed\n"); goto err_cleanup_data; diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c index 093bfad00b0..327f9b8ed1f 100644 --- a/drivers/hid/hid-roccat-arvo.c +++ b/drivers/hid/hid-roccat-arvo.c @@ -39,7 +39,7 @@ static ssize_t arvo_sysfs_show_mode_key(struct device *dev, int retval; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_receive(usb_dev, ARVO_COMMAND_MODE_KEY, + retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_MODE_KEY, &temp_buf, sizeof(struct arvo_mode_key)); mutex_unlock(&arvo->arvo_lock); if (retval) @@ -67,7 +67,7 @@ static ssize_t arvo_sysfs_set_mode_key(struct device *dev, temp_buf.state = state; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_send(usb_dev, ARVO_COMMAND_MODE_KEY, + retval = roccat_common2_send(usb_dev, ARVO_COMMAND_MODE_KEY, &temp_buf, sizeof(struct arvo_mode_key)); mutex_unlock(&arvo->arvo_lock); if (retval) @@ -87,7 +87,7 @@ static ssize_t arvo_sysfs_show_key_mask(struct device *dev, int retval; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_receive(usb_dev, ARVO_COMMAND_KEY_MASK, + retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_KEY_MASK, &temp_buf, sizeof(struct arvo_key_mask)); mutex_unlock(&arvo->arvo_lock); if (retval) @@ -115,7 +115,7 @@ static ssize_t arvo_sysfs_set_key_mask(struct device *dev, temp_buf.key_mask = key_mask; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_send(usb_dev, ARVO_COMMAND_KEY_MASK, + retval = roccat_common2_send(usb_dev, ARVO_COMMAND_KEY_MASK, &temp_buf, sizeof(struct arvo_key_mask)); mutex_unlock(&arvo->arvo_lock); if (retval) @@ -130,7 +130,7 @@ static int arvo_get_actual_profile(struct usb_device *usb_dev) struct arvo_actual_profile temp_buf; int retval; - retval = roccat_common_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE, + retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE, &temp_buf, sizeof(struct arvo_actual_profile)); if (retval) @@ -170,7 +170,7 @@ static ssize_t arvo_sysfs_set_actual_profile(struct device *dev, temp_buf.actual_profile = profile; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE, + retval = roccat_common2_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE, &temp_buf, sizeof(struct arvo_actual_profile)); if (!retval) { arvo->actual_profile = profile; @@ -194,7 +194,7 @@ static ssize_t arvo_sysfs_write(struct file *fp, return -EINVAL; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_send(usb_dev, command, buf, real_size); + retval = roccat_common2_send(usb_dev, command, buf, real_size); mutex_unlock(&arvo->arvo_lock); return (retval ? retval : real_size); @@ -217,7 +217,7 @@ static ssize_t arvo_sysfs_read(struct file *fp, return -EINVAL; mutex_lock(&arvo->arvo_lock); - retval = roccat_common_receive(usb_dev, command, buf, real_size); + retval = roccat_common2_receive(usb_dev, command, buf, real_size); mutex_unlock(&arvo->arvo_lock); return (retval ? retval : real_size); diff --git a/drivers/hid/hid-roccat-common.c b/drivers/hid/hid-roccat-common.c index a6d93992c75..74f70403262 100644 --- a/drivers/hid/hid-roccat-common.c +++ b/drivers/hid/hid-roccat-common.c @@ -16,12 +16,12 @@ #include <linux/module.h> #include "hid-roccat-common.h" -static inline uint16_t roccat_common_feature_report(uint8_t report_id) +static inline uint16_t roccat_common2_feature_report(uint8_t report_id) { return 0x300 | report_id; } -int roccat_common_receive(struct usb_device *usb_dev, uint report_id, +int roccat_common2_receive(struct usb_device *usb_dev, uint report_id, void *data, uint size) { char *buf; @@ -34,16 +34,16 @@ int roccat_common_receive(struct usb_device *usb_dev, uint report_id, len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), HID_REQ_GET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - roccat_common_feature_report(report_id), + roccat_common2_feature_report(report_id), 0, buf, size, USB_CTRL_SET_TIMEOUT); memcpy(data, buf, size); kfree(buf); return ((len < 0) ? len : ((len != size) ? -EIO : 0)); } -EXPORT_SYMBOL_GPL(roccat_common_receive); +EXPORT_SYMBOL_GPL(roccat_common2_receive); -int roccat_common_send(struct usb_device *usb_dev, uint report_id, +int roccat_common2_send(struct usb_device *usb_dev, uint report_id, void const *data, uint size) { char *buf; @@ -56,13 +56,71 @@ int roccat_common_send(struct usb_device *usb_dev, uint report_id, len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), HID_REQ_SET_REPORT, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - roccat_common_feature_report(report_id), + roccat_common2_feature_report(report_id), 0, buf, size, USB_CTRL_SET_TIMEOUT); kfree(buf); return ((len < 0) ? len : ((len != size) ? -EIO : 0)); } -EXPORT_SYMBOL_GPL(roccat_common_send); +EXPORT_SYMBOL_GPL(roccat_common2_send); + +enum roccat_common2_control_states { + ROCCAT_COMMON_CONTROL_STATUS_OVERLOAD = 0, + ROCCAT_COMMON_CONTROL_STATUS_OK = 1, + ROCCAT_COMMON_CONTROL_STATUS_INVALID = 2, + ROCCAT_COMMON_CONTROL_STATUS_WAIT = 3, +}; + +static int roccat_common2_receive_control_status(struct usb_device *usb_dev) +{ + int retval; + struct roccat_common2_control control; + + do { + msleep(50); + retval = roccat_common2_receive(usb_dev, + ROCCAT_COMMON_COMMAND_CONTROL, + &control, sizeof(struct roccat_common2_control)); + + if (retval) + return retval; + + switch (control.value) { + case ROCCAT_COMMON_CONTROL_STATUS_OK: + return 0; + case ROCCAT_COMMON_CONTROL_STATUS_WAIT: + msleep(500); + continue; + case ROCCAT_COMMON_CONTROL_STATUS_INVALID: + + case ROCCAT_COMMON_CONTROL_STATUS_OVERLOAD: + /* seems to be critical - replug necessary */ + return -EINVAL; + default: + dev_err(&usb_dev->dev, + "roccat_common2_receive_control_status: " + "unknown response value 0x%x\n", + control.value); + return -EINVAL; + } + + } while (1); +} + +int roccat_common2_send_with_status(struct usb_device *usb_dev, + uint command, void const *buf, uint size) +{ + int retval; + + retval = roccat_common2_send(usb_dev, command, buf, size); + if (retval) + return retval; + + msleep(100); + + return roccat_common2_receive_control_status(usb_dev); +} +EXPORT_SYMBOL_GPL(roccat_common2_send_with_status); MODULE_AUTHOR("Stefan Achatz"); MODULE_DESCRIPTION("USB Roccat common driver"); diff --git a/drivers/hid/hid-roccat-common.h b/drivers/hid/hid-roccat-common.h index 9a5bc61f969..a97746a63b7 100644 --- a/drivers/hid/hid-roccat-common.h +++ b/drivers/hid/hid-roccat-common.h @@ -15,9 +15,21 @@ #include <linux/usb.h> #include <linux/types.h> -int roccat_common_receive(struct usb_device *usb_dev, uint report_id, +enum roccat_common2_commands { + ROCCAT_COMMON_COMMAND_CONTROL = 0x4, +}; + +struct roccat_common2_control { + uint8_t command; + uint8_t value; + uint8_t request; /* always 0 on requesting write check */ +} __packed; + +int roccat_common2_receive(struct usb_device *usb_dev, uint report_id, void *data, uint size); -int roccat_common_send(struct usb_device *usb_dev, uint report_id, +int roccat_common2_send(struct usb_device *usb_dev, uint report_id, void const *data, uint size); +int roccat_common2_send_with_status(struct usb_device *usb_dev, + uint command, void const *buf, uint size); #endif diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c index 0e4a0ab4714..5669916c294 100644 --- a/drivers/hid/hid-roccat-isku.c +++ b/drivers/hid/hid-roccat-isku.c @@ -36,51 +36,7 @@ static void isku_profile_activated(struct isku_device *isku, uint new_profile) static int isku_receive(struct usb_device *usb_dev, uint command, void *buf, uint size) { - return roccat_common_receive(usb_dev, command, buf, size); -} - -static int isku_receive_control_status(struct usb_device *usb_dev) -{ - int retval; - struct isku_control control; - - do { - msleep(50); - retval = isku_receive(usb_dev, ISKU_COMMAND_CONTROL, - &control, sizeof(struct isku_control)); - - if (retval) - return retval; - - switch (control.value) { - case ISKU_CONTROL_VALUE_STATUS_OK: - return 0; - case ISKU_CONTROL_VALUE_STATUS_WAIT: - continue; - case ISKU_CONTROL_VALUE_STATUS_INVALID: - /* seems to be critical - replug necessary */ - case ISKU_CONTROL_VALUE_STATUS_OVERLOAD: - return -EINVAL; - default: - hid_err(usb_dev, "isku_receive_control_status: " - "unknown response value 0x%x\n", - control.value); - return -EINVAL; - } - - } while (1); -} - -static int isku_send(struct usb_device *usb_dev, uint command, - void const *buf, uint size) -{ - int retval; - - retval = roccat_common_send(usb_dev, command, buf, size); - if (retval) - return retval; - - return isku_receive_control_status(usb_dev); + return roccat_common2_receive(usb_dev, command, buf, size); } static int isku_get_actual_profile(struct usb_device *usb_dev) @@ -100,7 +56,8 @@ static int isku_set_actual_profile(struct usb_device *usb_dev, int new_profile) buf.command = ISKU_COMMAND_ACTUAL_PROFILE; buf.size = sizeof(struct isku_actual_profile); buf.actual_profile = new_profile; - return isku_send(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE, &buf, + return roccat_common2_send_with_status(usb_dev, + ISKU_COMMAND_ACTUAL_PROFILE, &buf, sizeof(struct isku_actual_profile)); } @@ -197,7 +154,8 @@ static ssize_t isku_sysfs_write(struct file *fp, struct kobject *kobj, return -EINVAL; mutex_lock(&isku->isku_lock); - retval = isku_send(usb_dev, command, (void *)buf, real_size); + retval = roccat_common2_send_with_status(usb_dev, command, + (void *)buf, real_size); mutex_unlock(&isku->isku_lock); return retval ? retval : real_size; diff --git a/drivers/hid/hid-roccat-isku.h b/drivers/hid/hid-roccat-isku.h index 075f6efaec5..605b3ce2163 100644 --- a/drivers/hid/hid-roccat-isku.h +++ b/drivers/hid/hid-roccat-isku.h @@ -25,13 +25,6 @@ struct isku_control { uint8_t request; } __packed; -enum isku_control_values { - ISKU_CONTROL_VALUE_STATUS_OVERLOAD = 0, - ISKU_CONTROL_VALUE_STATUS_OK = 1, - ISKU_CONTROL_VALUE_STATUS_INVALID = 2, - ISKU_CONTROL_VALUE_STATUS_WAIT = 3, -}; - struct isku_actual_profile { uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */ uint8_t size; /* always 3 */ diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 40090d60215..9ce2d0b615a 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -138,7 +138,7 @@ static int kone_check_write(struct usb_device *usb_dev) return 0; /* unknown answer */ - hid_err(usb_dev, "got retval %d when checking write\n", data); + dev_err(&usb_dev->dev, "got retval %d when checking write\n", data); return -EIO; } @@ -503,7 +503,7 @@ static ssize_t kone_sysfs_set_tcu(struct device *dev, retval = kone_set_settings(usb_dev, &kone->settings); if (retval) { - hid_err(usb_dev, "couldn't set tcu state\n"); + dev_err(&usb_dev->dev, "couldn't set tcu state\n"); /* * try to reread valid settings into buffer overwriting * first error code @@ -519,7 +519,7 @@ static ssize_t kone_sysfs_set_tcu(struct device *dev, retval = size; exit_no_settings: - hid_err(usb_dev, "couldn't read settings\n"); + dev_err(&usb_dev->dev, "couldn't read settings\n"); exit_unlock: mutex_unlock(&kone->kone_lock); return retval; diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index 59e47770fa1..f5602fec486 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -39,88 +39,26 @@ static void koneplus_profile_activated(struct koneplus_device *koneplus, static int koneplus_send_control(struct usb_device *usb_dev, uint value, enum koneplus_control_requests request) { - struct koneplus_control control; + struct roccat_common2_control control; if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && value > 4) return -EINVAL; - control.command = KONEPLUS_COMMAND_CONTROL; + control.command = ROCCAT_COMMON_COMMAND_CONTROL; control.value = value; control.request = request; - return roccat_common_send(usb_dev, KONEPLUS_COMMAND_CONTROL, - &control, sizeof(struct koneplus_control)); -} - -static int koneplus_receive_control_status(struct usb_device *usb_dev) -{ - int retval; - struct koneplus_control control; - - do { - retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_CONTROL, - &control, sizeof(struct koneplus_control)); - - /* check if we get a completely wrong answer */ - if (retval) - return retval; - - if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OK) - return 0; - - /* indicates that hardware needs some more time to complete action */ - if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_WAIT) { - msleep(500); /* windows driver uses 1000 */ - continue; - } - - /* seems to be critical - replug necessary */ - if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) - return -EINVAL; - - hid_err(usb_dev, "koneplus_receive_control_status: " - "unknown response value 0x%x\n", control.value); - return -EINVAL; - } while (1); -} - -static int koneplus_send(struct usb_device *usb_dev, uint command, - void const *buf, uint size) -{ - int retval; - - retval = roccat_common_send(usb_dev, command, buf, size); - if (retval) - return retval; - - return koneplus_receive_control_status(usb_dev); -} - -static int koneplus_select_profile(struct usb_device *usb_dev, uint number, - enum koneplus_control_requests request) -{ - int retval; - - retval = koneplus_send_control(usb_dev, number, request); - if (retval) - return retval; - - /* allow time to settle things - windows driver uses 500 */ - msleep(100); - - retval = koneplus_receive_control_status(usb_dev); - if (retval) - return retval; - - return 0; + return roccat_common2_send_with_status(usb_dev, + ROCCAT_COMMON_COMMAND_CONTROL, + &control, sizeof(struct roccat_common2_control)); } static int koneplus_get_info(struct usb_device *usb_dev, struct koneplus_info *buf) { - return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_INFO, + return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_INFO, buf, sizeof(struct koneplus_info)); } @@ -129,19 +67,20 @@ static int koneplus_get_profile_settings(struct usb_device *usb_dev, { int retval; - retval = koneplus_select_profile(usb_dev, number, + retval = koneplus_send_control(usb_dev, number, KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); if (retval) return retval; - return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS, + return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS, buf, sizeof(struct koneplus_profile_settings)); } static int koneplus_set_profile_settings(struct usb_device *usb_dev, struct koneplus_profile_settings const *settings) { - return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS, + return roccat_common2_send_with_status(usb_dev, + KONEPLUS_COMMAND_PROFILE_SETTINGS, settings, sizeof(struct koneplus_profile_settings)); } @@ -150,19 +89,20 @@ static int koneplus_get_profile_buttons(struct usb_device *usb_dev, { int retval; - retval = koneplus_select_profile(usb_dev, number, + retval = koneplus_send_control(usb_dev, number, KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); if (retval) return retval; - return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS, + return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS, buf, sizeof(struct koneplus_profile_buttons)); } static int koneplus_set_profile_buttons(struct usb_device *usb_dev, struct koneplus_profile_buttons const *buttons) { - return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS, + return roccat_common2_send_with_status(usb_dev, + KONEPLUS_COMMAND_PROFILE_BUTTONS, buttons, sizeof(struct koneplus_profile_buttons)); } @@ -172,7 +112,7 @@ static int koneplus_get_actual_profile(struct usb_device *usb_dev) struct koneplus_actual_profile buf; int retval; - retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE, + retval = roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE, &buf, sizeof(struct koneplus_actual_profile)); return retval ? retval : buf.actual_profile; @@ -187,7 +127,8 @@ static int koneplus_set_actual_profile(struct usb_device *usb_dev, buf.size = sizeof(struct koneplus_actual_profile); buf.actual_profile = new_profile; - return koneplus_send(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE, + return roccat_common2_send_with_status(usb_dev, + KONEPLUS_COMMAND_ACTUAL_PROFILE, &buf, sizeof(struct koneplus_actual_profile)); } @@ -208,7 +149,7 @@ static ssize_t koneplus_sysfs_read(struct file *fp, struct kobject *kobj, return -EINVAL; mutex_lock(&koneplus->koneplus_lock); - retval = roccat_common_receive(usb_dev, command, buf, real_size); + retval = roccat_common2_receive(usb_dev, command, buf, real_size); mutex_unlock(&koneplus->koneplus_lock); if (retval) @@ -231,7 +172,8 @@ static ssize_t koneplus_sysfs_write(struct file *fp, struct kobject *kobj, return -EINVAL; mutex_lock(&koneplus->koneplus_lock); - retval = koneplus_send(usb_dev, command, buf, real_size); + retval = roccat_common2_send_with_status(usb_dev, command, + buf, real_size); mutex_unlock(&koneplus->koneplus_lock); if (retval) diff --git a/drivers/hid/hid-roccat-koneplus.h b/drivers/hid/hid-roccat-koneplus.h index c03332a4fa9..7074b2a4b94 100644 --- a/drivers/hid/hid-roccat-koneplus.h +++ b/drivers/hid/hid-roccat-koneplus.h @@ -20,32 +20,11 @@ struct koneplus_talk { uint8_t data[14]; } __packed; -/* - * case 1: writes request 80 and reads value 1 - * - */ -struct koneplus_control { - uint8_t command; /* KONEPLUS_COMMAND_CONTROL */ - /* - * value is profile number in range 0-4 for requesting settings and buttons - * 1 if status ok for requesting status - */ - uint8_t value; - uint8_t request; -} __attribute__ ((__packed__)); - enum koneplus_control_requests { - KONEPLUS_CONTROL_REQUEST_STATUS = 0x00, KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x80, KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x90, }; -enum koneplus_control_values { - KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, - KONEPLUS_CONTROL_REQUEST_STATUS_OK = 1, - KONEPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, -}; - struct koneplus_actual_profile { uint8_t command; /* KONEPLUS_COMMAND_ACTUAL_PROFILE */ uint8_t size; /* always 3 */ @@ -137,7 +116,6 @@ struct koneplus_tcu_image { } __attribute__ ((__packed__)); enum koneplus_commands { - KONEPLUS_COMMAND_CONTROL = 0x4, KONEPLUS_COMMAND_ACTUAL_PROFILE = 0x5, KONEPLUS_COMMAND_PROFILE_SETTINGS = 0x6, KONEPLUS_COMMAND_PROFILE_BUTTONS = 0x7, diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index 112d934132c..ca6527ac655 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -47,69 +47,23 @@ static int kovaplus_send_control(struct usb_device *usb_dev, uint value, enum kovaplus_control_requests request) { int retval; - struct kovaplus_control control; + struct roccat_common2_control control; if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && value > 4) return -EINVAL; - control.command = KOVAPLUS_COMMAND_CONTROL; + control.command = ROCCAT_COMMON_COMMAND_CONTROL; control.value = value; control.request = request; - retval = roccat_common_send(usb_dev, KOVAPLUS_COMMAND_CONTROL, - &control, sizeof(struct kovaplus_control)); + retval = roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL, + &control, sizeof(struct roccat_common2_control)); return retval; } -static int kovaplus_receive_control_status(struct usb_device *usb_dev) -{ - int retval; - struct kovaplus_control control; - - do { - retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_CONTROL, - &control, sizeof(struct kovaplus_control)); - - /* check if we get a completely wrong answer */ - if (retval) - return retval; - - if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OK) - return 0; - - /* indicates that hardware needs some more time to complete action */ - if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT) { - msleep(500); /* windows driver uses 1000 */ - continue; - } - - /* seems to be critical - replug necessary */ - if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) - return -EINVAL; - - hid_err(usb_dev, "roccat_common_receive_control_status: " - "unknown response value 0x%x\n", control.value); - return -EINVAL; - } while (1); -} - -static int kovaplus_send(struct usb_device *usb_dev, uint command, - void const *buf, uint size) -{ - int retval; - - retval = roccat_common_send(usb_dev, command, buf, size); - if (retval) - return retval; - - msleep(100); - - return kovaplus_receive_control_status(usb_dev); -} - static int kovaplus_select_profile(struct usb_device *usb_dev, uint number, enum kovaplus_control_requests request) { @@ -119,7 +73,7 @@ static int kovaplus_select_profile(struct usb_device *usb_dev, uint number, static int kovaplus_get_info(struct usb_device *usb_dev, struct kovaplus_info *buf) { - return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_INFO, + return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_INFO, buf, sizeof(struct kovaplus_info)); } @@ -133,14 +87,15 @@ static int kovaplus_get_profile_settings(struct usb_device *usb_dev, if (retval) return retval; - return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS, + return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS, buf, sizeof(struct kovaplus_profile_settings)); } static int kovaplus_set_profile_settings(struct usb_device *usb_dev, struct kovaplus_profile_settings const *settings) { - return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS, + return roccat_common2_send_with_status(usb_dev, + KOVAPLUS_COMMAND_PROFILE_SETTINGS, settings, sizeof(struct kovaplus_profile_settings)); } @@ -154,14 +109,15 @@ static int kovaplus_get_profile_buttons(struct usb_device *usb_dev, if (retval) return retval; - return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS, + return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS, buf, sizeof(struct kovaplus_profile_buttons)); } static int kovaplus_set_profile_buttons(struct usb_device *usb_dev, struct kovaplus_profile_buttons const *buttons) { - return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS, + return roccat_common2_send_with_status(usb_dev, + KOVAPLUS_COMMAND_PROFILE_BUTTONS, buttons, sizeof(struct kovaplus_profile_buttons)); } @@ -171,7 +127,7 @@ static int kovaplus_get_actual_profile(struct usb_device *usb_dev) struct kovaplus_actual_profile buf; int retval; - retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE, + retval = roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE, &buf, sizeof(struct kovaplus_actual_profile)); return retval ? retval : buf.actual_profile; @@ -186,7 +142,8 @@ static int kovaplus_set_actual_profile(struct usb_device *usb_dev, buf.size = sizeof(struct kovaplus_actual_profile); buf.actual_profile = new_profile; - return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE, + return roccat_common2_send_with_status(usb_dev, + KOVAPLUS_COMMAND_ACTUAL_PROFILE, &buf, sizeof(struct kovaplus_actual_profile)); } diff --git a/drivers/hid/hid-roccat-kovaplus.h b/drivers/hid/hid-roccat-kovaplus.h index fb2aed44a8e..f82daa1cdcb 100644 --- a/drivers/hid/hid-roccat-kovaplus.h +++ b/drivers/hid/hid-roccat-kovaplus.h @@ -14,27 +14,13 @@ #include <linux/types.h> -struct kovaplus_control { - uint8_t command; /* KOVAPLUS_COMMAND_CONTROL */ - uint8_t value; - uint8_t request; -} __packed; - enum kovaplus_control_requests { - /* read after write; value = 1 */ - KOVAPLUS_CONTROL_REQUEST_STATUS = 0x0, /* write; value = profile number range 0-4 */ KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10, /* write; value = profile number range 0-4 */ KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20, }; -enum kovaplus_control_values { - KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, /* supposed */ - KOVAPLUS_CONTROL_REQUEST_STATUS_OK = 1, - KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, /* supposed */ -}; - struct kovaplus_actual_profile { uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */ uint8_t size; /* always 3 */ @@ -75,7 +61,6 @@ struct kovaplus_a { } __packed; enum kovaplus_commands { - KOVAPLUS_COMMAND_CONTROL = 0x4, KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5, KOVAPLUS_COMMAND_PROFILE_SETTINGS = 0x6, KOVAPLUS_COMMAND_PROFILE_BUTTONS = 0x7, diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index df05c1b1064..1317c177a3e 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -42,43 +42,19 @@ static void profile_activated(struct pyra_device *pyra, static int pyra_send_control(struct usb_device *usb_dev, int value, enum pyra_control_requests request) { - struct pyra_control control; + struct roccat_common2_control control; if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS || request == PYRA_CONTROL_REQUEST_PROFILE_BUTTONS) && (value < 0 || value > 4)) return -EINVAL; - control.command = PYRA_COMMAND_CONTROL; + control.command = ROCCAT_COMMON_COMMAND_CONTROL; control.value = value; control.request = request; - return roccat_common_send(usb_dev, PYRA_COMMAND_CONTROL, - &control, sizeof(struct pyra_control)); -} - -static int pyra_receive_control_status(struct usb_device *usb_dev) -{ - int retval; - struct pyra_control control; - - do { - msleep(10); - retval = roccat_common_receive(usb_dev, PYRA_COMMAND_CONTROL, - &control, sizeof(struct pyra_control)); - - /* requested too early, try again */ - } while (retval == -EPROTO); - - if (!retval && control.command == PYRA_COMMAND_CONTROL && - control.request == PYRA_CONTROL_REQUEST_STATUS && - control.value == 1) - return 0; - else { - hid_err(usb_dev, "receive control status: unknown response 0x%x 0x%x\n", - control.request, control.value); - return retval ? retval : -EINVAL; - } + return roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL, + &control, sizeof(struct roccat_common2_control)); } static int pyra_get_profile_settings(struct usb_device *usb_dev, @@ -89,7 +65,7 @@ static int pyra_get_profile_settings(struct usb_device *usb_dev, PYRA_CONTROL_REQUEST_PROFILE_SETTINGS); if (retval) return retval; - return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, + return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, buf, sizeof(struct pyra_profile_settings)); } @@ -101,51 +77,44 @@ static int pyra_get_profile_buttons(struct usb_device *usb_dev, PYRA_CONTROL_REQUEST_PROFILE_BUTTONS); if (retval) return retval; - return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, + return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, buf, sizeof(struct pyra_profile_buttons)); } static int pyra_get_settings(struct usb_device *usb_dev, struct pyra_settings *buf) { - return roccat_common_receive(usb_dev, PYRA_COMMAND_SETTINGS, + return roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS, buf, sizeof(struct pyra_settings)); } static int pyra_get_info(struct usb_device *usb_dev, struct pyra_info *buf) { - return roccat_common_receive(usb_dev, PYRA_COMMAND_INFO, + return roccat_common2_receive(usb_dev, PYRA_COMMAND_INFO, buf, sizeof(struct pyra_info)); } -static int pyra_send(struct usb_device *usb_dev, uint command, - void const *buf, uint size) -{ - int retval; - retval = roccat_common_send(usb_dev, command, buf, size); - if (retval) - return retval; - return pyra_receive_control_status(usb_dev); -} - static int pyra_set_profile_settings(struct usb_device *usb_dev, struct pyra_profile_settings const *settings) { - return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, settings, + return roccat_common2_send_with_status(usb_dev, + PYRA_COMMAND_PROFILE_SETTINGS, settings, sizeof(struct pyra_profile_settings)); } static int pyra_set_profile_buttons(struct usb_device *usb_dev, struct pyra_profile_buttons const *buttons) { - return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, buttons, + return roccat_common2_send_with_status(usb_dev, + PYRA_COMMAND_PROFILE_BUTTONS, buttons, sizeof(struct pyra_profile_buttons)); } static int pyra_set_settings(struct usb_device *usb_dev, struct pyra_settings const *settings) { - return pyra_send(usb_dev, PYRA_COMMAND_SETTINGS, settings, + return roccat_common2_send_with_status(usb_dev, + PYRA_COMMAND_SETTINGS, settings, sizeof(struct pyra_settings)); } diff --git a/drivers/hid/hid-roccat-pyra.h b/drivers/hid/hid-roccat-pyra.h index 0442d7fa2dc..eada7830fa9 100644 --- a/drivers/hid/hid-roccat-pyra.h +++ b/drivers/hid/hid-roccat-pyra.h @@ -20,18 +20,7 @@ struct pyra_b { uint8_t unknown; /* 1 */ } __attribute__ ((__packed__)); -struct pyra_control { - uint8_t command; /* PYRA_COMMAND_CONTROL */ - /* - * value is profile number for request_settings and request_buttons - * 1 if status ok for request_status - */ - uint8_t value; /* Range 0-4 */ - uint8_t request; -} __attribute__ ((__packed__)); - enum pyra_control_requests { - PYRA_CONTROL_REQUEST_STATUS = 0x00, PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10, PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20 }; @@ -75,7 +64,6 @@ struct pyra_info { } __attribute__ ((__packed__)); enum pyra_commands { - PYRA_COMMAND_CONTROL = 0x4, PYRA_COMMAND_SETTINGS = 0x5, PYRA_COMMAND_PROFILE_SETTINGS = 0x6, PYRA_COMMAND_PROFILE_BUTTONS = 0x7, diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c new file mode 100644 index 00000000000..014afba407e --- /dev/null +++ b/drivers/hid/hid-roccat-savu.c @@ -0,0 +1,316 @@ +/* + * Roccat Savu driver for Linux + * + * Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * 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. + */ + +/* Roccat Savu is a gamer mouse with macro keys that can be configured in + * 5 profiles. + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hid-roccat.h> +#include "hid-ids.h" +#include "hid-roccat-common.h" +#include "hid-roccat-savu.h" + +static struct class *savu_class; + +static ssize_t savu_sysfs_read(struct file *fp, struct kobject *kobj, + char *buf, loff_t off, size_t count, + size_t real_size, uint command) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct savu_device *savu = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval; + + if (off >= real_size) + return 0; + + if (off != 0 || count != real_size) + return -EINVAL; + + mutex_lock(&savu->savu_lock); + retval = roccat_common2_receive(usb_dev, command, buf, real_size); + mutex_unlock(&savu->savu_lock); + + return retval ? retval : real_size; +} + +static ssize_t savu_sysfs_write(struct file *fp, struct kobject *kobj, + void const *buf, loff_t off, size_t count, + size_t real_size, uint command) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct savu_device *savu = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval; + + if (off != 0 || count != real_size) + return -EINVAL; + + mutex_lock(&savu->savu_lock); + retval = roccat_common2_send_with_status(usb_dev, command, + (void *)buf, real_size); + mutex_unlock(&savu->savu_lock); + + return retval ? retval : real_size; +} + +#define SAVU_SYSFS_W(thingy, THINGY) \ +static ssize_t savu_sysfs_write_ ## thingy(struct file *fp, \ + struct kobject *kobj, struct bin_attribute *attr, char *buf, \ + loff_t off, size_t count) \ +{ \ + return savu_sysfs_write(fp, kobj, buf, off, count, \ + SAVU_SIZE_ ## THINGY, SAVU_COMMAND_ ## THINGY); \ +} + +#define SAVU_SYSFS_R(thingy, THINGY) \ +static ssize_t savu_sysfs_read_ ## thingy(struct file *fp, \ + struct kobject *kobj, struct bin_attribute *attr, char *buf, \ + loff_t off, size_t count) \ +{ \ + return savu_sysfs_read(fp, kobj, buf, off, count, \ + SAVU_SIZE_ ## THINGY, SAVU_COMMAND_ ## THINGY); \ +} + +#define SAVU_SYSFS_RW(thingy, THINGY) \ +SAVU_SYSFS_W(thingy, THINGY) \ +SAVU_SYSFS_R(thingy, THINGY) + +#define SAVU_BIN_ATTRIBUTE_RW(thingy, THINGY) \ +{ \ + .attr = { .name = #thingy, .mode = 0660 }, \ + .size = SAVU_SIZE_ ## THINGY, \ + .read = savu_sysfs_read_ ## thingy, \ + .write = savu_sysfs_write_ ## thingy \ +} + +#define SAVU_BIN_ATTRIBUTE_R(thingy, THINGY) \ +{ \ + .attr = { .name = #thingy, .mode = 0440 }, \ + .size = SAVU_SIZE_ ## THINGY, \ + .read = savu_sysfs_read_ ## thingy, \ +} + +#define SAVU_BIN_ATTRIBUTE_W(thingy, THINGY) \ +{ \ + .attr = { .name = #thingy, .mode = 0220 }, \ + .size = SAVU_SIZE_ ## THINGY, \ + .write = savu_sysfs_write_ ## thingy \ +} + +SAVU_SYSFS_W(control, CONTROL) +SAVU_SYSFS_RW(profile, PROFILE) +SAVU_SYSFS_RW(general, GENERAL) +SAVU_SYSFS_RW(buttons, BUTTONS) +SAVU_SYSFS_RW(macro, MACRO) +SAVU_SYSFS_R(info, INFO) +SAVU_SYSFS_RW(sensor, SENSOR) + +static struct bin_attribute savu_bin_attributes[] = { + SAVU_BIN_ATTRIBUTE_W(control, CONTROL), + SAVU_BIN_ATTRIBUTE_RW(profile, PROFILE), + SAVU_BIN_ATTRIBUTE_RW(general, GENERAL), + SAVU_BIN_ATTRIBUTE_RW(buttons, BUTTONS), + SAVU_BIN_ATTRIBUTE_RW(macro, MACRO), + SAVU_BIN_ATTRIBUTE_R(info, INFO), + SAVU_BIN_ATTRIBUTE_RW(sensor, SENSOR), + __ATTR_NULL +}; + +static int savu_init_savu_device_struct(struct usb_device *usb_dev, + struct savu_device *savu) +{ + mutex_init(&savu->savu_lock); + + return 0; +} + +static int savu_init_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct savu_device *savu; + int retval; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + != USB_INTERFACE_PROTOCOL_MOUSE) { + hid_set_drvdata(hdev, NULL); + return 0; + } + + savu = kzalloc(sizeof(*savu), GFP_KERNEL); + if (!savu) { + hid_err(hdev, "can't alloc device descriptor\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, savu); + + retval = savu_init_savu_device_struct(usb_dev, savu); + if (retval) { + hid_err(hdev, "couldn't init struct savu_device\n"); + goto exit_free; + } + + retval = roccat_connect(savu_class, hdev, + sizeof(struct savu_roccat_report)); + if (retval < 0) { + hid_err(hdev, "couldn't init char dev\n"); + } else { + savu->chrdev_minor = retval; + savu->roccat_claimed = 1; + } + + return 0; +exit_free: + kfree(savu); + return retval; +} + +static void savu_remove_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct savu_device *savu; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + != USB_INTERFACE_PROTOCOL_MOUSE) + return; + + savu = hid_get_drvdata(hdev); + if (savu->roccat_claimed) + roccat_disconnect(savu->chrdev_minor); + kfree(savu); +} + +static int savu_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int retval; + + retval = hid_parse(hdev); + if (retval) { + hid_err(hdev, "parse failed\n"); + goto exit; + } + + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + hid_err(hdev, "hw start failed\n"); + goto exit; + } + + retval = savu_init_specials(hdev); + if (retval) { + hid_err(hdev, "couldn't install mouse\n"); + goto exit_stop; + } + + return 0; + +exit_stop: + hid_hw_stop(hdev); +exit: + return retval; +} + +static void savu_remove(struct hid_device *hdev) +{ + savu_remove_specials(hdev); + hid_hw_stop(hdev); +} + +static void savu_report_to_chrdev(struct savu_device const *savu, + u8 const *data) +{ + struct savu_roccat_report roccat_report; + struct savu_mouse_report_special const *special_report; + + if (data[0] != SAVU_MOUSE_REPORT_NUMBER_SPECIAL) + return; + + special_report = (struct savu_mouse_report_special const *)data; + + roccat_report.type = special_report->type; + roccat_report.data[0] = special_report->data[0]; + roccat_report.data[1] = special_report->data[1]; + roccat_report_event(savu->chrdev_minor, + (uint8_t const *)&roccat_report); +} + +static int savu_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct savu_device *savu = hid_get_drvdata(hdev); + + if (intf->cur_altsetting->desc.bInterfaceProtocol + != USB_INTERFACE_PROTOCOL_MOUSE) + return 0; + + if (savu == NULL) + return 0; + + if (savu->roccat_claimed) + savu_report_to_chrdev(savu, data); + + return 0; +} + +static const struct hid_device_id savu_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, savu_devices); + +static struct hid_driver savu_driver = { + .name = "savu", + .id_table = savu_devices, + .probe = savu_probe, + .remove = savu_remove, + .raw_event = savu_raw_event +}; + +static int __init savu_init(void) +{ + int retval; + + savu_class = class_create(THIS_MODULE, "savu"); + if (IS_ERR(savu_class)) + return PTR_ERR(savu_class); + savu_class->dev_bin_attrs = savu_bin_attributes; + + retval = hid_register_driver(&savu_driver); + if (retval) + class_destroy(savu_class); + return retval; +} + +static void __exit savu_exit(void) +{ + hid_unregister_driver(&savu_driver); + class_destroy(savu_class); +} + +module_init(savu_init); +module_exit(savu_exit); + +MODULE_AUTHOR("Stefan Achatz"); +MODULE_DESCRIPTION("USB Roccat Savu driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-roccat-savu.h b/drivers/hid/hid-roccat-savu.h new file mode 100644 index 00000000000..9120ba72087 --- /dev/null +++ b/drivers/hid/hid-roccat-savu.h @@ -0,0 +1,87 @@ +#ifndef __HID_ROCCAT_SAVU_H +#define __HID_ROCCAT_SAVU_H + +/* + * Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * 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/types.h> + +enum { + SAVU_SIZE_CONTROL = 0x03, + SAVU_SIZE_PROFILE = 0x03, + SAVU_SIZE_GENERAL = 0x10, + SAVU_SIZE_BUTTONS = 0x2f, + SAVU_SIZE_MACRO = 0x0823, + SAVU_SIZE_INFO = 0x08, + SAVU_SIZE_SENSOR = 0x04, +}; + +enum savu_control_requests { + SAVU_CONTROL_REQUEST_GENERAL = 0x80, + SAVU_CONTROL_REQUEST_BUTTONS = 0x90, +}; + +enum savu_commands { + SAVU_COMMAND_CONTROL = 0x4, + SAVU_COMMAND_PROFILE = 0x5, + SAVU_COMMAND_GENERAL = 0x6, + SAVU_COMMAND_BUTTONS = 0x7, + SAVU_COMMAND_MACRO = 0x8, + SAVU_COMMAND_INFO = 0x9, + SAVU_COMMAND_SENSOR = 0xc, +}; + +struct savu_mouse_report_special { + uint8_t report_number; /* always 3 */ + uint8_t zero; + uint8_t type; + uint8_t data[2]; +} __packed; + +enum { + SAVU_MOUSE_REPORT_NUMBER_SPECIAL = 3, +}; + +enum savu_mouse_report_button_types { + /* data1 = new profile range 1-5 */ + SAVU_MOUSE_REPORT_BUTTON_TYPE_PROFILE = 0x20, + + /* data1 = button number range 1-24; data2 = action */ + SAVU_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60, + + /* data1 = button number range 1-24; data2 = action */ + SAVU_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80, + + /* data1 = setting number range 1-5 */ + SAVU_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0, + + /* data1 and data2 = range 0x1-0xb */ + SAVU_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0, + + /* data1 = 22 = next track... + * data2 = action + */ + SAVU_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0, +}; + +struct savu_roccat_report { + uint8_t type; + uint8_t data[2]; +} __packed; + +struct savu_device { + int roccat_claimed; + int chrdev_minor; + + struct mutex savu_lock; +}; + +#endif diff --git a/drivers/hid/hid-wiimote-ext.c b/drivers/hid/hid-wiimote-ext.c index aa958706c0e..0a1805c9b0e 100644 --- a/drivers/hid/hid-wiimote-ext.c +++ b/drivers/hid/hid-wiimote-ext.c @@ -77,7 +77,7 @@ static __u16 wiiext_keymap[] = { BTN_TR, /* WIIEXT_KEY_RT */ }; -/* diable all extensions */ +/* disable all extensions */ static void ext_disable(struct wiimote_ext *ext) { unsigned long flags; diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 36fa77b40ff..3b6f7bf5a77 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -96,6 +96,7 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, } kfree(list->buffer[list->tail].value); + list->buffer[list->tail].value = NULL; list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); } out: @@ -300,6 +301,7 @@ static int hidraw_release(struct inode * inode, struct file * file) struct hidraw *dev; struct hidraw_list *list = file->private_data; int ret; + int i; mutex_lock(&minors_lock); if (!hidraw_table[minor]) { @@ -317,6 +319,9 @@ static int hidraw_release(struct inode * inode, struct file * file) kfree(list->hidraw); } } + + for (i = 0; i < HIDRAW_BUFFER_SIZE; ++i) + kfree(list->buffer[i].value); kfree(list); ret = 0; unlock: @@ -446,12 +451,17 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len) int ret = 0; list_for_each_entry(list, &dev->list, node) { + int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); + + if (new_head == list->tail) + continue; + if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) { ret = -ENOMEM; break; } list->buffer[list->head].len = len; - list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); + list->head = new_head; kill_fasync(&list->fasync, SIGIO, POLL_IN); } diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c new file mode 100644 index 00000000000..714cd8cc957 --- /dev/null +++ b/drivers/hid/uhid.c @@ -0,0 +1,572 @@ +/* + * 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/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> + +#define UHID_NAME "uhid" +#define UHID_BUFSIZE 32 + +struct uhid_device { + struct mutex devlock; + bool running; + + __u8 *rd_data; + uint rd_size; + + 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]; + + struct mutex report_lock; + wait_queue_head_t report_wait; + atomic_t report_done; + atomic_t report_id; + struct uhid_event report_buf; +}; + +static struct miscdevice uhid_misc; + +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; + + return uhid_queue_event(uhid, UHID_START); +} + +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; + + return uhid_queue_event(uhid, UHID_OPEN); +} + +static void uhid_hid_close(struct hid_device *hid) +{ + struct uhid_device *uhid = hid->driver_data; + + uhid_queue_event(uhid, UHID_CLOSE); +} + +static int uhid_hid_input(struct input_dev *input, unsigned int type, + unsigned int code, int value) +{ + struct hid_device *hid = input_get_drvdata(input); + struct uhid_device *uhid = hid->driver_data; + unsigned long flags; + struct uhid_event *ev; + + ev = kzalloc(sizeof(*ev), GFP_ATOMIC); + if (!ev) + return -ENOMEM; + + ev->type = UHID_OUTPUT_EV; + ev->u.output_ev.type = type; + ev->u.output_ev.code = code; + ev->u.output_ev.value = value; + + spin_lock_irqsave(&uhid->qlock, flags); + uhid_queue(uhid, ev); + spin_unlock_irqrestore(&uhid->qlock, flags); + + return 0; +} + +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); +} + +static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum, + __u8 *buf, size_t count, unsigned char rtype) +{ + struct uhid_device *uhid = hid->driver_data; + __u8 report_type; + struct uhid_event *ev; + unsigned long flags; + int ret; + size_t uninitialized_var(len); + struct uhid_feature_answer_req *req; + + if (!uhid->running) + return -EIO; + + switch (rtype) { + case HID_FEATURE_REPORT: + report_type = UHID_FEATURE_REPORT; + break; + case HID_OUTPUT_REPORT: + report_type = UHID_OUTPUT_REPORT; + break; + case HID_INPUT_REPORT: + report_type = UHID_INPUT_REPORT; + break; + default: + return -EINVAL; + } + + ret = mutex_lock_interruptible(&uhid->report_lock); + if (ret) + return ret; + + ev = kzalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) { + ret = -ENOMEM; + goto unlock; + } + + spin_lock_irqsave(&uhid->qlock, flags); + ev->type = UHID_FEATURE; + ev->u.feature.id = atomic_inc_return(&uhid->report_id); + ev->u.feature.rnum = rnum; + ev->u.feature.rtype = report_type; + + atomic_set(&uhid->report_done, 0); + uhid_queue(uhid, ev); + spin_unlock_irqrestore(&uhid->qlock, flags); + + ret = wait_event_interruptible_timeout(uhid->report_wait, + atomic_read(&uhid->report_done), 5 * HZ); + + /* + * Make sure "uhid->running" is cleared on shutdown before + * "uhid->report_done" is set. + */ + smp_rmb(); + if (!ret || !uhid->running) { + ret = -EIO; + } else if (ret < 0) { + ret = -ERESTARTSYS; + } else { + spin_lock_irqsave(&uhid->qlock, flags); + req = &uhid->report_buf.u.feature_answer; + + if (req->err) { + ret = -EIO; + } else { + ret = 0; + len = min(count, + min_t(size_t, req->size, UHID_DATA_MAX)); + memcpy(buf, req->data, len); + } + + spin_unlock_irqrestore(&uhid->qlock, flags); + } + + atomic_set(&uhid->report_done, 1); + +unlock: + mutex_unlock(&uhid->report_lock); + return ret ? ret : len; +} + +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 struct hid_ll_driver uhid_hid_driver = { + .start = uhid_hid_start, + .stop = uhid_hid_stop, + .open = uhid_hid_open, + .close = uhid_hid_close, + .hidinput_input_event = uhid_hid_input, + .parse = uhid_hid_parse, +}; + +static int uhid_dev_create(struct uhid_device *uhid, + const struct uhid_event *ev) +{ + struct hid_device *hid; + int ret; + + if (uhid->running) + return -EALREADY; + + uhid->rd_size = ev->u.create.rd_size; + if (uhid->rd_size <= 0 || uhid->rd_size > HID_MAX_DESCRIPTOR_SIZE) + return -EINVAL; + + uhid->rd_data = kmalloc(uhid->rd_size, GFP_KERNEL); + if (!uhid->rd_data) + return -ENOMEM; + + if (copy_from_user(uhid->rd_data, ev->u.create.rd_data, + uhid->rd_size)) { + ret = -EFAULT; + goto err_free; + } + + hid = hid_allocate_device(); + if (IS_ERR(hid)) { + ret = PTR_ERR(hid); + goto err_free; + } + + strncpy(hid->name, ev->u.create.name, 127); + hid->name[127] = 0; + strncpy(hid->phys, ev->u.create.phys, 63); + hid->phys[63] = 0; + strncpy(hid->uniq, ev->u.create.uniq, 63); + hid->uniq[63] = 0; + + hid->ll_driver = &uhid_hid_driver; + hid->hid_get_raw_report = uhid_hid_get_raw; + 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; + hid->version = ev->u.create.version; + hid->country = ev->u.create.country; + hid->driver_data = uhid; + hid->dev.parent = uhid_misc.this_device; + + uhid->hid = hid; + uhid->running = true; + + ret = hid_add_device(hid); + if (ret) { + hid_err(hid, "Cannot register HID device\n"); + goto err_hid; + } + + return 0; + +err_hid: + hid_destroy_device(hid); + uhid->hid = NULL; + uhid->running = false; +err_free: + kfree(uhid->rd_data); + return ret; +} + +static int uhid_dev_destroy(struct uhid_device *uhid) +{ + if (!uhid->running) + return -EINVAL; + + /* clear "running" before setting "report_done" */ + uhid->running = false; + smp_wmb(); + atomic_set(&uhid->report_done, 1); + wake_up_interruptible(&uhid->report_wait); + + hid_destroy_device(uhid->hid); + 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_feature_answer(struct uhid_device *uhid, + struct uhid_event *ev) +{ + unsigned long flags; + + if (!uhid->running) + return -EINVAL; + + spin_lock_irqsave(&uhid->qlock, flags); + + /* id for old report; drop it silently */ + if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id) + goto unlock; + if (atomic_read(&uhid->report_done)) + goto unlock; + + memcpy(&uhid->report_buf, ev, sizeof(*ev)); + atomic_set(&uhid->report_done, 1); + wake_up_interruptible(&uhid->report_wait); + +unlock: + spin_unlock_irqrestore(&uhid->qlock, flags); + 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; + atomic_set(&uhid->report_done, 1); + + 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)); + if (copy_from_user(&uhid->input_buf, buffer, len)) { + ret = -EFAULT; + goto unlock; + } + + switch (uhid->input_buf.type) { + case UHID_CREATE: + ret = uhid_dev_create(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_FEATURE_ANSWER: + ret = uhid_dev_feature_answer(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; + + poll_wait(file, &uhid->waitq, wait); + + if (uhid->head != uhid->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +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 = MISC_DYNAMIC_MINOR, + .name = UHID_NAME, +}; + +static int __init uhid_init(void) +{ + return misc_register(&uhid_misc); +} + +static void __exit uhid_exit(void) +{ + misc_deregister(&uhid_misc); +} + +module_init(uhid_init); +module_exit(uhid_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>"); +MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem"); diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 482f936fc29..dedd8e4e5c6 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -84,7 +84,7 @@ static int hid_start_in(struct hid_device *hid) spin_lock_irqsave(&usbhid->lock, flags); if (hid->open > 0 && !test_bit(HID_DISCONNECTED, &usbhid->iofl) && - !test_bit(HID_REPORTED_IDLE, &usbhid->iofl) && + !test_bit(HID_SUSPENDED, &usbhid->iofl) && !test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) { rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC); if (rc != 0) { @@ -207,15 +207,27 @@ static int usbhid_restart_out_queue(struct usbhid_device *usbhid) int kicked; int r; - if (!hid) + if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) || + test_bit(HID_SUSPENDED, &usbhid->iofl)) return 0; if ((kicked = (usbhid->outhead != usbhid->outtail))) { hid_dbg(hid, "Kicking head %d tail %d", usbhid->outhead, usbhid->outtail); + /* Try to wake up from autosuspend... */ r = usb_autopm_get_interface_async(usbhid->intf); if (r < 0) return r; + + /* + * If still suspended, don't submit. Submission will + * occur if/when resume drains the queue. + */ + if (test_bit(HID_SUSPENDED, &usbhid->iofl)) { + usb_autopm_put_interface_no_suspend(usbhid->intf); + return r; + } + /* Asynchronously flush queue. */ set_bit(HID_OUT_RUNNING, &usbhid->iofl); if (hid_submit_out(hid)) { @@ -234,15 +246,27 @@ static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid) int r; WARN_ON(hid == NULL); - if (!hid) + if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) || + test_bit(HID_SUSPENDED, &usbhid->iofl)) return 0; if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) { hid_dbg(hid, "Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail); + /* Try to wake up from autosuspend... */ r = usb_autopm_get_interface_async(usbhid->intf); if (r < 0) return r; + + /* + * If still suspended, don't submit. Submission will + * occur if/when resume drains the queue. + */ + if (test_bit(HID_SUSPENDED, &usbhid->iofl)) { + usb_autopm_put_interface_no_suspend(usbhid->intf); + return r; + } + /* Asynchronously flush queue. */ set_bit(HID_CTRL_RUNNING, &usbhid->iofl); if (hid_submit_ctrl(hid)) { @@ -331,9 +355,12 @@ static int hid_submit_out(struct hid_device *hid) usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0); usbhid->urbout->dev = hid_to_usb_dev(hid); - memcpy(usbhid->outbuf, raw_report, - usbhid->urbout->transfer_buffer_length); - kfree(raw_report); + if (raw_report) { + memcpy(usbhid->outbuf, raw_report, + usbhid->urbout->transfer_buffer_length); + kfree(raw_report); + usbhid->out[usbhid->outtail].raw_report = NULL; + } dbg_hid("submitting out urb\n"); @@ -362,8 +389,11 @@ static int hid_submit_ctrl(struct hid_device *hid) if (dir == USB_DIR_OUT) { usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0); usbhid->urbctrl->transfer_buffer_length = len; - memcpy(usbhid->ctrlbuf, raw_report, len); - kfree(raw_report); + if (raw_report) { + memcpy(usbhid->ctrlbuf, raw_report, len); + kfree(raw_report); + usbhid->ctrl[usbhid->ctrltail].raw_report = NULL; + } } else { int maxpacket, padlen; @@ -407,16 +437,6 @@ static int hid_submit_ctrl(struct hid_device *hid) * Output interrupt completion handler. */ -static int irq_out_pump_restart(struct hid_device *hid) -{ - struct usbhid_device *usbhid = hid->driver_data; - - if (usbhid->outhead != usbhid->outtail) - return hid_submit_out(hid); - else - return -1; -} - static void hid_irq_out(struct urb *urb) { struct hid_device *hid = urb->context; @@ -441,15 +461,17 @@ static void hid_irq_out(struct urb *urb) spin_lock_irqsave(&usbhid->lock, flags); - if (unplug) + if (unplug) { usbhid->outtail = usbhid->outhead; - else + } else { usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1); - if (!irq_out_pump_restart(hid)) { - /* Successfully submitted next urb in queue */ - spin_unlock_irqrestore(&usbhid->lock, flags); - return; + if (usbhid->outhead != usbhid->outtail && + hid_submit_out(hid) == 0) { + /* Successfully submitted next urb in queue */ + spin_unlock_irqrestore(&usbhid->lock, flags); + return; + } } clear_bit(HID_OUT_RUNNING, &usbhid->iofl); @@ -461,15 +483,6 @@ static void hid_irq_out(struct urb *urb) /* * Control pipe completion handler. */ -static int ctrl_pump_restart(struct hid_device *hid) -{ - struct usbhid_device *usbhid = hid->driver_data; - - if (usbhid->ctrlhead != usbhid->ctrltail) - return hid_submit_ctrl(hid); - else - return -1; -} static void hid_ctrl(struct urb *urb) { @@ -498,15 +511,17 @@ static void hid_ctrl(struct urb *urb) hid_warn(urb->dev, "ctrl urb status %d received\n", status); } - if (unplug) + if (unplug) { usbhid->ctrltail = usbhid->ctrlhead; - else + } else { usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1); - if (!ctrl_pump_restart(hid)) { - /* Successfully submitted next urb in queue */ - spin_unlock(&usbhid->lock); - return; + if (usbhid->ctrlhead != usbhid->ctrltail && + hid_submit_ctrl(hid) == 0) { + /* Successfully submitted next urb in queue */ + spin_unlock(&usbhid->lock); + return; + } } clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); @@ -540,49 +555,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re usbhid->out[usbhid->outhead].report = report; usbhid->outhead = head; - /* Try to awake from autosuspend... */ - if (usb_autopm_get_interface_async(usbhid->intf) < 0) - return; + /* If the queue isn't running, restart it */ + if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) { + usbhid_restart_out_queue(usbhid); - /* - * But if still suspended, leave urb enqueued, don't submit. - * Submission will occur if/when resume() drains the queue. - */ - if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) - return; + /* Otherwise see if an earlier request has timed out */ + } else if (time_after(jiffies, usbhid->last_out + HZ * 5)) { + + /* Prevent autosuspend following the unlink */ + usb_autopm_get_interface_no_resume(usbhid->intf); - if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) { - if (hid_submit_out(hid)) { - clear_bit(HID_OUT_RUNNING, &usbhid->iofl); - usb_autopm_put_interface_async(usbhid->intf); - } - wake_up(&usbhid->wait); - } else { /* - * the queue is known to run - * but an earlier request may be stuck - * we may need to time out - * no race because the URB is blocked under - * spinlock + * Prevent resubmission in case the URB completes + * before we can unlink it. We don't want to cancel + * the wrong transfer! */ - if (time_after(jiffies, usbhid->last_out + HZ * 5)) { - usb_block_urb(usbhid->urbout); - /* drop lock to not deadlock if the callback is called */ - spin_unlock(&usbhid->lock); - usb_unlink_urb(usbhid->urbout); - spin_lock(&usbhid->lock); - usb_unblock_urb(usbhid->urbout); - /* - * if the unlinking has already completed - * the pump will have been stopped - * it must be restarted now - */ - if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) - if (!irq_out_pump_restart(hid)) - set_bit(HID_OUT_RUNNING, &usbhid->iofl); + usb_block_urb(usbhid->urbout); + /* Drop lock to avoid deadlock if the callback runs */ + spin_unlock(&usbhid->lock); - } + usb_unlink_urb(usbhid->urbout); + spin_lock(&usbhid->lock); + usb_unblock_urb(usbhid->urbout); + + /* Unlink might have stopped the queue */ + if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) + usbhid_restart_out_queue(usbhid); + + /* Now we can allow autosuspend again */ + usb_autopm_put_interface_async(usbhid->intf); } return; } @@ -604,47 +606,36 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re usbhid->ctrl[usbhid->ctrlhead].dir = dir; usbhid->ctrlhead = head; - /* Try to awake from autosuspend... */ - if (usb_autopm_get_interface_async(usbhid->intf) < 0) - return; + /* If the queue isn't running, restart it */ + if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) { + usbhid_restart_ctrl_queue(usbhid); - /* - * If already suspended, leave urb enqueued, but don't submit. - * Submission will occur if/when resume() drains the queue. - */ - if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl)) - return; + /* Otherwise see if an earlier request has timed out */ + } else if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) { + + /* Prevent autosuspend following the unlink */ + usb_autopm_get_interface_no_resume(usbhid->intf); - if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) { - if (hid_submit_ctrl(hid)) { - clear_bit(HID_CTRL_RUNNING, &usbhid->iofl); - usb_autopm_put_interface_async(usbhid->intf); - } - wake_up(&usbhid->wait); - } else { /* - * the queue is known to run - * but an earlier request may be stuck - * we may need to time out - * no race because the URB is blocked under - * spinlock + * Prevent resubmission in case the URB completes + * before we can unlink it. We don't want to cancel + * the wrong transfer! */ - if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) { - usb_block_urb(usbhid->urbctrl); - /* drop lock to not deadlock if the callback is called */ - spin_unlock(&usbhid->lock); - usb_unlink_urb(usbhid->urbctrl); - spin_lock(&usbhid->lock); - usb_unblock_urb(usbhid->urbctrl); - /* - * if the unlinking has already completed - * the pump will have been stopped - * it must be restarted now - */ - if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) - if (!ctrl_pump_restart(hid)) - set_bit(HID_CTRL_RUNNING, &usbhid->iofl); - } + usb_block_urb(usbhid->urbctrl); + + /* Drop lock to avoid deadlock if the callback runs */ + spin_unlock(&usbhid->lock); + + usb_unlink_urb(usbhid->urbctrl); + spin_lock(&usbhid->lock); + usb_unblock_urb(usbhid->urbctrl); + + /* Unlink might have stopped the queue */ + if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) + usbhid_restart_ctrl_queue(usbhid); + + /* Now we can allow autosuspend again */ + usb_autopm_put_interface_async(usbhid->intf); } } @@ -1002,9 +993,10 @@ static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t co static void usbhid_restart_queues(struct usbhid_device *usbhid) { - if (usbhid->urbout) + if (usbhid->urbout && !test_bit(HID_OUT_RUNNING, &usbhid->iofl)) usbhid_restart_out_queue(usbhid); - usbhid_restart_ctrl_queue(usbhid); + if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) + usbhid_restart_ctrl_queue(usbhid); } static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid) @@ -1471,11 +1463,38 @@ void usbhid_put_power(struct hid_device *hid) #ifdef CONFIG_PM +static int hid_resume_common(struct hid_device *hid, bool driver_suspended) +{ + struct usbhid_device *usbhid = hid->driver_data; + int status; + + spin_lock_irq(&usbhid->lock); + clear_bit(HID_SUSPENDED, &usbhid->iofl); + usbhid_mark_busy(usbhid); + + if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) || + test_bit(HID_RESET_PENDING, &usbhid->iofl)) + schedule_work(&usbhid->reset_work); + usbhid->retry_delay = 0; + + usbhid_restart_queues(usbhid); + spin_unlock_irq(&usbhid->lock); + + status = hid_start_in(hid); + if (status < 0) + hid_io_error(hid); + + if (driver_suspended && hid->driver && hid->driver->resume) + status = hid->driver->resume(hid); + return status; +} + static int hid_suspend(struct usb_interface *intf, pm_message_t message) { struct hid_device *hid = usb_get_intfdata(intf); struct usbhid_device *usbhid = hid->driver_data; int status; + bool driver_suspended = false; if (PMSG_IS_AUTO(message)) { spin_lock_irq(&usbhid->lock); /* Sync with error handler */ @@ -1486,13 +1505,14 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) && !test_bit(HID_KEYS_PRESSED, &usbhid->iofl) && (!usbhid->ledcount || ignoreled)) { - set_bit(HID_REPORTED_IDLE, &usbhid->iofl); + set_bit(HID_SUSPENDED, &usbhid->iofl); spin_unlock_irq(&usbhid->lock); if (hid->driver && hid->driver->suspend) { status = hid->driver->suspend(hid, message); if (status < 0) - return status; + goto failed; } + driver_suspended = true; } else { usbhid_mark_busy(usbhid); spin_unlock_irq(&usbhid->lock); @@ -1505,11 +1525,14 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) if (status < 0) return status; } + driver_suspended = true; spin_lock_irq(&usbhid->lock); - set_bit(HID_REPORTED_IDLE, &usbhid->iofl); + set_bit(HID_SUSPENDED, &usbhid->iofl); spin_unlock_irq(&usbhid->lock); - if (usbhid_wait_io(hid) < 0) - return -EIO; + if (usbhid_wait_io(hid) < 0) { + status = -EIO; + goto failed; + } } hid_cancel_delayed_stuff(usbhid); @@ -1517,14 +1540,15 @@ static int hid_suspend(struct usb_interface *intf, pm_message_t message) if (PMSG_IS_AUTO(message) && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) { /* lost race against keypresses */ - status = hid_start_in(hid); - if (status < 0) - hid_io_error(hid); - usbhid_mark_busy(usbhid); - return -EBUSY; + status = -EBUSY; + goto failed; } dev_dbg(&intf->dev, "suspend\n"); return 0; + + failed: + hid_resume_common(hid, driver_suspended); + return status; } static int hid_resume(struct usb_interface *intf) @@ -1536,23 +1560,7 @@ static int hid_resume(struct usb_interface *intf) if (!test_bit(HID_STARTED, &usbhid->iofl)) return 0; - clear_bit(HID_REPORTED_IDLE, &usbhid->iofl); - usbhid_mark_busy(usbhid); - - if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) || - test_bit(HID_RESET_PENDING, &usbhid->iofl)) - schedule_work(&usbhid->reset_work); - usbhid->retry_delay = 0; - status = hid_start_in(hid); - if (status < 0) - hid_io_error(hid); - usbhid_restart_queues(usbhid); - - if (status >= 0 && hid->driver && hid->driver->resume) { - int ret = hid->driver->resume(hid); - if (ret < 0) - status = ret; - } + status = hid_resume_common(hid, true); dev_dbg(&intf->dev, "resume status %d\n", status); return 0; } @@ -1563,7 +1571,7 @@ static int hid_reset_resume(struct usb_interface *intf) struct usbhid_device *usbhid = hid->driver_data; int status; - clear_bit(HID_REPORTED_IDLE, &usbhid->iofl); + clear_bit(HID_SUSPENDED, &usbhid->iofl); status = hid_post_reset(intf); if (status >= 0 && hid->driver && hid->driver->reset_resume) { int ret = hid->driver->reset_resume(hid); diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h index 1883d7b9487..bd87a61e530 100644 --- a/drivers/hid/usbhid/usbhid.h +++ b/drivers/hid/usbhid/usbhid.h @@ -53,7 +53,6 @@ struct usb_interface *usbhid_find_interface(int minor); #define HID_CLEAR_HALT 6 #define HID_DISCONNECTED 7 #define HID_STARTED 8 -#define HID_REPORTED_IDLE 9 #define HID_KEYS_PRESSED 10 #define HID_NO_BANDWIDTH 11 diff --git a/include/linux/Kbuild b/include/linux/Kbuild index 8760be30b37..cb2a7d1ad47 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -376,6 +376,7 @@ header-y += tty.h header-y += types.h header-y += udf_fs_i.h header-y += udp.h +header-y += uhid.h header-y += uinput.h header-y += uio.h header-y += ultrasound.h diff --git a/include/linux/hid.h b/include/linux/hid.h index 449fa385703..42970de1b40 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -200,6 +200,7 @@ struct hid_item { #define HID_UP_DIGITIZER 0x000d0000 #define HID_UP_PID 0x000f0000 #define HID_UP_HPVENDOR 0xff7f0000 +#define HID_UP_HPVENDOR2 0xff010000 #define HID_UP_MSVENDOR 0xff000000 #define HID_UP_CUSTOM 0x00ff0000 #define HID_UP_LOGIVENDOR 0xffbc0000 diff --git a/include/linux/uhid.h b/include/linux/uhid.h new file mode 100644 index 00000000000..9c6974f1696 --- /dev/null +++ b/include/linux/uhid.h @@ -0,0 +1,104 @@ +#ifndef __UHID_H_ +#define __UHID_H_ + +/* + * 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. + */ + +/* + * Public header for user-space communication. We try to keep every structure + * aligned but to be safe we also use __attribute__((__packed__)). Therefore, + * the communication should be ABI compatible even between architectures. + */ + +#include <linux/input.h> +#include <linux/types.h> + +enum uhid_event_type { + UHID_CREATE, + UHID_DESTROY, + UHID_START, + UHID_STOP, + UHID_OPEN, + UHID_CLOSE, + UHID_OUTPUT, + UHID_OUTPUT_EV, + UHID_INPUT, + UHID_FEATURE, + UHID_FEATURE_ANSWER, +}; + +struct uhid_create_req { + __u8 name[128]; + __u8 phys[64]; + __u8 uniq[64]; + __u8 __user *rd_data; + __u16 rd_size; + + __u16 bus; + __u32 vendor; + __u32 product; + __u32 version; + __u32 country; +} __attribute__((__packed__)); + +#define UHID_DATA_MAX 4096 + +enum uhid_report_type { + UHID_FEATURE_REPORT, + UHID_OUTPUT_REPORT, + UHID_INPUT_REPORT, +}; + +struct uhid_input_req { + __u8 data[UHID_DATA_MAX]; + __u16 size; +} __attribute__((__packed__)); + +struct uhid_output_req { + __u8 data[UHID_DATA_MAX]; + __u16 size; + __u8 rtype; +} __attribute__((__packed__)); + +struct uhid_output_ev_req { + __u16 type; + __u16 code; + __s32 value; +} __attribute__((__packed__)); + +struct uhid_feature_req { + __u32 id; + __u8 rnum; + __u8 rtype; +} __attribute__((__packed__)); + +struct uhid_feature_answer_req { + __u32 id; + __u16 err; + __u16 size; + __u8 data[UHID_DATA_MAX]; +}; + +struct uhid_event { + __u32 type; + + union { + struct uhid_create_req create; + struct uhid_input_req input; + struct uhid_output_req output; + struct uhid_output_ev_req output_ev; + struct uhid_feature_req feature; + struct uhid_feature_answer_req feature_answer; + } u; +} __attribute__((__packed__)); + +#endif /* __UHID_H_ */ diff --git a/samples/uhid/Makefile b/samples/uhid/Makefile new file mode 100644 index 00000000000..c95a696560a --- /dev/null +++ b/samples/uhid/Makefile @@ -0,0 +1,10 @@ +# kbuild trick to avoid linker error. Can be omitted if a module is built. +obj- := dummy.o + +# List of programs to build +hostprogs-y := uhid-example + +# Tell kbuild to always build the programs +always := $(hostprogs-y) + +HOSTCFLAGS_uhid-example.o += -I$(objtree)/usr/include diff --git a/samples/uhid/uhid-example.c b/samples/uhid/uhid-example.c new file mode 100644 index 00000000000..03ce3c059a5 --- /dev/null +++ b/samples/uhid/uhid-example.c @@ -0,0 +1,381 @@ +/* + * UHID Example + * + * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com> + * + * The code may be used by anyone for any purpose, + * and can serve as a starting point for developing + * applications using uhid. + */ + +/* UHID Example + * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this + * program as root and then use the following keys to control the mouse: + * q: Quit the application + * 1: Toggle left button (down, up, ...) + * 2: Toggle right button + * 3: Toggle middle button + * a: Move mouse left + * d: Move mouse right + * w: Move mouse up + * s: Move mouse down + * r: Move wheel up + * f: Move wheel down + * + * If uhid is not available as /dev/uhid, then you can pass a different path as + * first argument. + * If <linux/uhid.h> is not installed in /usr, then compile this with: + * gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c + * And ignore the warning about kernel headers. However, it is recommended to + * use the installed uhid.h if available. + */ + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <linux/uhid.h> + +/* HID Report Desciptor + * We emulate a basic 3 button mouse with wheel. This is the report-descriptor + * as the kernel will parse it: + * + * INPUT[INPUT] + * Field(0) + * Physical(GenericDesktop.Pointer) + * Application(GenericDesktop.Mouse) + * Usage(3) + * Button.0001 + * Button.0002 + * Button.0003 + * Logical Minimum(0) + * Logical Maximum(1) + * Report Size(1) + * Report Count(3) + * Report Offset(0) + * Flags( Variable Absolute ) + * Field(1) + * Physical(GenericDesktop.Pointer) + * Application(GenericDesktop.Mouse) + * Usage(3) + * GenericDesktop.X + * GenericDesktop.Y + * GenericDesktop.Wheel + * Logical Minimum(-128) + * Logical Maximum(127) + * Report Size(8) + * Report Count(3) + * Report Offset(8) + * Flags( Variable Relative ) + * + * This is the mapping that we expect: + * Button.0001 ---> Key.LeftBtn + * Button.0002 ---> Key.RightBtn + * Button.0003 ---> Key.MiddleBtn + * GenericDesktop.X ---> Relative.X + * GenericDesktop.Y ---> Relative.Y + * GenericDesktop.Wheel ---> Relative.Wheel + * + * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc + * This file should print the same information as showed above. + */ + +static unsigned char rdesc[] = { + 0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, + 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, + 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, + 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01, + 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, + 0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03, + 0x81, 0x06, 0xc0, 0xc0, +}; + +static int uhid_write(int fd, const struct uhid_event *ev) +{ + ssize_t ret; + + ret = write(fd, ev, sizeof(*ev)); + if (ret < 0) { + fprintf(stderr, "Cannot write to uhid: %m\n"); + return -errno; + } else if (ret != sizeof(*ev)) { + fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n", + ret, sizeof(ev)); + return -EFAULT; + } else { + return 0; + } +} + +static int create(int fd) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char*)ev.u.create.name, "test-uhid-device"); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = 0x15d9; + ev.u.create.product = 0x0a37; + ev.u.create.version = 0; + ev.u.create.country = 0; + + return uhid_write(fd, &ev); +} + +static void destroy(int fd) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(fd, &ev); +} + +static int event(int fd) +{ + struct uhid_event ev; + ssize_t ret; + + memset(&ev, 0, sizeof(ev)); + ret = read(fd, &ev, sizeof(ev)); + if (ret == 0) { + fprintf(stderr, "Read HUP on uhid-cdev\n"); + return -EFAULT; + } else if (ret < 0) { + fprintf(stderr, "Cannot read uhid-cdev: %m\n"); + return -errno; + } else if (ret != sizeof(ev)) { + fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n", + ret, sizeof(ev)); + return -EFAULT; + } + + switch (ev.type) { + case UHID_START: + fprintf(stderr, "UHID_START from uhid-dev\n"); + break; + case UHID_STOP: + fprintf(stderr, "UHID_STOP from uhid-dev\n"); + break; + case UHID_OPEN: + fprintf(stderr, "UHID_OPEN from uhid-dev\n"); + break; + case UHID_CLOSE: + fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); + break; + case UHID_OUTPUT: + fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); + break; + case UHID_OUTPUT_EV: + fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n"); + break; + default: + fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); + } + + return 0; +} + +static bool btn1_down; +static bool btn2_down; +static bool btn3_down; +static signed char abs_hor; +static signed char abs_ver; +static signed char wheel; + +static int send_event(int fd) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + ev.u.input.size = 4; + + if (btn1_down) + ev.u.input.data[0] |= 0x1; + if (btn2_down) + ev.u.input.data[0] |= 0x2; + if (btn3_down) + ev.u.input.data[0] |= 0x4; + + ev.u.input.data[1] = abs_hor; + ev.u.input.data[2] = abs_ver; + ev.u.input.data[3] = wheel; + + return uhid_write(fd, &ev); +} + +static int keyboard(int fd) +{ + char buf[128]; + ssize_t ret, i; + + ret = read(STDIN_FILENO, buf, sizeof(buf)); + if (ret == 0) { + fprintf(stderr, "Read HUP on stdin\n"); + return -EFAULT; + } else if (ret < 0) { + fprintf(stderr, "Cannot read stdin: %m\n"); + return -errno; + } + + for (i = 0; i < ret; ++i) { + switch (buf[i]) { + case '1': + btn1_down = !btn1_down; + ret = send_event(fd); + if (ret) + return ret; + break; + case '2': + btn2_down = !btn2_down; + ret = send_event(fd); + if (ret) + return ret; + break; + case '3': + btn3_down = !btn3_down; + ret = send_event(fd); + if (ret) + return ret; + break; + case 'a': + abs_hor = -20; + ret = send_event(fd); + abs_hor = 0; + if (ret) + return ret; + break; + case 'd': + abs_hor = 20; + ret = send_event(fd); + abs_hor = 0; + if (ret) + return ret; + break; + case 'w': + abs_ver = -20; + ret = send_event(fd); + abs_ver = 0; + if (ret) + return ret; + break; + case 's': + abs_ver = 20; + ret = send_event(fd); + abs_ver = 0; + if (ret) + return ret; + break; + case 'r': + wheel = 1; + ret = send_event(fd); + wheel = 0; + if (ret) + return ret; + break; + case 'f': + wheel = -1; + ret = send_event(fd); + wheel = 0; + if (ret) + return ret; + break; + case 'q': + return -ECANCELED; + default: + fprintf(stderr, "Invalid input: %c\n", buf[i]); + } + } + + return 0; +} + +int main(int argc, char **argv) +{ + int fd; + const char *path = "/dev/uhid"; + struct pollfd pfds[2]; + int ret; + struct termios state; + + ret = tcgetattr(STDIN_FILENO, &state); + if (ret) { + fprintf(stderr, "Cannot get tty state\n"); + } else { + state.c_lflag &= ~ICANON; + state.c_cc[VMIN] = 1; + ret = tcsetattr(STDIN_FILENO, TCSANOW, &state); + if (ret) + fprintf(stderr, "Cannot set tty state\n"); + } + + if (argc >= 2) { + if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { + fprintf(stderr, "Usage: %s [%s]\n", argv[0], path); + return EXIT_SUCCESS; + } else { + path = argv[1]; + } + } + + fprintf(stderr, "Open uhid-cdev %s\n", path); + fd = open(path, O_RDWR | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path); + return EXIT_FAILURE; + } + + fprintf(stderr, "Create uhid device\n"); + ret = create(fd); + if (ret) { + close(fd); + return EXIT_FAILURE; + } + + pfds[0].fd = STDIN_FILENO; + pfds[0].events = POLLIN; + pfds[1].fd = fd; + pfds[1].events = POLLIN; + + fprintf(stderr, "Press 'q' to quit...\n"); + while (1) { + ret = poll(pfds, 2, -1); + if (ret < 0) { + fprintf(stderr, "Cannot poll for fds: %m\n"); + break; + } + if (pfds[0].revents & POLLHUP) { + fprintf(stderr, "Received HUP on stdin\n"); + break; + } + if (pfds[1].revents & POLLHUP) { + fprintf(stderr, "Received HUP on uhid-cdev\n"); + break; + } + + if (pfds[0].revents & POLLIN) { + ret = keyboard(fd); + if (ret) + break; + } + if (pfds[1].revents & POLLIN) { + ret = event(fd); + if (ret) + break; + } + } + + fprintf(stderr, "Destroy uhid device\n"); + destroy(fd); + return EXIT_SUCCESS; +} |