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
  ...
This commit is contained in:
Linus Torvalds 2012-07-24 13:30:14 -07:00
commit e8ff13b0bf
40 changed files with 2979 additions and 550 deletions

View File

@ -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).

View File

@ -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

169
Documentation/hid/uhid.txt Normal file
View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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) },

View File

@ -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 },
{ }

View File

@ -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");

View File

@ -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

View File

@ -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;

View File

@ -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");

View File

@ -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);
__set_bit(EV_ABS, input->evbit);
/* 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);
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);
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);
/* 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_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_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_set_events_per_packet(input, 60);
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,

View File

@ -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,

View File

@ -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;

View File

@ -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);

View File

@ -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");

View File

@ -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

View File

@ -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;

View File

@ -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 */

View File

@ -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;

View File

@ -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)

View File

@ -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,

View File

@ -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));
}

View File

@ -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,

View File

@ -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));
}

View File

@ -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,

View File

@ -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");

View File

@ -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

View File

@ -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;

View File

@ -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);
}

572
drivers/hid/uhid.c Normal file
View File

@ -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");

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

104
include/linux/uhid.h Normal file
View File

@ -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_ */

10
samples/uhid/Makefile Normal file
View File

@ -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

381
samples/uhid/uhid-example.c Normal file
View File

@ -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;
}