mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 03:24:45 +01:00
hid-for-linus-2025093001
-----BEGIN PGP SIGNATURE-----
iQJHBAABCgAxFiEEoEVH9lhNrxiMPSyI7MXwXhnZSjYFAmjb8vETHGJlbnRpc3NA
a2VybmVsLm9yZwAKCRDsxfBeGdlKNhMhD/42hnNxVA+pRYUdwgX+5fGPkKpM6ayS
z627qOGgFsrwidipRmx68VZpHnKTpZMKyqxYQYEQ0SExVviXaIohQGu04OF+N+En
3fo9zHJiXxnlxVv9R+7LiV/S74yN1nLM+nURnvG7NjIqOeLfxO0NnNdh+MIGmYzv
OdUdAO7p84F5aadKPjyFTHbO7Y2bAT2XT0xTJR+SZ7MnP8rWRbN3l/BfrkTL+D3X
Bhg9Xdgoqst6f1Be5m6oYthHaXbs3yjqGYMRkhFRKlEsd/ArBRudyMzqAQ602WMl
gHMcHwpc5ztJTIOBZU6QzBAbJBozrPWzr6Lvih3jE2HZBs7B2mw0NcnE4+7xkaYs
llNcrwlBTRuS0qwvrJcNvstV7z9toCx4PjVCYiMhTAdkq1m3J1KqCAPA5OvIELLq
xXjHmmNRYe9bvAik4cuVxeuw5azvgxYd/lFJx7OmENTw/pczXot5N3+bAqdl+4ka
yFF+yUvpYTdrYXQmj74FxJODlErHUSRxNtFeAk6nJ1HLZB/lcl6XF9YnCqpE4TVe
WPzuzrpoJvOeUvqBHtXYQ2/g8argaOAQu62NpfjF8/OA1nBF4Jg/n/lRGy4Mmmec
ar7w4+3y72KSVmLJ7F775Kakrod/gT8tA1RPtHGFFXbayGqw6Cz9E6E0P0q08n0y
v0CUWASm2YIEWA==
=8Nwa
-----END PGP SIGNATURE-----
Merge tag 'hid-for-linus-2025093001' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid
Pull HID updates from Benjamin Tissoires:
- haptic touchpad support (Angela Czubak and Jonathan Denose)
- support for audio jack handling on DualSense Playstation controllers
(Cristian Ciocaltea)
- allow HID-BPF to rebind a driver to hid-multitouch (Benjamin
Tissoires)
- rework hidraw ioctls to make them safer (and tested) (Benjamin
Tissoires)
- various PIDFF and universal-PIDFF fixes/improvements (Tomasz Pakuła)
- better configuration of Intel QuickI2C through ACPI (Xinpeng Sun)
- other assorted cleanups and fixes
* tag 'hid-for-linus-2025093001' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (58 commits)
HID: playstation: Switch to scoped_guard() in {dualsense|dualshock4}_output_worker()
HID: playstation: Silence sparse warnings for locking context imbalances
HID: playstation: Update SP preamp gain comment line
HID: intel-thc-hid: intel-quicki2c: support ACPI config for advanced features
HID: core: Change hid_driver to use a const char* for name
HID: hidraw: tighten ioctl command parsing
selftests/hid: hidraw: forge wrong ioctls and tests them
selftests/hid: hidraw: add more coverage for hidraw ioctls
selftests/hid: update vmtest.sh for virtme-ng
HID: playstation: Support DualSense audio jack event reporting
HID: playstation: Support DualSense audio jack hotplug detection
HID: playstation: Redefine DualSense input report status field
HID: playstation: Prefer kzalloc(sizeof(*buf)...)
HID: playstation: Document spinlock_t usage
HID: playstation: Fix all alignment and line length issues
HID: playstation: Correct spelling in comment sections
HID: playstation: Replace uint{32,16,8}_t with u{32,16,8}
HID: playstation: Simplify locking with guard() and scoped_guard()
HID: playstation: Add spaces around arithmetic operators
HID: playstation: Make use of bitfield macros
...
This commit is contained in:
commit
54ba6d9b13
34 changed files with 3166 additions and 1317 deletions
|
|
@ -400,6 +400,20 @@ can report through the rotational axes (absolute and/or relative rx, ry, rz).
|
|||
All other axes retain their meaning. A device must not mix
|
||||
regular directional axes and accelerometer axes on the same event node.
|
||||
|
||||
INPUT_PROP_HAPTIC_TOUCHPAD
|
||||
--------------------------
|
||||
|
||||
The INPUT_PROP_HAPTIC_TOUCHPAD property indicates that device:
|
||||
- supports simple haptic auto and manual triggering
|
||||
- can differentiate between at least 5 fingers
|
||||
- uses correct resolution for the X/Y (units and value)
|
||||
- reports correct force per touch, and correct units for them (newtons or grams)
|
||||
- follows the MT protocol type B
|
||||
|
||||
Summing up, such devices follow the MS spec for input devices in
|
||||
Win8 and Win8.1, and in addition support the Simple haptic controller HID table,
|
||||
and report correct units for the pressure.
|
||||
|
||||
Guidelines
|
||||
==========
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,17 @@ config HID_GENERIC
|
|||
|
||||
If unsure, say Y.
|
||||
|
||||
config HID_HAPTIC
|
||||
tristate "Haptic touchpad support"
|
||||
default n
|
||||
help
|
||||
Support for touchpads with force sensors and haptic actuators instead of a
|
||||
traditional button.
|
||||
Adds extra parsing and FF device for the hid multitouch driver.
|
||||
It can be used for Elan 2703 haptic touchpad.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
menu "Special HID drivers"
|
||||
|
||||
config HID_A4TECH
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
hid-y := hid-core.o hid-input.o hid-quirks.o
|
||||
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
|
||||
hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
|
||||
|
||||
obj-$(CONFIG_HID_BPF) += bpf/
|
||||
|
||||
|
|
|
|||
|
|
@ -1387,9 +1387,6 @@ static const struct hid_device_id asus_devices[] = {
|
|||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
|
|
@ -1419,6 +1416,9 @@ static const struct hid_device_id asus_devices[] = {
|
|||
* Note bind to the HID_GROUP_GENERIC group, so that we only bind to the keyboard
|
||||
* part, while letting hid-multitouch.c handle the touchpad.
|
||||
*/
|
||||
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
|
||||
USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
|
||||
USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -943,6 +943,15 @@ static int hid_scan_report(struct hid_device *hid)
|
|||
parser->device = hid;
|
||||
hid->group = HID_GROUP_GENERIC;
|
||||
|
||||
/*
|
||||
* In case we are re-scanning after a BPF has been loaded,
|
||||
* we need to use the bpf report descriptor, not the original one.
|
||||
*/
|
||||
if (hid->bpf_rdesc && hid->bpf_rsize) {
|
||||
start = hid->bpf_rdesc;
|
||||
end = start + hid->bpf_rsize;
|
||||
}
|
||||
|
||||
/*
|
||||
* The parsing is simpler than the one in hid_open_report() as we should
|
||||
* be robust against hid errors. Those errors will be raised by
|
||||
|
|
@ -2708,12 +2717,32 @@ static bool hid_check_device_match(struct hid_device *hdev,
|
|||
return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
|
||||
}
|
||||
|
||||
static void hid_set_group(struct hid_device *hdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (hid_ignore_special_drivers) {
|
||||
hdev->group = HID_GROUP_GENERIC;
|
||||
} else if (!hdev->group &&
|
||||
!(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
|
||||
ret = hid_scan_report(hdev);
|
||||
if (ret)
|
||||
hid_warn(hdev, "bad device descriptor (%d)\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
||||
{
|
||||
const struct hid_device_id *id;
|
||||
int ret;
|
||||
|
||||
if (!hdev->bpf_rsize) {
|
||||
/* we keep a reference to the currently scanned report descriptor */
|
||||
const __u8 *original_rdesc = hdev->bpf_rdesc;
|
||||
|
||||
if (!original_rdesc)
|
||||
original_rdesc = hdev->dev_rdesc;
|
||||
|
||||
/* in case a bpf program gets detached, we need to free the old one */
|
||||
hid_free_bpf_rdesc(hdev);
|
||||
|
||||
|
|
@ -2723,6 +2752,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
|||
/* call_hid_bpf_rdesc_fixup will always return a valid pointer */
|
||||
hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc,
|
||||
&hdev->bpf_rsize);
|
||||
|
||||
/* the report descriptor changed, we need to re-scan it */
|
||||
if (original_rdesc != hdev->bpf_rdesc) {
|
||||
hdev->group = 0;
|
||||
hid_set_group(hdev);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hid_check_device_match(hdev, hdrv, &id))
|
||||
|
|
@ -2903,14 +2938,7 @@ int hid_add_device(struct hid_device *hdev)
|
|||
/*
|
||||
* Scan generic devices for group information
|
||||
*/
|
||||
if (hid_ignore_special_drivers) {
|
||||
hdev->group = HID_GROUP_GENERIC;
|
||||
} else if (!hdev->group &&
|
||||
!(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
|
||||
ret = hid_scan_report(hdev);
|
||||
if (ret)
|
||||
hid_warn(hdev, "bad device descriptor (%d)\n", ret);
|
||||
}
|
||||
hid_set_group(hdev);
|
||||
|
||||
hdev->id = atomic_inc_return(&id);
|
||||
|
||||
|
|
|
|||
580
drivers/hid/hid-haptic.c
Normal file
580
drivers/hid/hid-haptic.c
Normal file
|
|
@ -0,0 +1,580 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID Haptic support for Linux
|
||||
*
|
||||
* Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
|
||||
*/
|
||||
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "hid-haptic.h"
|
||||
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage *usage)
|
||||
{
|
||||
u16 usage_hid;
|
||||
|
||||
if (usage->hid == HID_HP_AUTOTRIGGER) {
|
||||
if (usage->usage_index >= field->report_count) {
|
||||
dev_err(&hdev->dev,
|
||||
"HID_HP_AUTOTRIGGER out of range\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hid_device_io_start(hdev);
|
||||
hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT);
|
||||
hid_hw_wait(hdev);
|
||||
hid_device_io_stop(hdev);
|
||||
haptic->default_auto_trigger =
|
||||
field->value[usage->usage_index];
|
||||
haptic->auto_trigger_report = field->report;
|
||||
} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) {
|
||||
usage_hid = usage->hid & HID_USAGE;
|
||||
switch (field->logical) {
|
||||
case HID_HP_WAVEFORMLIST:
|
||||
if (usage_hid > haptic->max_waveform_id)
|
||||
haptic->max_waveform_id = usage_hid;
|
||||
break;
|
||||
case HID_HP_DURATIONLIST:
|
||||
if (usage_hid > haptic->max_duration_id)
|
||||
haptic->max_duration_id = usage_hid;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
|
||||
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field)
|
||||
{
|
||||
if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) {
|
||||
haptic->force_logical_minimum = field->logical_minimum;
|
||||
haptic->force_physical_minimum = field->physical_minimum;
|
||||
haptic->force_resolution = input_abs_get_res(hi->input,
|
||||
ABS_MT_PRESSURE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
|
||||
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
if (usage->hid == HID_HP_MANUALTRIGGER) {
|
||||
haptic->manual_trigger_report = field->report;
|
||||
/* we don't really want to map these fields */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_mapping);
|
||||
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
|
||||
if (hi->application == HID_DG_TOUCHPAD) {
|
||||
if (haptic->auto_trigger_report &&
|
||||
haptic->manual_trigger_report) {
|
||||
__set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
|
||||
|
||||
static void parse_auto_trigger_field(struct hid_haptic_device *haptic,
|
||||
struct hid_field *field)
|
||||
{
|
||||
int count = field->report_count;
|
||||
int n;
|
||||
u16 usage_hid;
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
switch (field->usage[n].hid & HID_USAGE_PAGE) {
|
||||
case HID_UP_ORDINAL:
|
||||
usage_hid = field->usage[n].hid & HID_USAGE;
|
||||
switch (field->logical) {
|
||||
case HID_HP_WAVEFORMLIST:
|
||||
haptic->hid_usage_map[usage_hid] = field->value[n];
|
||||
if (field->value[n] ==
|
||||
(HID_HP_WAVEFORMPRESS & HID_USAGE)) {
|
||||
haptic->press_ordinal = usage_hid;
|
||||
} else if (field->value[n] ==
|
||||
(HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
|
||||
haptic->release_ordinal = usage_hid;
|
||||
}
|
||||
break;
|
||||
case HID_HP_DURATIONLIST:
|
||||
haptic->duration_map[usage_hid] =
|
||||
field->value[n];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case HID_UP_HAPTIC:
|
||||
switch (field->usage[n].hid) {
|
||||
case HID_HP_WAVEFORMVENDORID:
|
||||
haptic->vendor_id = field->value[n];
|
||||
break;
|
||||
case HID_HP_WAVEFORMVENDORPAGE:
|
||||
haptic->vendor_page = field->value[n];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Should not really happen */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_effect_buf(struct hid_haptic_device *haptic,
|
||||
struct ff_haptic_effect *effect,
|
||||
struct hid_haptic_effect *haptic_effect,
|
||||
int waveform_ordinal)
|
||||
{
|
||||
struct hid_report *rep = haptic->manual_trigger_report;
|
||||
struct hid_usage *usage;
|
||||
struct hid_field *field;
|
||||
s32 value;
|
||||
int i, j;
|
||||
u8 *buf = haptic_effect->report_buf;
|
||||
|
||||
mutex_lock(&haptic->manual_trigger_mutex);
|
||||
for (i = 0; i < rep->maxfield; i++) {
|
||||
field = rep->field[i];
|
||||
/* Ignore if report count is out of bounds. */
|
||||
if (field->report_count < 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < field->maxusage; j++) {
|
||||
usage = &field->usage[j];
|
||||
|
||||
switch (usage->hid) {
|
||||
case HID_HP_INTENSITY:
|
||||
if (effect->intensity > 100) {
|
||||
value = field->logical_maximum;
|
||||
} else {
|
||||
value = field->logical_minimum +
|
||||
effect->intensity *
|
||||
(field->logical_maximum -
|
||||
field->logical_minimum) / 100;
|
||||
}
|
||||
break;
|
||||
case HID_HP_REPEATCOUNT:
|
||||
value = effect->repeat_count;
|
||||
break;
|
||||
case HID_HP_RETRIGGERPERIOD:
|
||||
value = effect->retrigger_period;
|
||||
break;
|
||||
case HID_HP_MANUALTRIGGER:
|
||||
value = waveform_ordinal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
field->value[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
hid_output_report(rep, buf);
|
||||
mutex_unlock(&haptic->manual_trigger_mutex);
|
||||
}
|
||||
|
||||
static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
int mode)
|
||||
{
|
||||
struct hid_report *rep = haptic->auto_trigger_report;
|
||||
struct hid_field *field;
|
||||
s32 value;
|
||||
int i, j;
|
||||
|
||||
if (mode == HID_HAPTIC_MODE_HOST)
|
||||
value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
|
||||
else
|
||||
value = haptic->default_auto_trigger;
|
||||
|
||||
mutex_lock(&haptic->auto_trigger_mutex);
|
||||
for (i = 0; i < rep->maxfield; i++) {
|
||||
field = rep->field[i];
|
||||
/* Ignore if report count is out of bounds. */
|
||||
if (field->report_count < 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < field->maxusage; j++) {
|
||||
if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
|
||||
field->value[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* send the report */
|
||||
hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
|
||||
mutex_unlock(&haptic->auto_trigger_mutex);
|
||||
haptic->mode = mode;
|
||||
}
|
||||
|
||||
static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
struct ff_effect *old)
|
||||
{
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct ff_device *ff = dev->ff;
|
||||
struct hid_haptic_device *haptic = ff->private;
|
||||
int i, ordinal = 0;
|
||||
bool switch_modes = false;
|
||||
|
||||
/* If vendor range, check vendor id and page */
|
||||
if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
|
||||
effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
|
||||
(effect->u.haptic.vendor_id != haptic->vendor_id ||
|
||||
effect->u.haptic.vendor_waveform_page != haptic->vendor_page))
|
||||
return -EINVAL;
|
||||
|
||||
/* Check hid_usage */
|
||||
for (i = 1; i <= haptic->max_waveform_id; i++) {
|
||||
if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) {
|
||||
ordinal = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ordinal < 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Fill the buffer for the effect id */
|
||||
fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id],
|
||||
ordinal);
|
||||
|
||||
if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
|
||||
effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
|
||||
switch_modes = true;
|
||||
|
||||
/* If device is in autonomous mode, and the uploaded effect signals userspace
|
||||
* wants control of the device, change modes
|
||||
*/
|
||||
if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
struct hid_haptic_effect *effect)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_hw_output_report(hdev, effect->report_buf,
|
||||
haptic->manual_trigger_report_len);
|
||||
if (ret < 0) {
|
||||
ret = hid_hw_raw_request(hdev,
|
||||
haptic->manual_trigger_report->id,
|
||||
effect->report_buf,
|
||||
haptic->manual_trigger_report_len,
|
||||
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void haptic_work_handler(struct work_struct *work)
|
||||
{
|
||||
|
||||
struct hid_haptic_effect *effect = container_of(work,
|
||||
struct hid_haptic_effect,
|
||||
work);
|
||||
struct input_dev *dev = effect->input_dev;
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
|
||||
mutex_lock(&haptic->manual_trigger_mutex);
|
||||
if (effect != &haptic->stop_effect)
|
||||
play_effect(hdev, haptic, &haptic->stop_effect);
|
||||
|
||||
play_effect(hdev, haptic, effect);
|
||||
mutex_unlock(&haptic->manual_trigger_mutex);
|
||||
|
||||
}
|
||||
|
||||
static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value)
|
||||
{
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
|
||||
if (value)
|
||||
queue_work(haptic->wq, &haptic->effect[effect_id].work);
|
||||
else
|
||||
queue_work(haptic->wq, &haptic->stop_effect.work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void effect_set_default(struct ff_effect *effect)
|
||||
{
|
||||
effect->type = FF_HAPTIC;
|
||||
effect->id = -1;
|
||||
effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
|
||||
effect->u.haptic.intensity = 100;
|
||||
effect->u.haptic.retrigger_period = 0;
|
||||
effect->u.haptic.repeat_count = 0;
|
||||
}
|
||||
|
||||
static int hid_haptic_erase(struct input_dev *dev, int effect_id)
|
||||
{
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct ff_effect effect;
|
||||
int ordinal;
|
||||
|
||||
effect_set_default(&effect);
|
||||
|
||||
if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
|
||||
ordinal = haptic->release_ordinal;
|
||||
if (!ordinal) {
|
||||
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
|
||||
if (haptic->mode == HID_HAPTIC_MODE_HOST)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
|
||||
} else
|
||||
effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
|
||||
|
||||
fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
|
||||
ordinal);
|
||||
} else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
|
||||
ordinal = haptic->press_ordinal;
|
||||
if (!ordinal) {
|
||||
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
|
||||
if (haptic->mode == HID_HAPTIC_MODE_HOST)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
|
||||
}
|
||||
else
|
||||
effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
|
||||
|
||||
fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
|
||||
ordinal);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hid_haptic_destroy(struct ff_device *ff)
|
||||
{
|
||||
struct hid_haptic_device *haptic = ff->private;
|
||||
struct hid_device *hdev = haptic->hdev;
|
||||
int r;
|
||||
|
||||
if (hdev)
|
||||
put_device(&hdev->dev);
|
||||
|
||||
kfree(haptic->stop_effect.report_buf);
|
||||
haptic->stop_effect.report_buf = NULL;
|
||||
|
||||
if (haptic->effect) {
|
||||
for (r = 0; r < ff->max_effects; r++)
|
||||
kfree(haptic->effect[r].report_buf);
|
||||
kfree(haptic->effect);
|
||||
}
|
||||
haptic->effect = NULL;
|
||||
|
||||
destroy_workqueue(haptic->wq);
|
||||
haptic->wq = NULL;
|
||||
|
||||
kfree(haptic->duration_map);
|
||||
haptic->duration_map = NULL;
|
||||
|
||||
kfree(haptic->hid_usage_map);
|
||||
haptic->hid_usage_map = NULL;
|
||||
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
int hid_haptic_init(struct hid_device *hdev,
|
||||
struct hid_haptic_device **haptic_ptr)
|
||||
{
|
||||
struct hid_haptic_device *haptic = *haptic_ptr;
|
||||
struct input_dev *dev = NULL;
|
||||
struct hid_input *hidinput;
|
||||
struct ff_device *ff;
|
||||
int ret = 0, r;
|
||||
struct ff_haptic_effect stop_effect = {
|
||||
.hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE,
|
||||
};
|
||||
const char *prefix = "hid-haptic";
|
||||
char *name;
|
||||
int (*flush)(struct input_dev *dev, struct file *file);
|
||||
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
|
||||
|
||||
haptic->hdev = hdev;
|
||||
haptic->max_waveform_id = max(2u, haptic->max_waveform_id);
|
||||
haptic->max_duration_id = max(2u, haptic->max_duration_id);
|
||||
|
||||
haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1,
|
||||
sizeof(u16), GFP_KERNEL);
|
||||
if (!haptic->hid_usage_map) {
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
haptic->duration_map = kcalloc(haptic->max_duration_id + 1,
|
||||
sizeof(u32), GFP_KERNEL);
|
||||
if (!haptic->duration_map) {
|
||||
ret = -ENOMEM;
|
||||
goto usage_map;
|
||||
}
|
||||
|
||||
if (haptic->max_waveform_id != haptic->max_duration_id)
|
||||
dev_warn(&hdev->dev,
|
||||
"Haptic duration and waveform lists have different max id (%u and %u).\n",
|
||||
haptic->max_duration_id, haptic->max_waveform_id);
|
||||
|
||||
haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] =
|
||||
HID_HP_WAVEFORMNONE & HID_USAGE;
|
||||
haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
|
||||
HID_HP_WAVEFORMSTOP & HID_USAGE;
|
||||
|
||||
mutex_init(&haptic->auto_trigger_mutex);
|
||||
for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
|
||||
parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
|
||||
|
||||
list_for_each_entry(hidinput, &hdev->inputs, list) {
|
||||
if (hidinput->application == HID_DG_TOUCHPAD) {
|
||||
dev = hidinput->input;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
dev_err(&hdev->dev, "Failed to find the input device\n");
|
||||
ret = -ENODEV;
|
||||
goto duration_map;
|
||||
}
|
||||
|
||||
haptic->input_dev = dev;
|
||||
haptic->manual_trigger_report_len =
|
||||
hid_report_len(haptic->manual_trigger_report);
|
||||
mutex_init(&haptic->manual_trigger_mutex);
|
||||
name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL);
|
||||
if (name) {
|
||||
sprintf(name, "%s %s", prefix, hdev->name);
|
||||
haptic->wq = create_singlethread_workqueue(name);
|
||||
kfree(name);
|
||||
}
|
||||
if (!haptic->wq) {
|
||||
ret = -ENOMEM;
|
||||
goto duration_map;
|
||||
}
|
||||
haptic->effect = kcalloc(FF_MAX_EFFECTS,
|
||||
sizeof(struct hid_haptic_effect), GFP_KERNEL);
|
||||
if (!haptic->effect) {
|
||||
ret = -ENOMEM;
|
||||
goto output_queue;
|
||||
}
|
||||
for (r = 0; r < FF_MAX_EFFECTS; r++) {
|
||||
haptic->effect[r].report_buf =
|
||||
hid_alloc_report_buf(haptic->manual_trigger_report,
|
||||
GFP_KERNEL);
|
||||
if (!haptic->effect[r].report_buf) {
|
||||
dev_err(&hdev->dev,
|
||||
"Failed to allocate a buffer for an effect.\n");
|
||||
ret = -ENOMEM;
|
||||
goto buffer_free;
|
||||
}
|
||||
haptic->effect[r].input_dev = dev;
|
||||
INIT_WORK(&haptic->effect[r].work, haptic_work_handler);
|
||||
}
|
||||
haptic->stop_effect.report_buf =
|
||||
hid_alloc_report_buf(haptic->manual_trigger_report,
|
||||
GFP_KERNEL);
|
||||
if (!haptic->stop_effect.report_buf) {
|
||||
dev_err(&hdev->dev,
|
||||
"Failed to allocate a buffer for stop effect.\n");
|
||||
ret = -ENOMEM;
|
||||
goto buffer_free;
|
||||
}
|
||||
haptic->stop_effect.input_dev = dev;
|
||||
INIT_WORK(&haptic->stop_effect.work, haptic_work_handler);
|
||||
fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect,
|
||||
HID_HAPTIC_ORDINAL_WAVEFORMSTOP);
|
||||
|
||||
input_set_capability(dev, EV_FF, FF_HAPTIC);
|
||||
|
||||
flush = dev->flush;
|
||||
event = dev->event;
|
||||
ret = input_ff_create(dev, FF_MAX_EFFECTS);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "Failed to create ff device.\n");
|
||||
goto stop_buffer_free;
|
||||
}
|
||||
|
||||
ff = dev->ff;
|
||||
ff->private = haptic;
|
||||
ff->upload = hid_haptic_upload_effect;
|
||||
ff->playback = hid_haptic_playback;
|
||||
ff->erase = hid_haptic_erase;
|
||||
ff->destroy = hid_haptic_destroy;
|
||||
if (!try_module_get(THIS_MODULE)) {
|
||||
dev_err(&hdev->dev, "Failed to increase module count.\n");
|
||||
goto input_free;
|
||||
}
|
||||
if (!get_device(&hdev->dev)) {
|
||||
dev_err(&hdev->dev, "Failed to get hdev device.\n");
|
||||
module_put(THIS_MODULE);
|
||||
goto input_free;
|
||||
}
|
||||
return 0;
|
||||
|
||||
input_free:
|
||||
input_ff_destroy(dev);
|
||||
/* Do not let double free happen, input_ff_destroy will call
|
||||
* hid_haptic_destroy.
|
||||
*/
|
||||
*haptic_ptr = NULL;
|
||||
/* Restore dev flush and event */
|
||||
dev->flush = flush;
|
||||
dev->event = event;
|
||||
return ret;
|
||||
stop_buffer_free:
|
||||
kfree(haptic->stop_effect.report_buf);
|
||||
haptic->stop_effect.report_buf = NULL;
|
||||
buffer_free:
|
||||
while (--r >= 0)
|
||||
kfree(haptic->effect[r].report_buf);
|
||||
kfree(haptic->effect);
|
||||
haptic->effect = NULL;
|
||||
output_queue:
|
||||
destroy_workqueue(haptic->wq);
|
||||
haptic->wq = NULL;
|
||||
duration_map:
|
||||
kfree(haptic->duration_map);
|
||||
haptic->duration_map = NULL;
|
||||
usage_map:
|
||||
kfree(haptic->hid_usage_map);
|
||||
haptic->hid_usage_map = NULL;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_init);
|
||||
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
|
||||
{
|
||||
haptic->pressure_sum = 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset);
|
||||
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure)
|
||||
{
|
||||
haptic->pressure_sum += pressure;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase);
|
||||
127
drivers/hid/hid-haptic.h
Normal file
127
drivers/hid/hid-haptic.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* HID Haptic support for Linux
|
||||
*
|
||||
* Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
|
||||
*/
|
||||
|
||||
#include <linux/hid.h>
|
||||
|
||||
#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1
|
||||
#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2
|
||||
|
||||
#define HID_HAPTIC_MODE_DEVICE 0
|
||||
#define HID_HAPTIC_MODE_HOST 1
|
||||
|
||||
struct hid_haptic_effect {
|
||||
u8 *report_buf;
|
||||
struct input_dev *input_dev;
|
||||
struct work_struct work;
|
||||
struct list_head control;
|
||||
struct mutex control_mutex;
|
||||
};
|
||||
|
||||
struct hid_haptic_effect_node {
|
||||
struct list_head node;
|
||||
struct file *file;
|
||||
};
|
||||
|
||||
struct hid_haptic_device {
|
||||
struct input_dev *input_dev;
|
||||
struct hid_device *hdev;
|
||||
struct hid_report *auto_trigger_report;
|
||||
struct mutex auto_trigger_mutex;
|
||||
struct workqueue_struct *wq;
|
||||
struct hid_report *manual_trigger_report;
|
||||
struct mutex manual_trigger_mutex;
|
||||
size_t manual_trigger_report_len;
|
||||
int pressed_state;
|
||||
s32 pressure_sum;
|
||||
s32 force_logical_minimum;
|
||||
s32 force_physical_minimum;
|
||||
s32 force_resolution;
|
||||
u32 mode;
|
||||
u32 default_auto_trigger;
|
||||
u32 vendor_page;
|
||||
u32 vendor_id;
|
||||
u32 max_waveform_id;
|
||||
u32 max_duration_id;
|
||||
u16 *hid_usage_map;
|
||||
u32 *duration_map;
|
||||
u16 press_ordinal;
|
||||
u16 release_ordinal;
|
||||
struct hid_haptic_effect *effect;
|
||||
struct hid_haptic_effect stop_effect;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_HID_HAPTIC)
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage
|
||||
*usage);
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field);
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max);
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi);
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure);
|
||||
#else
|
||||
static inline
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage
|
||||
*usage)
|
||||
{}
|
||||
static inline
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
|
||||
{}
|
||||
static inline
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
|
||||
static inline
|
||||
bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
|
||||
static inline
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure)
|
||||
{}
|
||||
#endif
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO 0x1a30
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c
|
||||
|
|
@ -1296,6 +1296,8 @@
|
|||
|
||||
#define USB_VENDOR_ID_STEELSERIES 0x1038
|
||||
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_1 0x12b6
|
||||
#define USB_DEVICE_ID_STEELSERIES_ARCTIS_9 0x12c2
|
||||
|
||||
#define USB_VENDOR_ID_SUN 0x0430
|
||||
#define USB_DEVICE_ID_RARITAN_KVM_DONGLE 0xcdab
|
||||
|
|
|
|||
|
|
@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
|
|||
}
|
||||
break;
|
||||
|
||||
case ABS_PRESSURE:
|
||||
case ABS_MT_PRESSURE:
|
||||
if (field->unit == HID_UNIT_NEWTON) {
|
||||
/* Convert to grams, 1 newton is 101.97 grams */
|
||||
prev = physical_extents;
|
||||
physical_extents *= 10197;
|
||||
if (physical_extents < prev)
|
||||
return 0;
|
||||
unit_exponent -= 2;
|
||||
} else if (field->unit != HID_UNIT_GRAM) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -683,9 +696,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
|||
if (field->report_count < 1)
|
||||
goto ignore;
|
||||
|
||||
/* only LED usages are supported in output fields */
|
||||
/* only LED and HAPTIC usages are supported in output fields */
|
||||
if (field->report_type == HID_OUTPUT_REPORT &&
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED &&
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) {
|
||||
goto ignore;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
|
|||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#include "hid-haptic.h"
|
||||
|
||||
/* quirks to control the device */
|
||||
#define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0)
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTID BIT(1)
|
||||
|
|
@ -168,11 +170,13 @@ struct mt_report_data {
|
|||
struct mt_device {
|
||||
struct mt_class mtclass; /* our mt device class */
|
||||
struct timer_list release_timer; /* to release sticky fingers */
|
||||
struct hid_haptic_device *haptic; /* haptic related configuration */
|
||||
struct hid_device *hdev; /* hid_device we're attached to */
|
||||
unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_*) */
|
||||
__u8 inputmode_value; /* InputMode HID feature value */
|
||||
__u8 maxcontacts;
|
||||
bool is_buttonpad; /* is this device a button pad? */
|
||||
bool is_haptic_touchpad; /* is this device a haptic touchpad? */
|
||||
bool serial_maybe; /* need to check for serial protocol */
|
||||
|
||||
struct list_head applications;
|
||||
|
|
@ -533,6 +537,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
|
|||
mt_get_feature(hdev, field->report);
|
||||
break;
|
||||
}
|
||||
|
||||
hid_haptic_feature_mapping(hdev, td->haptic, field, usage);
|
||||
}
|
||||
|
||||
static void set_abs(struct input_dev *input, unsigned int code,
|
||||
|
|
@ -888,6 +894,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
case HID_DG_TIPPRESSURE:
|
||||
set_abs(hi->input, ABS_MT_PRESSURE, field,
|
||||
cls->sn_pressure);
|
||||
td->is_haptic_touchpad =
|
||||
hid_haptic_check_pressure_unit(td->haptic,
|
||||
hi, field);
|
||||
MT_STORE_FIELD(p);
|
||||
return 1;
|
||||
case HID_DG_SCANTIME:
|
||||
|
|
@ -1008,6 +1017,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
|
|||
|
||||
app->num_received = 0;
|
||||
app->left_button_state = 0;
|
||||
if (td->is_haptic_touchpad)
|
||||
hid_haptic_pressure_reset(td->haptic);
|
||||
|
||||
if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
|
||||
set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
|
||||
|
|
@ -1165,6 +1176,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
|
|||
minor = minor >> 1;
|
||||
}
|
||||
|
||||
if (td->is_haptic_touchpad)
|
||||
hid_haptic_pressure_increase(td->haptic, *slot->p);
|
||||
|
||||
x = hdev->quirks & HID_QUIRK_X_INVERT ?
|
||||
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
|
||||
*slot->x;
|
||||
|
|
@ -1366,6 +1380,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
|
|||
if (cls->is_indirect)
|
||||
app->mt_flags |= INPUT_MT_POINTER;
|
||||
|
||||
if (td->is_haptic_touchpad)
|
||||
app->mt_flags |= INPUT_MT_TOTAL_FORCE;
|
||||
|
||||
if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
|
||||
app->mt_flags |= INPUT_MT_DROP_UNUSED;
|
||||
|
||||
|
|
@ -1401,6 +1418,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
struct mt_application *application;
|
||||
struct mt_report_data *rdata;
|
||||
int ret;
|
||||
|
||||
rdata = mt_find_report_data(td, field->report);
|
||||
if (!rdata) {
|
||||
|
|
@ -1463,6 +1481,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
|||
if (field->physical == HID_DG_STYLUS)
|
||||
hi->application = HID_DG_STYLUS;
|
||||
|
||||
ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
|
||||
max);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* let hid-core decide for the others */
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1685,6 +1708,14 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
|||
struct hid_report *report;
|
||||
int ret;
|
||||
|
||||
if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
|
||||
td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT)) {
|
||||
if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0)
|
||||
td->is_haptic_touchpad = false;
|
||||
} else {
|
||||
td->is_haptic_touchpad = false;
|
||||
}
|
||||
|
||||
list_for_each_entry(report, &hi->reports, hidinput_list) {
|
||||
rdata = mt_find_report_data(td, report);
|
||||
if (!rdata) {
|
||||
|
|
@ -1827,6 +1858,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
dev_err(&hdev->dev, "cannot allocate multitouch data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL);
|
||||
if (!td->haptic)
|
||||
return -ENOMEM;
|
||||
|
||||
td->haptic->hdev = hdev;
|
||||
td->hdev = hdev;
|
||||
td->mtclass = *mtclass;
|
||||
td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
|
||||
|
|
@ -1895,6 +1931,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
|
||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||
|
||||
if (td->is_haptic_touchpad) {
|
||||
if (hid_haptic_init(hdev, &td->haptic)) {
|
||||
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
|
||||
hdev->name);
|
||||
td->is_haptic_touchpad = false;
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
} else {
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -695,6 +695,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
|||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_STEELSERIES)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9) },
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_SUNPLUS)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
|
||||
|
|
|
|||
|
|
@ -249,11 +249,11 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
{
|
||||
int ret, i;
|
||||
struct led_classdev *led;
|
||||
struct steelseries_srws1_data *drv_data;
|
||||
size_t name_sz;
|
||||
char *name;
|
||||
|
||||
struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
|
||||
|
||||
drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
|
||||
if (drv_data == NULL) {
|
||||
hid_err(hdev, "can't alloc SRW-S1 memory\n");
|
||||
return -ENOMEM;
|
||||
|
|
@ -264,18 +264,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
goto err_free;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
|
||||
ret = -ENODEV;
|
||||
goto err_free;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
goto err_free;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* register led subsystem */
|
||||
|
|
@ -288,10 +288,10 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
name_sz = strlen(hdev->uniq) + 16;
|
||||
|
||||
/* 'ALL', for setting all LEDs simultaneously */
|
||||
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
|
||||
led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
|
||||
if (!led) {
|
||||
hid_err(hdev, "can't allocate memory for LED ALL\n");
|
||||
goto err_led;
|
||||
goto out;
|
||||
}
|
||||
|
||||
name = (void *)(&led[1]);
|
||||
|
|
@ -303,16 +303,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
led->brightness_set = steelseries_srws1_led_all_set_brightness;
|
||||
|
||||
drv_data->led[SRWS1_NUMBER_LEDS] = led;
|
||||
ret = led_classdev_register(&hdev->dev, led);
|
||||
if (ret)
|
||||
goto err_led;
|
||||
ret = devm_led_classdev_register(&hdev->dev, led);
|
||||
if (ret) {
|
||||
hid_err(hdev, "failed to register LED %d. Aborting.\n", SRWS1_NUMBER_LEDS);
|
||||
goto out; /* let the driver continue without LEDs */
|
||||
}
|
||||
|
||||
/* Each individual LED */
|
||||
for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
|
||||
led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
|
||||
led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
|
||||
if (!led) {
|
||||
hid_err(hdev, "can't allocate memory for LED %d\n", i);
|
||||
goto err_led;
|
||||
break;
|
||||
}
|
||||
|
||||
name = (void *)(&led[1]);
|
||||
|
|
@ -324,53 +326,18 @@ static int steelseries_srws1_probe(struct hid_device *hdev,
|
|||
led->brightness_set = steelseries_srws1_led_set_brightness;
|
||||
|
||||
drv_data->led[i] = led;
|
||||
ret = led_classdev_register(&hdev->dev, led);
|
||||
ret = devm_led_classdev_register(&hdev->dev, led);
|
||||
|
||||
if (ret) {
|
||||
hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
|
||||
err_led:
|
||||
/* Deregister all LEDs (if any) */
|
||||
for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
|
||||
led = drv_data->led[i];
|
||||
drv_data->led[i] = NULL;
|
||||
if (!led)
|
||||
continue;
|
||||
led_classdev_unregister(led);
|
||||
kfree(led);
|
||||
}
|
||||
goto out; /* but let the driver continue without LEDs */
|
||||
break; /* but let the driver continue without LEDs */
|
||||
}
|
||||
}
|
||||
out:
|
||||
return 0;
|
||||
err_free:
|
||||
kfree(drv_data);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steelseries_srws1_remove(struct hid_device *hdev)
|
||||
{
|
||||
int i;
|
||||
struct led_classdev *led;
|
||||
|
||||
struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev);
|
||||
|
||||
if (drv_data) {
|
||||
/* Deregister LEDs (if any) */
|
||||
for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
|
||||
led = drv_data->led[i];
|
||||
drv_data->led[i] = NULL;
|
||||
if (!led)
|
||||
continue;
|
||||
led_classdev_unregister(led);
|
||||
kfree(led);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hid_hw_stop(hdev);
|
||||
kfree(drv_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000
|
||||
|
|
@ -405,13 +372,12 @@ static int steelseries_headset_request_battery(struct hid_device *hdev,
|
|||
|
||||
static void steelseries_headset_fetch_battery(struct hid_device *hdev)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
int ret = 0;
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_1)
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1)
|
||||
ret = steelseries_headset_request_battery(hdev,
|
||||
arctis_1_battery_request, sizeof(arctis_1_battery_request));
|
||||
else if (sd->quirks & STEELSERIES_ARCTIS_9)
|
||||
else if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9)
|
||||
ret = steelseries_headset_request_battery(hdev,
|
||||
arctis_9_battery_request, sizeof(arctis_9_battery_request));
|
||||
|
||||
|
|
@ -567,14 +533,7 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
|
|||
struct steelseries_device *sd;
|
||||
int ret;
|
||||
|
||||
sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
|
||||
if (!sd)
|
||||
return -ENOMEM;
|
||||
hid_set_drvdata(hdev, sd);
|
||||
sd->hdev = hdev;
|
||||
sd->quirks = id->driver_data;
|
||||
|
||||
if (sd->quirks & STEELSERIES_SRWS1) {
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
return steelseries_srws1_probe(hdev, id);
|
||||
|
|
@ -583,6 +542,13 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
|
|||
#endif
|
||||
}
|
||||
|
||||
sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
|
||||
if (!sd)
|
||||
return -ENOMEM;
|
||||
hid_set_drvdata(hdev, sd);
|
||||
sd->hdev = hdev;
|
||||
sd->quirks = id->driver_data;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -610,17 +576,19 @@ static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id
|
|||
|
||||
static void steelseries_remove(struct hid_device *hdev)
|
||||
{
|
||||
struct steelseries_device *sd = hid_get_drvdata(hdev);
|
||||
struct steelseries_device *sd;
|
||||
unsigned long flags;
|
||||
|
||||
if (sd->quirks & STEELSERIES_SRWS1) {
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) {
|
||||
#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
|
||||
(IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
|
||||
steelseries_srws1_remove(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
sd = hid_get_drvdata(hdev);
|
||||
|
||||
spin_lock_irqsave(&sd->lock, flags);
|
||||
sd->removed = true;
|
||||
spin_unlock_irqrestore(&sd->lock, flags);
|
||||
|
|
@ -667,10 +635,10 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
|
|||
unsigned long flags;
|
||||
|
||||
/* Not a headset */
|
||||
if (sd->quirks & STEELSERIES_SRWS1)
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1)
|
||||
return 0;
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_1) {
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_1) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
|
||||
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
|
||||
|
|
@ -688,7 +656,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
|
|||
}
|
||||
}
|
||||
|
||||
if (sd->quirks & STEELSERIES_ARCTIS_9) {
|
||||
if (hdev->product == USB_DEVICE_ID_STEELSERIES_ARCTIS_9) {
|
||||
hid_dbg(sd->hdev,
|
||||
"Parsing raw event for Arctis 9 headset (%*ph)\n", size, read_buf);
|
||||
if (size < ARCTIS_9_BATTERY_RESPONSE_LEN) {
|
||||
|
|
@ -757,11 +725,11 @@ static const struct hid_device_id steelseries_devices[] = {
|
|||
.driver_data = STEELSERIES_SRWS1 },
|
||||
|
||||
{ /* SteelSeries Arctis 1 Wireless for XBox */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6),
|
||||
.driver_data = STEELSERIES_ARCTIS_1 },
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_1),
|
||||
.driver_data = STEELSERIES_ARCTIS_1 },
|
||||
|
||||
{ /* SteelSeries Arctis 9 Wireless for XBox */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12c2),
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_ARCTIS_9),
|
||||
.driver_data = STEELSERIES_ARCTIS_9 },
|
||||
|
||||
{ }
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include <linux/ctype.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <linux/string_choices.h>
|
||||
|
||||
/**
|
||||
* uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
|
||||
|
|
@ -59,7 +60,7 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
|
|||
size_t i;
|
||||
|
||||
hid_dbg(hdev, "\t.usage_invalid = %s\n",
|
||||
(pen->usage_invalid ? "true" : "false"));
|
||||
str_true_false(pen->usage_invalid));
|
||||
hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr);
|
||||
hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size);
|
||||
hid_dbg(hdev, "\t.id = %u\n", pen->id);
|
||||
|
|
@ -74,9 +75,9 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
|
|||
hid_dbg(hdev, "\t.inrange = %s\n",
|
||||
uclogic_params_pen_inrange_to_str(pen->inrange));
|
||||
hid_dbg(hdev, "\t.fragmented_hires = %s\n",
|
||||
(pen->fragmented_hires ? "true" : "false"));
|
||||
str_true_false(pen->fragmented_hires));
|
||||
hid_dbg(hdev, "\t.tilt_y_flipped = %s\n",
|
||||
(pen->tilt_y_flipped ? "true" : "false"));
|
||||
str_true_false(pen->tilt_y_flipped));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -119,8 +120,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev,
|
|||
{
|
||||
size_t i;
|
||||
|
||||
hid_dbg(hdev, ".invalid = %s\n",
|
||||
params->invalid ? "true" : "false");
|
||||
hid_dbg(hdev, ".invalid = %s\n", str_true_false(params->invalid));
|
||||
hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
|
||||
hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
|
||||
hid_dbg(hdev, ".pen = {\n");
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@
|
|||
* Copyright (c) 2024, 2025 Tomasz Pakuła
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include "hid-ids.h"
|
||||
#include "usbhid/hid-pidff.h"
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#define JOY_RANGE (BTN_DEAD - BTN_JOYSTICK + 1)
|
||||
|
||||
|
|
@ -21,8 +21,10 @@
|
|||
* Map buttons manually to extend the default joystick button limit
|
||||
*/
|
||||
static int universal_pidff_input_mapping(struct hid_device *hdev,
|
||||
struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field,
|
||||
struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
|
||||
return 0;
|
||||
|
|
@ -126,65 +128,64 @@ static int universal_pidff_input_configured(struct hid_device *hdev,
|
|||
if (!test_bit(axis, input->absbit))
|
||||
continue;
|
||||
|
||||
input_set_abs_params(input, axis,
|
||||
input->absinfo[axis].minimum,
|
||||
input->absinfo[axis].maximum,
|
||||
axis == ABS_X ? 0 : 8, 0);
|
||||
input_set_abs_params(input, axis, input->absinfo[axis].minimum,
|
||||
input->absinfo[axis].maximum,
|
||||
axis == ABS_X ? 0 : 8, 0);
|
||||
}
|
||||
|
||||
/* Remove fuzz and deadzone from the second joystick axis */
|
||||
if (hdev->vendor == USB_VENDOR_ID_FFBEAST &&
|
||||
hdev->product == USB_DEVICE_ID_FFBEAST_JOYSTICK)
|
||||
input_set_abs_params(input, ABS_Y,
|
||||
input->absinfo[ABS_Y].minimum,
|
||||
input->absinfo[ABS_Y].maximum, 0, 0);
|
||||
input->absinfo[ABS_Y].minimum,
|
||||
input->absinfo[ABS_Y].maximum, 0, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id universal_pidff_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R3_2),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R5_2),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R9_2),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R12_2),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_MOZA, USB_DEVICE_ID_MOZA_R16_R21_2),
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION },
|
||||
.driver_data = HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C5) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CAMMUS, USB_DEVICE_ID_CAMMUS_C12) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_VRS, USB_DEVICE_ID_VRS_DFP),
|
||||
.driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
|
||||
.driver_data = HID_PIDFF_QUIRK_PERMISSIVE_CONTROL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_JOYSTICK), },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_RUDDER), },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_FFBEAST, USB_DEVICE_ID_FFBEAST_WHEEL) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V10),
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12),
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE),
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_PXN_V12_LITE_2),
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LITE_STAR, USB_DEVICE_ID_LITE_STAR_GT987),
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
.driver_data = HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_INVICTA) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_FORTE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_LA_PRIMA) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASETEK, USB_DEVICE_ID_ASETEK_TONY_KANAAN) },
|
||||
{ }
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, universal_pidff_devices);
|
||||
|
||||
|
|
|
|||
|
|
@ -394,15 +394,127 @@ static int hidraw_revoke(struct hidraw_list *list)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static long hidraw_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
|
||||
void __user *arg)
|
||||
{
|
||||
struct hid_device *hid = dev->hid;
|
||||
|
||||
switch (cmd) {
|
||||
case HIDIOCGRDESCSIZE:
|
||||
if (put_user(hid->rsize, (int __user *)arg))
|
||||
return -EFAULT;
|
||||
break;
|
||||
|
||||
case HIDIOCGRDESC:
|
||||
{
|
||||
__u32 len;
|
||||
|
||||
if (get_user(len, (int __user *)arg))
|
||||
return -EFAULT;
|
||||
|
||||
if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_to_user(arg + offsetof(
|
||||
struct hidraw_report_descriptor,
|
||||
value[0]),
|
||||
hid->rdesc,
|
||||
min(hid->rsize, len)))
|
||||
return -EFAULT;
|
||||
|
||||
break;
|
||||
}
|
||||
case HIDIOCGRAWINFO:
|
||||
{
|
||||
struct hidraw_devinfo dinfo;
|
||||
|
||||
dinfo.bustype = hid->bus;
|
||||
dinfo.vendor = hid->vendor;
|
||||
dinfo.product = hid->product;
|
||||
if (copy_to_user(arg, &dinfo, sizeof(dinfo)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
}
|
||||
case HIDIOCREVOKE:
|
||||
{
|
||||
struct hidraw_list *list = file->private_data;
|
||||
|
||||
if (arg)
|
||||
return -EINVAL;
|
||||
|
||||
return hidraw_revoke(list);
|
||||
}
|
||||
default:
|
||||
/*
|
||||
* None of the above ioctls can return -EAGAIN, so
|
||||
* use it as a marker that we need to check variable
|
||||
* length ioctls.
|
||||
*/
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
|
||||
void __user *user_arg)
|
||||
{
|
||||
int len = _IOC_SIZE(cmd);
|
||||
|
||||
switch (cmd & ~IOCSIZE_MASK) {
|
||||
case HIDIOCSFEATURE(0):
|
||||
return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
|
||||
case HIDIOCGFEATURE(0):
|
||||
return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
|
||||
case HIDIOCSINPUT(0):
|
||||
return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
|
||||
case HIDIOCGINPUT(0):
|
||||
return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
|
||||
case HIDIOCSOUTPUT(0):
|
||||
return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
|
||||
case HIDIOCGOUTPUT(0):
|
||||
return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
|
||||
void __user *user_arg)
|
||||
{
|
||||
struct hid_device *hid = dev->hid;
|
||||
int len = _IOC_SIZE(cmd);
|
||||
int field_len;
|
||||
|
||||
switch (cmd & ~IOCSIZE_MASK) {
|
||||
case HIDIOCGRAWNAME(0):
|
||||
field_len = strlen(hid->name) + 1;
|
||||
if (len > field_len)
|
||||
len = field_len;
|
||||
return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len;
|
||||
case HIDIOCGRAWPHYS(0):
|
||||
field_len = strlen(hid->phys) + 1;
|
||||
if (len > field_len)
|
||||
len = field_len;
|
||||
return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len;
|
||||
case HIDIOCGRAWUNIQ(0):
|
||||
field_len = strlen(hid->uniq) + 1;
|
||||
if (len > field_len)
|
||||
len = field_len;
|
||||
return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct inode *inode = file_inode(file);
|
||||
unsigned int minor = iminor(inode);
|
||||
long ret = 0;
|
||||
struct hidraw *dev;
|
||||
struct hidraw_list *list = file->private_data;
|
||||
void __user *user_arg = (void __user*) arg;
|
||||
void __user *user_arg = (void __user *)arg;
|
||||
int ret;
|
||||
|
||||
down_read(&minors_rwsem);
|
||||
dev = hidraw_table[minor];
|
||||
|
|
@ -411,124 +523,32 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
|
|||
goto out;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case HIDIOCGRDESCSIZE:
|
||||
if (put_user(dev->hid->rsize, (int __user *)arg))
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
|
||||
case HIDIOCGRDESC:
|
||||
{
|
||||
__u32 len;
|
||||
|
||||
if (get_user(len, (int __user *)arg))
|
||||
ret = -EFAULT;
|
||||
else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
|
||||
ret = -EINVAL;
|
||||
else if (copy_to_user(user_arg + offsetof(
|
||||
struct hidraw_report_descriptor,
|
||||
value[0]),
|
||||
dev->hid->rdesc,
|
||||
min(dev->hid->rsize, len)))
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
case HIDIOCGRAWINFO:
|
||||
{
|
||||
struct hidraw_devinfo dinfo;
|
||||
|
||||
dinfo.bustype = dev->hid->bus;
|
||||
dinfo.vendor = dev->hid->vendor;
|
||||
dinfo.product = dev->hid->product;
|
||||
if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
case HIDIOCREVOKE:
|
||||
{
|
||||
if (user_arg)
|
||||
ret = -EINVAL;
|
||||
else
|
||||
ret = hidraw_revoke(list);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
struct hid_device *hid = dev->hid;
|
||||
if (_IOC_TYPE(cmd) != 'H') {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
|
||||
int len = _IOC_SIZE(cmd);
|
||||
ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
|
||||
break;
|
||||
}
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
|
||||
int len = _IOC_SIZE(cmd);
|
||||
ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
|
||||
int len = _IOC_SIZE(cmd);
|
||||
ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
|
||||
break;
|
||||
}
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
|
||||
int len = _IOC_SIZE(cmd);
|
||||
ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
|
||||
int len = _IOC_SIZE(cmd);
|
||||
ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
|
||||
break;
|
||||
}
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
|
||||
int len = _IOC_SIZE(cmd);
|
||||
ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Begin Read-only ioctls. */
|
||||
if (_IOC_DIR(cmd) != _IOC_READ) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
|
||||
int len = strlen(hid->name) + 1;
|
||||
if (len > _IOC_SIZE(cmd))
|
||||
len = _IOC_SIZE(cmd);
|
||||
ret = copy_to_user(user_arg, hid->name, len) ?
|
||||
-EFAULT : len;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
|
||||
int len = strlen(hid->phys) + 1;
|
||||
if (len > _IOC_SIZE(cmd))
|
||||
len = _IOC_SIZE(cmd);
|
||||
ret = copy_to_user(user_arg, hid->phys, len) ?
|
||||
-EFAULT : len;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
|
||||
int len = strlen(hid->uniq) + 1;
|
||||
if (len > _IOC_SIZE(cmd))
|
||||
len = _IOC_SIZE(cmd);
|
||||
ret = copy_to_user(user_arg, hid->uniq, len) ?
|
||||
-EFAULT : len;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = -ENOTTY;
|
||||
if (_IOC_TYPE(cmd) != 'H') {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) {
|
||||
ret = -ENOTTY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg);
|
||||
if (ret != -EAGAIN)
|
||||
goto out;
|
||||
|
||||
switch (_IOC_DIR(cmd)) {
|
||||
case (_IOC_READ | _IOC_WRITE):
|
||||
ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg);
|
||||
break;
|
||||
case _IOC_READ:
|
||||
ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg);
|
||||
break;
|
||||
default:
|
||||
/* Any other IOC_DIR is wrong */
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
up_read(&minors_rwsem);
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
|
|||
return hid_descriptor_address;
|
||||
}
|
||||
|
||||
static void i2c_hid_acpi_restore_sequence(struct i2chid_ops *ops)
|
||||
{
|
||||
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
|
||||
|
||||
i2c_hid_acpi_get_descriptor(ihid_acpi);
|
||||
}
|
||||
|
||||
static void i2c_hid_acpi_shutdown_tail(struct i2chid_ops *ops)
|
||||
{
|
||||
struct i2c_hid_acpi *ihid_acpi = container_of(ops, struct i2c_hid_acpi, ops);
|
||||
|
|
@ -96,6 +103,7 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
|
|||
|
||||
ihid_acpi->adev = ACPI_COMPANION(dev);
|
||||
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
|
||||
ihid_acpi->ops.restore_sequence = i2c_hid_acpi_restore_sequence;
|
||||
|
||||
ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
|
||||
if (ret < 0)
|
||||
|
|
|
|||
|
|
@ -961,6 +961,14 @@ static void i2c_hid_core_shutdown_tail(struct i2c_hid *ihid)
|
|||
ihid->ops->shutdown_tail(ihid->ops);
|
||||
}
|
||||
|
||||
static void i2c_hid_core_restore_sequence(struct i2c_hid *ihid)
|
||||
{
|
||||
if (!ihid->ops->restore_sequence)
|
||||
return;
|
||||
|
||||
ihid->ops->restore_sequence(ihid->ops);
|
||||
}
|
||||
|
||||
static int i2c_hid_core_suspend(struct i2c_hid *ihid, bool force_poweroff)
|
||||
{
|
||||
struct i2c_client *client = ihid->client;
|
||||
|
|
@ -1370,8 +1378,26 @@ static int i2c_hid_core_pm_resume(struct device *dev)
|
|||
return i2c_hid_core_resume(ihid);
|
||||
}
|
||||
|
||||
static int i2c_hid_core_pm_restore(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct i2c_hid *ihid = i2c_get_clientdata(client);
|
||||
|
||||
if (ihid->is_panel_follower)
|
||||
return 0;
|
||||
|
||||
i2c_hid_core_restore_sequence(ihid);
|
||||
|
||||
return i2c_hid_core_resume(ihid);
|
||||
}
|
||||
|
||||
const struct dev_pm_ops i2c_hid_core_pm = {
|
||||
SYSTEM_SLEEP_PM_OPS(i2c_hid_core_pm_suspend, i2c_hid_core_pm_resume)
|
||||
.suspend = pm_sleep_ptr(i2c_hid_core_pm_suspend),
|
||||
.resume = pm_sleep_ptr(i2c_hid_core_pm_resume),
|
||||
.freeze = pm_sleep_ptr(i2c_hid_core_pm_suspend),
|
||||
.thaw = pm_sleep_ptr(i2c_hid_core_pm_resume),
|
||||
.poweroff = pm_sleep_ptr(i2c_hid_core_pm_suspend),
|
||||
.restore = pm_sleep_ptr(i2c_hid_core_pm_restore),
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(i2c_hid_core_pm);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
|
|||
* @power_up: do sequencing to power up the device.
|
||||
* @power_down: do sequencing to power down the device.
|
||||
* @shutdown_tail: called at the end of shutdown.
|
||||
* @restore_sequence: hibernation restore sequence.
|
||||
*/
|
||||
struct i2chid_ops {
|
||||
int (*power_up)(struct i2chid_ops *ops);
|
||||
void (*power_down)(struct i2chid_ops *ops);
|
||||
void (*shutdown_tail)(struct i2chid_ops *ops);
|
||||
void (*restore_sequence)(struct i2chid_ops *ops);
|
||||
};
|
||||
|
||||
int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
|
||||
|
|
|
|||
|
|
@ -498,6 +498,7 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
|
|||
{
|
||||
uint32_t reset_id;
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
/* Read reset ID */
|
||||
reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
|
||||
|
|
@ -510,12 +511,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
|
|||
/* ISHTP notification in IPC_RESET */
|
||||
ishtp_reset_handler(dev);
|
||||
|
||||
if (!ish_is_input_ready(dev))
|
||||
timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
|
||||
TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
|
||||
|
||||
ret = timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
|
||||
TIME_SLICE_FOR_INPUT_RDY_MS,
|
||||
TIMEOUT_FOR_INPUT_RDY_MS);
|
||||
/* ISH FW is dead */
|
||||
if (!ish_is_input_ready(dev))
|
||||
if (ret)
|
||||
return -EPIPE;
|
||||
|
||||
/* Send clock sync at once after reset */
|
||||
|
|
@ -531,9 +531,10 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
|
|||
sizeof(uint32_t));
|
||||
|
||||
/* Wait for ISH FW'es ILUP and ISHTP_READY */
|
||||
timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
|
||||
TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS);
|
||||
if (!ishtp_fw_is_ready(dev)) {
|
||||
ret = timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
|
||||
TIME_SLICE_FOR_FW_RDY_MS,
|
||||
TIMEOUT_FOR_FW_RDY_MS);
|
||||
if (ret) {
|
||||
/* ISH FW is dead */
|
||||
uint32_t ish_status;
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
static struct quicki2c_ddata ptl_ddata = {
|
||||
.max_detect_size = MAX_RX_DETECT_SIZE_PTL,
|
||||
.max_interrupt_delay = MAX_RX_INTERRUPT_DELAY,
|
||||
};
|
||||
|
||||
/* THC QuickI2C ACPI method to get device properties */
|
||||
|
|
@ -200,6 +201,21 @@ static int quicki2c_get_acpi_resources(struct quicki2c_device *qcdev)
|
|||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (qcdev->ddata) {
|
||||
qcdev->i2c_max_frame_size_enable = i2c_config.FSEN;
|
||||
qcdev->i2c_int_delay_enable = i2c_config.INDE;
|
||||
|
||||
if (i2c_config.FSVL <= qcdev->ddata->max_detect_size)
|
||||
qcdev->i2c_max_frame_size = i2c_config.FSVL;
|
||||
else
|
||||
qcdev->i2c_max_frame_size = qcdev->ddata->max_detect_size;
|
||||
|
||||
if (i2c_config.INDV <= qcdev->ddata->max_interrupt_delay)
|
||||
qcdev->i2c_int_delay = i2c_config.INDV;
|
||||
else
|
||||
qcdev->i2c_int_delay = qcdev->ddata->max_interrupt_delay;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -441,17 +457,24 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev)
|
|||
* max input length <= THC detect capability, enable the feature with device
|
||||
* max input length.
|
||||
*/
|
||||
if (qcdev->ddata->max_detect_size >=
|
||||
le16_to_cpu(qcdev->dev_desc.max_input_len)) {
|
||||
thc_i2c_set_rx_max_size(qcdev->thc_hw,
|
||||
le16_to_cpu(qcdev->dev_desc.max_input_len));
|
||||
if (qcdev->i2c_max_frame_size_enable) {
|
||||
if (qcdev->i2c_max_frame_size >=
|
||||
le16_to_cpu(qcdev->dev_desc.max_input_len)) {
|
||||
thc_i2c_set_rx_max_size(qcdev->thc_hw,
|
||||
le16_to_cpu(qcdev->dev_desc.max_input_len));
|
||||
} else {
|
||||
dev_warn(qcdev->dev,
|
||||
"Max frame size is smaller than hid max input length!");
|
||||
thc_i2c_set_rx_max_size(qcdev->thc_hw,
|
||||
le16_to_cpu(qcdev->i2c_max_frame_size));
|
||||
}
|
||||
thc_i2c_rx_max_size_enable(qcdev->thc_hw, true);
|
||||
}
|
||||
|
||||
/* If platform supports interrupt delay feature, enable it with given delay */
|
||||
if (qcdev->ddata->interrupt_delay) {
|
||||
if (qcdev->i2c_int_delay_enable) {
|
||||
thc_i2c_set_rx_int_delay(qcdev->thc_hw,
|
||||
qcdev->ddata->interrupt_delay);
|
||||
qcdev->i2c_int_delay * 10);
|
||||
thc_i2c_rx_int_delay_enable(qcdev->thc_hw, true);
|
||||
}
|
||||
}
|
||||
|
|
@ -464,10 +487,10 @@ static void quicki2c_dma_adv_enable(struct quicki2c_device *qcdev)
|
|||
*/
|
||||
static void quicki2c_dma_adv_disable(struct quicki2c_device *qcdev)
|
||||
{
|
||||
if (qcdev->ddata->max_detect_size)
|
||||
if (qcdev->i2c_max_frame_size_enable)
|
||||
thc_i2c_rx_max_size_enable(qcdev->thc_hw, false);
|
||||
|
||||
if (qcdev->ddata->interrupt_delay)
|
||||
if (qcdev->i2c_int_delay_enable)
|
||||
thc_i2c_rx_int_delay_enable(qcdev->thc_hw, false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@
|
|||
|
||||
/* PTL Max packet size detection capability is 255 Bytes */
|
||||
#define MAX_RX_DETECT_SIZE_PTL 255
|
||||
/* Max interrupt delay capability is 2.56ms */
|
||||
#define MAX_RX_INTERRUPT_DELAY 256
|
||||
|
||||
/* Default interrupt delay is 1ms, suitable for most devices */
|
||||
#define DEFAULT_INTERRUPT_DELAY_US (1 * USEC_PER_MSEC)
|
||||
|
|
@ -103,6 +105,10 @@ struct quicki2c_subip_acpi_parameter {
|
|||
* @HMTD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Transmit HOLD Period
|
||||
* @HMRD: High Speed Mode Plus (3.4Mbits/sec) Serial Data Line Receive HOLD Period
|
||||
* @HMSL: Maximum length (in ic_clk_cycles) of suppressed spikes in High Speed Mode
|
||||
* @FSEN: Maximum Frame Size Feature Enable Control
|
||||
* @FSVL: Maximum Frame Size Value (unit in Bytes)
|
||||
* @INDE: Interrupt Delay Feature Enable Control
|
||||
* @INDV: Interrupt Delay Value (unit in 10 us)
|
||||
*
|
||||
* Those properties get from QUICKI2C_ACPI_METHOD_NAME_ISUB method, used for
|
||||
* I2C timing configure.
|
||||
|
|
@ -129,17 +135,22 @@ struct quicki2c_subip_acpi_config {
|
|||
u64 HMTD;
|
||||
u64 HMRD;
|
||||
u64 HMSL;
|
||||
|
||||
u64 FSEN;
|
||||
u64 FSVL;
|
||||
u64 INDE;
|
||||
u64 INDV;
|
||||
u8 reserved;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct quicki2c_ddata - Driver specific data for quicki2c device
|
||||
* @max_detect_size: Identify max packet size detect for rx
|
||||
* @interrupt_delay: Identify interrupt detect delay for rx
|
||||
* @interrupt_delay: Identify max interrupt detect delay for rx
|
||||
*/
|
||||
struct quicki2c_ddata {
|
||||
u32 max_detect_size;
|
||||
u32 interrupt_delay;
|
||||
u32 max_interrupt_delay;
|
||||
};
|
||||
|
||||
struct device;
|
||||
|
|
@ -172,6 +183,10 @@ struct acpi_device;
|
|||
* @report_len: The length of input/output report packet
|
||||
* @reset_ack_wq: Workqueue for waiting reset response from device
|
||||
* @reset_ack: Indicate reset response received or not
|
||||
* @i2c_max_frame_size_enable: Indicate max frame size feature enabled or not
|
||||
* @i2c_max_frame_size: Max RX frame size (unit in Bytes)
|
||||
* @i2c_int_delay_enable: Indicate interrupt delay feature enabled or not
|
||||
* @i2c_int_delay: Interrupt detection delay value (unit in 10 us)
|
||||
*/
|
||||
struct quicki2c_device {
|
||||
struct device *dev;
|
||||
|
|
@ -202,6 +217,11 @@ struct quicki2c_device {
|
|||
|
||||
wait_queue_head_t reset_ack_wq;
|
||||
bool reset_ack;
|
||||
|
||||
u32 i2c_max_frame_size_enable;
|
||||
u32 i2c_max_frame_size;
|
||||
u32 i2c_int_delay_enable;
|
||||
u32 i2c_int_delay;
|
||||
};
|
||||
|
||||
#endif /* _QUICKI2C_DEV_H_ */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <linux/bitfield.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/string_choices.h>
|
||||
|
||||
#include "intel-thc-dev.h"
|
||||
#include "intel-thc-hw.h"
|
||||
|
|
@ -664,7 +665,7 @@ int thc_interrupt_quiesce(const struct thc_device *dev, bool int_quiesce)
|
|||
if (ret) {
|
||||
dev_err_once(dev->dev,
|
||||
"Timeout while waiting THC idle, target quiesce state = %s\n",
|
||||
int_quiesce ? "true" : "false");
|
||||
str_true_false(int_quiesce));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -16,7 +16,7 @@
|
|||
#define HID_PIDFF_QUIRK_PERMISSIVE_CONTROL BIT(2)
|
||||
|
||||
/* Use fixed 0x4000 direction during SET_EFFECT report upload */
|
||||
#define HID_PIDFF_QUIRK_FIX_WHEEL_DIRECTION BIT(3)
|
||||
#define HID_PIDFF_QUIRK_FIX_CONDITIONAL_DIRECTION BIT(3)
|
||||
|
||||
/* Force all periodic effects to be uploaded as SINE */
|
||||
#define HID_PIDFF_QUIRK_PERIODIC_SINE_ONLY BIT(4)
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
|
|||
struct input_mt *mt = dev->mt;
|
||||
struct input_mt_slot *oldest;
|
||||
int oldid, count, i;
|
||||
int p, reported_p = 0;
|
||||
|
||||
if (!mt)
|
||||
return;
|
||||
|
|
@ -216,6 +217,13 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
|
|||
oldest = ps;
|
||||
oldid = id;
|
||||
}
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
|
||||
p = input_mt_get_value(ps, ABS_MT_PRESSURE);
|
||||
if (mt->flags & INPUT_MT_TOTAL_FORCE)
|
||||
reported_p += p;
|
||||
else if (oldid == id)
|
||||
reported_p = p;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
|
|
@ -245,10 +253,8 @@ void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)
|
|||
input_event(dev, EV_ABS, ABS_X, x);
|
||||
input_event(dev, EV_ABS, ABS_Y, y);
|
||||
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit)) {
|
||||
int p = input_mt_get_value(oldest, ABS_MT_PRESSURE);
|
||||
input_event(dev, EV_ABS, ABS_PRESSURE, p);
|
||||
}
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
|
||||
input_event(dev, EV_ABS, ABS_PRESSURE, reported_p);
|
||||
} else {
|
||||
if (test_bit(ABS_MT_PRESSURE, dev->absbit))
|
||||
input_event(dev, EV_ABS, ABS_PRESSURE, 0);
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ struct hid_item {
|
|||
#define HID_UP_TELEPHONY 0x000b0000
|
||||
#define HID_UP_CONSUMER 0x000c0000
|
||||
#define HID_UP_DIGITIZER 0x000d0000
|
||||
#define HID_UP_HAPTIC 0x000e0000
|
||||
#define HID_UP_PID 0x000f0000
|
||||
#define HID_UP_BATTERY 0x00850000
|
||||
#define HID_UP_CAMERA 0x00900000
|
||||
|
|
@ -316,6 +317,28 @@ struct hid_item {
|
|||
#define HID_DG_TOOLSERIALNUMBER 0x000d005b
|
||||
#define HID_DG_LATENCYMODE 0x000d0060
|
||||
|
||||
#define HID_HP_SIMPLECONTROLLER 0x000e0001
|
||||
#define HID_HP_WAVEFORMLIST 0x000e0010
|
||||
#define HID_HP_DURATIONLIST 0x000e0011
|
||||
#define HID_HP_AUTOTRIGGER 0x000e0020
|
||||
#define HID_HP_MANUALTRIGGER 0x000e0021
|
||||
#define HID_HP_AUTOTRIGGERASSOCIATEDCONTROL 0x000e0022
|
||||
#define HID_HP_INTENSITY 0x000e0023
|
||||
#define HID_HP_REPEATCOUNT 0x000e0024
|
||||
#define HID_HP_RETRIGGERPERIOD 0x000e0025
|
||||
#define HID_HP_WAVEFORMVENDORPAGE 0x000e0026
|
||||
#define HID_HP_WAVEFORMVENDORID 0x000e0027
|
||||
#define HID_HP_WAVEFORMCUTOFFTIME 0x000e0028
|
||||
#define HID_HP_WAVEFORMNONE 0x000e1001
|
||||
#define HID_HP_WAVEFORMSTOP 0x000e1002
|
||||
#define HID_HP_WAVEFORMCLICK 0x000e1003
|
||||
#define HID_HP_WAVEFORMBUZZCONTINUOUS 0x000e1004
|
||||
#define HID_HP_WAVEFORMRUMBLECONTINUOUS 0x000e1005
|
||||
#define HID_HP_WAVEFORMPRESS 0x000e1006
|
||||
#define HID_HP_WAVEFORMRELEASE 0x000e1007
|
||||
#define HID_HP_VENDORWAVEFORMMIN 0x000e2001
|
||||
#define HID_HP_VENDORWAVEFORMMAX 0x000e2fff
|
||||
|
||||
#define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
|
||||
#define HID_BAT_CHARGING 0x00850044
|
||||
|
||||
|
|
@ -425,6 +448,12 @@ struct hid_item {
|
|||
#define HID_REPORT_PROTOCOL 1
|
||||
#define HID_BOOT_PROTOCOL 0
|
||||
|
||||
/*
|
||||
* HID units
|
||||
*/
|
||||
#define HID_UNIT_GRAM 0x0101
|
||||
#define HID_UNIT_NEWTON 0xe111
|
||||
|
||||
/*
|
||||
* This is the global environment of the parser. This information is
|
||||
* persistent for main-items. The global environment can be saved and
|
||||
|
|
@ -818,7 +847,7 @@ struct hid_usage_id {
|
|||
* zero from them.
|
||||
*/
|
||||
struct hid_driver {
|
||||
char *name;
|
||||
const char *name;
|
||||
const struct hid_device_id *id_table;
|
||||
|
||||
struct list_head dyn_list;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#define INPUT_MT_DROP_UNUSED 0x0004 /* drop contacts not seen in frame */
|
||||
#define INPUT_MT_TRACK 0x0008 /* use in-kernel tracking */
|
||||
#define INPUT_MT_SEMI_MT 0x0010 /* semi-mt device, finger count handled manually */
|
||||
#define INPUT_MT_TOTAL_FORCE 0x0020 /* calculate total force from slots pressure */
|
||||
|
||||
/**
|
||||
* struct input_mt_slot - represents the state of an input MT slot
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ struct hidraw_devinfo {
|
|||
#define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len)
|
||||
#define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */
|
||||
|
||||
#define HIDIOCTL_LAST _IOC_NR(HIDIOCREVOKE)
|
||||
|
||||
#define HIDRAW_FIRST_MINOR 0
|
||||
#define HIDRAW_MAX_DEVICES 64
|
||||
/* number of reports to buffer */
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */
|
||||
#define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */
|
||||
#define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */
|
||||
#define INPUT_PROP_HAPTIC_TOUCHPAD 0x07 /* is a haptic touchpad */
|
||||
|
||||
#define INPUT_PROP_MAX 0x1f
|
||||
#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1)
|
||||
|
|
|
|||
|
|
@ -429,6 +429,24 @@ struct ff_rumble_effect {
|
|||
__u16 weak_magnitude;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ff_haptic_effect
|
||||
* @hid_usage: hid_usage according to Haptics page (WAVEFORM_CLICK, etc.)
|
||||
* @vendor_id: the waveform vendor ID if hid_usage is in the vendor-defined range
|
||||
* @vendor_waveform_page: the vendor waveform page if hid_usage is in the vendor-defined range
|
||||
* @intensity: strength of the effect as percentage
|
||||
* @repeat_count: number of times to retrigger effect
|
||||
* @retrigger_period: time before effect is retriggered (in ms)
|
||||
*/
|
||||
struct ff_haptic_effect {
|
||||
__u16 hid_usage;
|
||||
__u16 vendor_id;
|
||||
__u8 vendor_waveform_page;
|
||||
__u16 intensity;
|
||||
__u16 repeat_count;
|
||||
__u16 retrigger_period;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ff_effect - defines force feedback effect
|
||||
* @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING,
|
||||
|
|
@ -465,6 +483,7 @@ struct ff_effect {
|
|||
struct ff_periodic_effect periodic;
|
||||
struct ff_condition_effect condition[2]; /* One for each axis */
|
||||
struct ff_rumble_effect rumble;
|
||||
struct ff_haptic_effect haptic;
|
||||
} u;
|
||||
};
|
||||
|
||||
|
|
@ -472,6 +491,7 @@ struct ff_effect {
|
|||
* Force feedback effect types
|
||||
*/
|
||||
|
||||
#define FF_HAPTIC 0x4f
|
||||
#define FF_RUMBLE 0x50
|
||||
#define FF_PERIODIC 0x51
|
||||
#define FF_CONSTANT 0x52
|
||||
|
|
@ -481,7 +501,7 @@ struct ff_effect {
|
|||
#define FF_INERTIA 0x56
|
||||
#define FF_RAMP 0x57
|
||||
|
||||
#define FF_EFFECT_MIN FF_RUMBLE
|
||||
#define FF_EFFECT_MIN FF_HAPTIC
|
||||
#define FF_EFFECT_MAX FF_RAMP
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -230,6 +230,12 @@ static int uhid_event(struct __test_metadata *_metadata, int fd)
|
|||
break;
|
||||
case UHID_SET_REPORT:
|
||||
UHID_LOG("UHID_SET_REPORT from uhid-dev");
|
||||
|
||||
answer.type = UHID_SET_REPORT_REPLY;
|
||||
answer.u.set_report_reply.id = ev.u.set_report.id;
|
||||
answer.u.set_report_reply.err = 0; /* success */
|
||||
|
||||
uhid_write(_metadata, fd, &answer);
|
||||
break;
|
||||
default:
|
||||
TH_LOG("Invalid event from uhid-dev: %u", ev.type);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
/* Copyright (c) 2022-2024 Red Hat */
|
||||
|
||||
#include "hid_common.h"
|
||||
#include <linux/input.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
/* for older kernels */
|
||||
#ifndef HIDIOCREVOKE
|
||||
|
|
@ -215,6 +218,476 @@ TEST_F(hidraw, write_event_revoked)
|
|||
pthread_mutex_unlock(&uhid_output_mtx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRDESCSIZE ioctl to get report descriptor size
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rdescsize)
|
||||
{
|
||||
int desc_size = 0;
|
||||
int err;
|
||||
|
||||
/* call HIDIOCGRDESCSIZE ioctl */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
|
||||
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed");
|
||||
|
||||
/* verify the size matches our test report descriptor */
|
||||
ASSERT_EQ(desc_size, sizeof(rdesc))
|
||||
TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRDESC ioctl to get report descriptor data
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rdesc)
|
||||
{
|
||||
struct hidraw_report_descriptor desc;
|
||||
int err;
|
||||
|
||||
/* get the full report descriptor */
|
||||
desc.size = sizeof(rdesc);
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
|
||||
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed");
|
||||
|
||||
/* verify the descriptor data matches our test descriptor */
|
||||
ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0)
|
||||
TH_LOG("report descriptor data mismatch");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRDESC ioctl with smaller buffer size
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rdesc_small_buffer)
|
||||
{
|
||||
struct hidraw_report_descriptor desc;
|
||||
int err;
|
||||
size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */
|
||||
|
||||
/* get partial report descriptor */
|
||||
desc.size = small_size;
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
|
||||
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer");
|
||||
|
||||
/* verify we got the first part of the descriptor */
|
||||
ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0)
|
||||
TH_LOG("partial report descriptor data mismatch");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRAWINFO ioctl to get device information
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rawinfo)
|
||||
{
|
||||
struct hidraw_devinfo devinfo;
|
||||
int err;
|
||||
|
||||
/* get device info */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo);
|
||||
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed");
|
||||
|
||||
/* verify device info matches our test setup */
|
||||
ASSERT_EQ(devinfo.bustype, BUS_USB)
|
||||
TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype);
|
||||
ASSERT_EQ(devinfo.vendor, 0x0001)
|
||||
TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor);
|
||||
ASSERT_EQ(devinfo.product, 0x0a37)
|
||||
TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGFEATURE ioctl to get feature report
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_gfeature)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* set report ID 1 in first byte */
|
||||
buf[0] = 1;
|
||||
|
||||
/* get feature report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
|
||||
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err);
|
||||
|
||||
/* verify we got the expected feature data */
|
||||
ASSERT_EQ(buf[0], feature_data[0])
|
||||
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
|
||||
ASSERT_EQ(buf[1], feature_data[1])
|
||||
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGFEATURE ioctl with invalid report ID
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_gfeature_invalid)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* set invalid report ID (not 1) */
|
||||
buf[0] = 2;
|
||||
|
||||
/* try to get feature report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID");
|
||||
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test ioctl with incorrect nr bits
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_invalid_nr)
|
||||
{
|
||||
char buf[256] = {0};
|
||||
int err;
|
||||
unsigned int bad_cmd;
|
||||
|
||||
/*
|
||||
* craft an ioctl command with wrong _IOC_NR bits
|
||||
*/
|
||||
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */
|
||||
|
||||
/* test the ioctl */
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should have failed");
|
||||
ASSERT_EQ(errno, ENOTTY)
|
||||
TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d", errno);
|
||||
|
||||
/*
|
||||
* craft an ioctl command with wrong _IOC_NR bits
|
||||
*/
|
||||
bad_cmd = _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */
|
||||
|
||||
/* test the ioctl */
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should have failed");
|
||||
ASSERT_EQ(errno, ENOTTY)
|
||||
TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", errno);
|
||||
|
||||
/* also test with bigger number */
|
||||
bad_cmd = _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid as well */
|
||||
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) should have failed");
|
||||
ASSERT_EQ(errno, ENOTTY)
|
||||
TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d", errno);
|
||||
|
||||
/* also test with bigger number: 0x42 is not valid as well */
|
||||
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf));
|
||||
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) should have failed");
|
||||
ASSERT_EQ(errno, ENOTTY)
|
||||
TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test ioctl with incorrect type bits
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_invalid_type)
|
||||
{
|
||||
char buf[256] = {0};
|
||||
int err;
|
||||
unsigned int bad_cmd;
|
||||
|
||||
/*
|
||||
* craft an ioctl command with wrong _IOC_TYPE bits
|
||||
*/
|
||||
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' should be 'H' */
|
||||
|
||||
/* test the ioctl */
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have failed");
|
||||
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_gfeature_invalid_dir)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
unsigned int bad_cmd;
|
||||
|
||||
/* set report ID 1 in first byte */
|
||||
buf[0] = 1;
|
||||
|
||||
/*
|
||||
* craft an ioctl command with wrong _IOC_DIR bits
|
||||
* HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE
|
||||
*/
|
||||
bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
|
||||
|
||||
/* try to get feature report with wrong direction bits */
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
|
||||
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
|
||||
|
||||
/* also test with only _IOC_READ */
|
||||
bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */
|
||||
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed");
|
||||
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test read-only ioctl with incorrect _IOC_DIR bits
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_readonly_invalid_dir)
|
||||
{
|
||||
char buf[256] = {0};
|
||||
int err;
|
||||
unsigned int bad_cmd;
|
||||
|
||||
/*
|
||||
* craft an ioctl command with wrong _IOC_DIR bits
|
||||
* HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE
|
||||
*/
|
||||
bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */
|
||||
|
||||
/* try to get device name with wrong direction bits */
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
|
||||
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
|
||||
|
||||
/* also test with _IOC_WRITE|_IOC_READ */
|
||||
bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */
|
||||
|
||||
err = ioctl(self->hidraw_fd, bad_cmd, buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed");
|
||||
ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCSFEATURE ioctl to set feature report
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_sfeature)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* prepare feature report data */
|
||||
buf[0] = 1; /* report ID */
|
||||
buf[1] = 0x42;
|
||||
buf[2] = 0x24;
|
||||
|
||||
/* set feature report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf);
|
||||
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err);
|
||||
|
||||
/*
|
||||
* Note: The uhid mock doesn't validate the set report data,
|
||||
* so we just verify the ioctl succeeds
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGINPUT ioctl to get input report
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_ginput)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* set report ID 1 in first byte */
|
||||
buf[0] = 1;
|
||||
|
||||
/* get input report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
|
||||
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err);
|
||||
|
||||
/* verify we got the expected input data */
|
||||
ASSERT_EQ(buf[0], feature_data[0])
|
||||
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
|
||||
ASSERT_EQ(buf[1], feature_data[1])
|
||||
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGINPUT ioctl with invalid report ID
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_ginput_invalid)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* set invalid report ID (not 1) */
|
||||
buf[0] = 2;
|
||||
|
||||
/* try to get input report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID");
|
||||
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCSINPUT ioctl to set input report
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_sinput)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* prepare input report data */
|
||||
buf[0] = 1; /* report ID */
|
||||
buf[1] = 0x55;
|
||||
buf[2] = 0xAA;
|
||||
|
||||
/* set input report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf);
|
||||
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err);
|
||||
|
||||
/*
|
||||
* Note: The uhid mock doesn't validate the set report data,
|
||||
* so we just verify the ioctl succeeds
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGOUTPUT ioctl to get output report
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_goutput)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* set report ID 1 in first byte */
|
||||
buf[0] = 1;
|
||||
|
||||
/* get output report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
|
||||
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err);
|
||||
|
||||
/* verify we got the expected output data */
|
||||
ASSERT_EQ(buf[0], feature_data[0])
|
||||
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
|
||||
ASSERT_EQ(buf[1], feature_data[1])
|
||||
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGOUTPUT ioctl with invalid report ID
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_goutput_invalid)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* set invalid report ID (not 1) */
|
||||
buf[0] = 2;
|
||||
|
||||
/* try to get output report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
|
||||
ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID");
|
||||
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCSOUTPUT ioctl to set output report
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_soutput)
|
||||
{
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
/* prepare output report data */
|
||||
buf[0] = 1; /* report ID */
|
||||
buf[1] = 0x33;
|
||||
buf[2] = 0xCC;
|
||||
|
||||
/* set output report */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf);
|
||||
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err);
|
||||
|
||||
/*
|
||||
* Note: The uhid mock doesn't validate the set report data,
|
||||
* so we just verify the ioctl succeeds
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRAWNAME ioctl to get device name string
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rawname)
|
||||
{
|
||||
char name[256] = {0};
|
||||
char expected_name[64];
|
||||
int err;
|
||||
|
||||
/* get device name */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name);
|
||||
ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err);
|
||||
|
||||
/* construct expected name based on device id */
|
||||
snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
|
||||
|
||||
/* verify the name matches expected pattern */
|
||||
ASSERT_EQ(strcmp(name, expected_name), 0)
|
||||
TH_LOG("expected name '%s', got '%s'", expected_name, name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRAWPHYS ioctl to get device physical address string
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rawphys)
|
||||
{
|
||||
char phys[256] = {0};
|
||||
char expected_phys[64];
|
||||
int err;
|
||||
|
||||
/* get device physical address */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys);
|
||||
ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err);
|
||||
|
||||
/* construct expected phys based on device id */
|
||||
snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id);
|
||||
|
||||
/* verify the phys matches expected value */
|
||||
ASSERT_EQ(strcmp(phys, expected_phys), 0)
|
||||
TH_LOG("expected phys '%s', got '%s'", expected_phys, phys);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_rawuniq)
|
||||
{
|
||||
char uniq[256] = {0};
|
||||
int err;
|
||||
|
||||
/* get device unique identifier */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq);
|
||||
ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err);
|
||||
|
||||
/* uniq is typically empty in our test setup */
|
||||
ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test device string ioctls with small buffer sizes
|
||||
*/
|
||||
TEST_F(hidraw, ioctl_strings_small_buffer)
|
||||
{
|
||||
char small_buf[8] = {0};
|
||||
char expected_name[64];
|
||||
int err;
|
||||
|
||||
/* test HIDIOCGRAWNAME with small buffer */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf);
|
||||
ASSERT_EQ(err, sizeof(small_buf))
|
||||
TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err);
|
||||
|
||||
/* construct expected truncated name */
|
||||
snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
|
||||
|
||||
/* verify we got truncated name (first 8 chars, no null terminator guaranteed) */
|
||||
ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0)
|
||||
TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf));
|
||||
|
||||
/* Note: hidraw driver doesn't guarantee null termination when buffer is too small */
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
return test_harness_run(argc, argv);
|
||||
|
|
|
|||
|
|
@ -1,296 +1,474 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Red Hat
|
||||
# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
|
||||
#
|
||||
# Dependencies:
|
||||
# * virtme-ng
|
||||
# * busybox-static (used by virtme-ng)
|
||||
# * qemu (used by virtme-ng)
|
||||
|
||||
set -u
|
||||
set -e
|
||||
readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
|
||||
|
||||
# This script currently only works for x86_64
|
||||
ARCH="$(uname -m)"
|
||||
case "${ARCH}" in
|
||||
x86_64)
|
||||
QEMU_BINARY=qemu-system-x86_64
|
||||
BZIMAGE="arch/x86/boot/bzImage"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
SCRIPT_DIR="$(dirname $(realpath $0))"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/results"
|
||||
KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
|
||||
B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py"
|
||||
NUM_COMPILE_JOBS="$(nproc)"
|
||||
LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
|
||||
LOG_FILE="${LOG_FILE_BASE}.log"
|
||||
EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
|
||||
CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1"
|
||||
source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
|
||||
|
||||
TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
|
||||
DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
|
||||
readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf
|
||||
readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw
|
||||
readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs"
|
||||
readonly SSH_GUEST_PORT=22
|
||||
readonly WAIT_PERIOD=3
|
||||
readonly WAIT_PERIOD_MAX=60
|
||||
readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
|
||||
readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid)
|
||||
|
||||
usage()
|
||||
{
|
||||
cat <<EOF
|
||||
Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>]
|
||||
readonly QEMU_OPTS="\
|
||||
--pidfile ${QEMU_PIDFILE} \
|
||||
"
|
||||
readonly KERNEL_CMDLINE=""
|
||||
readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log)
|
||||
readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest)
|
||||
readonly TEST_DESCS=(
|
||||
"Run hid_bpf tests in the VM."
|
||||
"Run hidraw tests in the VM."
|
||||
"Run the hid-tools test-suite in the VM."
|
||||
)
|
||||
|
||||
<command> is the command you would normally run when you are in
|
||||
the source kernel direcory. e.g:
|
||||
VERBOSE=0
|
||||
SHELL_MODE=0
|
||||
BUILD_HOST=""
|
||||
BUILD_HOST_PODMAN_CONTAINER_NAME=""
|
||||
|
||||
$0 -- ./tools/testing/selftests/hid/hid_bpf
|
||||
usage() {
|
||||
local name
|
||||
local desc
|
||||
local i
|
||||
|
||||
If no command is specified and a debug shell (-s) is not requested,
|
||||
"${DEFAULT_COMMAND}" will be run by default.
|
||||
echo
|
||||
echo "$0 [OPTIONS] [TEST]... [-- tests-args]"
|
||||
echo "If no TEST argument is given, all tests will be run."
|
||||
echo
|
||||
echo "Options"
|
||||
echo " -b: build the kernel from the current source tree and use it for guest VMs"
|
||||
echo " -H: hostname for remote build host (used with -b)"
|
||||
echo " -p: podman container name for remote build host (used with -b)"
|
||||
echo " Example: -H beefyserver -p vng"
|
||||
echo " -q: set the path to or name of qemu binary"
|
||||
echo " -s: start a shell in the VM instead of running tests"
|
||||
echo " -v: more verbose output (can be repeated multiple times)"
|
||||
echo
|
||||
echo "Available tests"
|
||||
|
||||
If you build your kernel using KBUILD_OUTPUT= or O= options, these
|
||||
can be passed as environment variables to the script:
|
||||
|
||||
O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
|
||||
|
||||
or
|
||||
|
||||
KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
|
||||
|
||||
Options:
|
||||
|
||||
-u) Update the boot2container script to a newer version.
|
||||
-d) Update the output directory (default: ${OUTPUT_DIR})
|
||||
-b) Run only the build steps for the kernel and the selftests
|
||||
-j) Number of jobs for compilation, similar to -j in make
|
||||
(default: ${NUM_COMPILE_JOBS})
|
||||
-s) Instead of powering off the VM, start an interactive
|
||||
shell. If <command> is specified, the shell runs after
|
||||
the command finishes executing
|
||||
EOF
|
||||
}
|
||||
|
||||
download()
|
||||
{
|
||||
local file="$1"
|
||||
|
||||
echo "Downloading $file..." >&2
|
||||
curl -Lsf "$file" -o "${@:2}"
|
||||
}
|
||||
|
||||
recompile_kernel()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local make_command="$2"
|
||||
|
||||
cd "${kernel_checkout}"
|
||||
|
||||
${make_command} olddefconfig
|
||||
${make_command} headers
|
||||
${make_command}
|
||||
}
|
||||
|
||||
update_selftests()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"
|
||||
|
||||
cd "${selftests_dir}"
|
||||
${make_command}
|
||||
}
|
||||
|
||||
run_vm()
|
||||
{
|
||||
local run_dir="$1"
|
||||
local b2c="$2"
|
||||
local kernel_bzimage="$3"
|
||||
local command="$4"
|
||||
local post_command=""
|
||||
|
||||
cd "${run_dir}"
|
||||
|
||||
if ! which "${QEMU_BINARY}" &> /dev/null; then
|
||||
cat <<EOF
|
||||
Could not find ${QEMU_BINARY}
|
||||
Please install qemu or set the QEMU_BINARY environment variable.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# alpine (used in post-container requires the PATH to have /bin
|
||||
export PATH=$PATH:/bin
|
||||
|
||||
if [[ "${debug_shell}" != "yes" ]]
|
||||
then
|
||||
touch ${OUTPUT_DIR}/${LOG_FILE}
|
||||
command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
else
|
||||
command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
|
||||
fi
|
||||
|
||||
set +e
|
||||
$b2c --command "${command}" \
|
||||
--kernel ${kernel_bzimage} \
|
||||
--workdir ${OUTPUT_DIR} \
|
||||
--image ${CONTAINER_IMAGE}
|
||||
|
||||
echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
|
||||
|
||||
set -e
|
||||
|
||||
${post_command}
|
||||
}
|
||||
|
||||
is_rel_path()
|
||||
{
|
||||
local path="$1"
|
||||
|
||||
[[ ${path:0:1} != "/" ]]
|
||||
}
|
||||
|
||||
do_update_kconfig()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local kconfig_file="$2"
|
||||
|
||||
rm -f "$kconfig_file" 2> /dev/null
|
||||
|
||||
for config in "${KCONFIG_REL_PATHS[@]}"; do
|
||||
local kconfig_src="${config}"
|
||||
cat "$kconfig_src" >> "$kconfig_file"
|
||||
for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
|
||||
name=${TEST_NAMES[${i}]}
|
||||
desc=${TEST_DESCS[${i}]}
|
||||
printf "\t%-35s%-35s\n" "${name}" "${desc}"
|
||||
done
|
||||
echo
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
update_kconfig()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local kconfig_file="$2"
|
||||
die() {
|
||||
echo "$*" >&2
|
||||
exit "${KSFT_FAIL}"
|
||||
}
|
||||
|
||||
if [[ -f "${kconfig_file}" ]]; then
|
||||
local local_modified="$(stat -c %Y "${kconfig_file}")"
|
||||
vm_ssh() {
|
||||
# vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22'
|
||||
# (ED25519) to the list of known hosts.",
|
||||
# So replace the command with what's actually called and add the "-q" option
|
||||
stdbuf -oL ssh -q \
|
||||
-F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \
|
||||
-l root virtme-ng%${SSH_GUEST_PORT} \
|
||||
"$@"
|
||||
return $?
|
||||
}
|
||||
|
||||
for config in "${KCONFIG_REL_PATHS[@]}"; do
|
||||
local kconfig_src="${config}"
|
||||
local src_modified="$(stat -c %Y "${kconfig_src}")"
|
||||
# Only update the config if it has been updated after the
|
||||
# previously cached config was created. This avoids
|
||||
# unnecessarily compiling the kernel and selftests.
|
||||
if [[ "${src_modified}" -gt "${local_modified}" ]]; then
|
||||
do_update_kconfig "$kernel_checkout" "$kconfig_file"
|
||||
# Once we have found one outdated configuration
|
||||
# there is no need to check other ones.
|
||||
cleanup() {
|
||||
if [[ -s "${QEMU_PIDFILE}" ]]; then
|
||||
pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# If failure occurred during or before qemu start up, then we need
|
||||
# to clean this up ourselves.
|
||||
if [[ -e "${QEMU_PIDFILE}" ]]; then
|
||||
rm "${QEMU_PIDFILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
check_args() {
|
||||
local found
|
||||
|
||||
for arg in "$@"; do
|
||||
found=0
|
||||
for name in "${TEST_NAMES[@]}"; do
|
||||
if [[ "${name}" = "${arg}" ]]; then
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
do_update_kconfig "$kernel_checkout" "$kconfig_file"
|
||||
fi
|
||||
}
|
||||
|
||||
main()
|
||||
{
|
||||
local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
local kernel_checkout=$(realpath "${script_dir}"/../../../../)
|
||||
# By default the script searches for the kernel in the checkout directory but
|
||||
# it also obeys environment variables O= and KBUILD_OUTPUT=
|
||||
local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
|
||||
local command="${DEFAULT_COMMAND}"
|
||||
local update_b2c="no"
|
||||
local debug_shell="no"
|
||||
local build_only="no"
|
||||
|
||||
while getopts ':hsud:j:b' opt; do
|
||||
case ${opt} in
|
||||
u)
|
||||
update_b2c="yes"
|
||||
;;
|
||||
d)
|
||||
OUTPUT_DIR="$OPTARG"
|
||||
;;
|
||||
j)
|
||||
NUM_COMPILE_JOBS="$OPTARG"
|
||||
;;
|
||||
s)
|
||||
command="/bin/sh"
|
||||
debug_shell="yes"
|
||||
;;
|
||||
b)
|
||||
build_only="yes"
|
||||
;;
|
||||
h)
|
||||
if [[ "${found}" -eq 0 ]]; then
|
||||
echo "${arg} is not an available test" >&2
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
\? )
|
||||
echo "Invalid Option: -$OPTARG"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
: )
|
||||
echo "Invalid Option: -$OPTARG requires an argument"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
shift $((OPTIND -1))
|
||||
|
||||
# trap 'catch "$?"' EXIT
|
||||
if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
|
||||
else
|
||||
command="$@"
|
||||
|
||||
if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
|
||||
then
|
||||
debug_shell="yes"
|
||||
fi
|
||||
for arg in "$@"; do
|
||||
if ! command -v > /dev/null "test_${arg}"; then
|
||||
echo "Test ${arg} not found" >&2
|
||||
usage
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_deps() {
|
||||
for dep in vng ${QEMU} busybox pkill ssh pytest; do
|
||||
if [[ ! -x $(command -v "${dep}") ]]; then
|
||||
echo -e "skip: dependency ${dep} not found!\n"
|
||||
exit "${KSFT_SKIP}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then
|
||||
printf "skip: %s not found!" "${HID_BPF_TEST}"
|
||||
printf " Please build the kselftest hid_bpf target.\n"
|
||||
exit "${KSFT_SKIP}"
|
||||
fi
|
||||
|
||||
local kconfig_file="${OUTPUT_DIR}/latest.config"
|
||||
local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
|
||||
|
||||
# Figure out where the kernel is being built.
|
||||
# O takes precedence over KBUILD_OUTPUT.
|
||||
if [[ "${O:=""}" != "" ]]; then
|
||||
if is_rel_path "${O}"; then
|
||||
O="$(realpath "${PWD}/${O}")"
|
||||
fi
|
||||
kernel_bzimage="${O}/${BZIMAGE}"
|
||||
make_command="${make_command} O=${O}"
|
||||
elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
|
||||
if is_rel_path "${KBUILD_OUTPUT}"; then
|
||||
KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
|
||||
fi
|
||||
kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
|
||||
make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
|
||||
fi
|
||||
|
||||
local b2c="${OUTPUT_DIR}/vm2c.py"
|
||||
|
||||
echo "Output directory: ${OUTPUT_DIR}"
|
||||
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
update_kconfig "${kernel_checkout}" "${kconfig_file}"
|
||||
|
||||
recompile_kernel "${kernel_checkout}" "${make_command}"
|
||||
update_selftests "${kernel_checkout}" "${make_command}"
|
||||
|
||||
if [[ "${build_only}" == "no" ]]; then
|
||||
if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
|
||||
echo "vm2c script not found in ${b2c}"
|
||||
update_b2c="yes"
|
||||
fi
|
||||
|
||||
if [[ "${update_b2c}" == "yes" ]]; then
|
||||
download $B2C_URL $b2c
|
||||
chmod +x $b2c
|
||||
fi
|
||||
|
||||
run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
|
||||
if [[ "${debug_shell}" != "yes" ]]; then
|
||||
echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
fi
|
||||
|
||||
exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
|
||||
if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then
|
||||
printf "skip: %s not found!" "${HIDRAW_TEST}"
|
||||
printf " Please build the kselftest hidraw target.\n"
|
||||
exit "${KSFT_SKIP}"
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
check_vng() {
|
||||
local tested_versions
|
||||
local version
|
||||
local ok
|
||||
|
||||
tested_versions=("1.36" "1.37")
|
||||
version="$(vng --version)"
|
||||
|
||||
ok=0
|
||||
for tv in "${tested_versions[@]}"; do
|
||||
if [[ "${version}" == *"${tv}"* ]]; then
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! "${ok}" -eq 1 ]]; then
|
||||
printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
|
||||
printf "not function properly.\n\tThe following versions have been tested: " >&2
|
||||
echo "${tested_versions[@]}" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
handle_build() {
|
||||
if [[ ! "${BUILD}" -eq 1 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
|
||||
echo "-b requires vmtest.sh called from the kernel source tree" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pushd "${KERNEL_CHECKOUT}" &>/dev/null
|
||||
|
||||
if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
|
||||
die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
|
||||
fi
|
||||
|
||||
local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build")
|
||||
|
||||
if [[ -n "${BUILD_HOST}" ]]; then
|
||||
vng_args+=("--build-host" "${BUILD_HOST}")
|
||||
fi
|
||||
|
||||
if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then
|
||||
vng_args+=("--build-host-exec-prefix" \
|
||||
"podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}")
|
||||
fi
|
||||
|
||||
if ! vng "${vng_args[@]}"; then
|
||||
die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
|
||||
fi
|
||||
|
||||
if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then
|
||||
die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})"
|
||||
fi
|
||||
|
||||
if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then
|
||||
die "failed to build HID selftests from source tree (${SCRIPT_DIR})"
|
||||
fi
|
||||
|
||||
popd &>/dev/null
|
||||
}
|
||||
|
||||
vm_start() {
|
||||
local logfile=/dev/null
|
||||
local verbose_opt=""
|
||||
local kernel_opt=""
|
||||
local qemu
|
||||
|
||||
qemu=$(command -v "${QEMU}")
|
||||
|
||||
if [[ "${VERBOSE}" -eq 2 ]]; then
|
||||
verbose_opt="--verbose"
|
||||
logfile=/dev/stdout
|
||||
fi
|
||||
|
||||
# If we are running from within the kernel source tree, use the kernel source tree
|
||||
# as the kernel to boot, otherwise use the currently running kernel.
|
||||
if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then
|
||||
kernel_opt="${KERNEL_CHECKOUT}"
|
||||
fi
|
||||
|
||||
vng \
|
||||
--run \
|
||||
${kernel_opt} \
|
||||
${verbose_opt} \
|
||||
--qemu-opts="${QEMU_OPTS}" \
|
||||
--qemu="${qemu}" \
|
||||
--user root \
|
||||
--append "${KERNEL_CMDLINE}" \
|
||||
--ssh "${SSH_GUEST_PORT}" \
|
||||
--rw &> ${logfile} &
|
||||
|
||||
local vng_pid=$!
|
||||
local elapsed=0
|
||||
|
||||
while [[ ! -s "${QEMU_PIDFILE}" ]]; do
|
||||
if ! kill -0 "${vng_pid}" 2>/dev/null; then
|
||||
echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2
|
||||
die "failed to boot VM"
|
||||
fi
|
||||
|
||||
if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then
|
||||
echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2
|
||||
die "failed to boot VM"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
elapsed=$((elapsed + 1))
|
||||
done
|
||||
}
|
||||
|
||||
vm_wait_for_ssh() {
|
||||
local i
|
||||
|
||||
i=0
|
||||
while true; do
|
||||
if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
|
||||
die "Timed out waiting for guest ssh"
|
||||
fi
|
||||
if vm_ssh -- true; then
|
||||
break
|
||||
fi
|
||||
i=$(( i + 1 ))
|
||||
sleep ${WAIT_PERIOD}
|
||||
done
|
||||
}
|
||||
|
||||
vm_mount_bpffs() {
|
||||
vm_ssh -- mount bpffs -t bpf /sys/fs/bpf
|
||||
}
|
||||
|
||||
__log_stdin() {
|
||||
stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }'
|
||||
}
|
||||
|
||||
__log_args() {
|
||||
echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
|
||||
}
|
||||
|
||||
log() {
|
||||
local verbose="$1"
|
||||
shift
|
||||
|
||||
local prefix="$1"
|
||||
|
||||
shift
|
||||
local redirect=
|
||||
if [[ ${verbose} -le 0 ]]; then
|
||||
redirect=/dev/null
|
||||
else
|
||||
redirect=/dev/stdout
|
||||
fi
|
||||
|
||||
if [[ "$#" -eq 0 ]]; then
|
||||
__log_stdin | tee -a "${LOG}" > ${redirect}
|
||||
else
|
||||
__log_args "$@" | tee -a "${LOG}" > ${redirect}
|
||||
fi
|
||||
}
|
||||
|
||||
log_setup() {
|
||||
log $((VERBOSE-1)) "setup" "$@"
|
||||
}
|
||||
|
||||
log_host() {
|
||||
local testname=$1
|
||||
|
||||
shift
|
||||
log $((VERBOSE-1)) "test:${testname}:host" "$@"
|
||||
}
|
||||
|
||||
log_guest() {
|
||||
local testname=$1
|
||||
|
||||
shift
|
||||
log ${VERBOSE} "# test:${testname}" "$@"
|
||||
}
|
||||
|
||||
test_vm_hid_bpf() {
|
||||
local testname="${FUNCNAME[0]#test_}"
|
||||
|
||||
vm_ssh -- "${HID_BPF_TEST}" \
|
||||
2>&1 | log_guest "${testname}"
|
||||
|
||||
return ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
test_vm_hidraw() {
|
||||
local testname="${FUNCNAME[0]#test_}"
|
||||
|
||||
vm_ssh -- "${HIDRAW_TEST}" \
|
||||
2>&1 | log_guest "${testname}"
|
||||
|
||||
return ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
test_vm_pytest() {
|
||||
local testname="${FUNCNAME[0]#test_}"
|
||||
|
||||
shift
|
||||
|
||||
vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \
|
||||
2>&1 | log_guest "${testname}"
|
||||
|
||||
return ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local vm_oops_cnt_before
|
||||
local vm_warn_cnt_before
|
||||
local vm_oops_cnt_after
|
||||
local vm_warn_cnt_after
|
||||
local name
|
||||
local rc
|
||||
|
||||
vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
|
||||
vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l)
|
||||
|
||||
name=$(echo "${1}" | awk '{ print $1 }')
|
||||
eval test_"${name}" "$@"
|
||||
rc=$?
|
||||
|
||||
vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
|
||||
if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
|
||||
echo "FAIL: kernel oops detected on vm" | log_host "${name}"
|
||||
rc=$KSFT_FAIL
|
||||
fi
|
||||
|
||||
vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l)
|
||||
if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then
|
||||
echo "FAIL: kernel error detected on vm" | log_host "${name}"
|
||||
vm_ssh -- dmesg --level=err | log_host "${name}"
|
||||
rc=$KSFT_FAIL
|
||||
fi
|
||||
|
||||
return "${rc}"
|
||||
}
|
||||
|
||||
QEMU="qemu-system-$(uname -m)"
|
||||
|
||||
while getopts :hvsbq:H:p: o
|
||||
do
|
||||
case $o in
|
||||
v) VERBOSE=$((VERBOSE+1));;
|
||||
s) SHELL_MODE=1;;
|
||||
b) BUILD=1;;
|
||||
q) QEMU=$OPTARG;;
|
||||
H) BUILD_HOST=$OPTARG;;
|
||||
p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;;
|
||||
h|*) usage;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
PARAMS=""
|
||||
|
||||
if [[ ${#} -eq 0 ]]; then
|
||||
ARGS=("${TEST_NAMES[@]}")
|
||||
else
|
||||
ARGS=()
|
||||
COUNT=0
|
||||
for arg in $@; do
|
||||
COUNT=$((COUNT+1))
|
||||
if [[ x"$arg" == x"--" ]]; then
|
||||
break
|
||||
fi
|
||||
ARGS+=($arg)
|
||||
done
|
||||
shift $COUNT
|
||||
PARAMS="$@"
|
||||
fi
|
||||
|
||||
if [[ "${SHELL_MODE}" -eq 0 ]]; then
|
||||
check_args "${ARGS[@]}"
|
||||
echo "1..${#ARGS[@]}"
|
||||
fi
|
||||
check_deps
|
||||
check_vng
|
||||
handle_build
|
||||
|
||||
log_setup "Booting up VM"
|
||||
vm_start
|
||||
vm_wait_for_ssh
|
||||
vm_mount_bpffs
|
||||
log_setup "VM booted up"
|
||||
|
||||
if [[ "${SHELL_MODE}" -eq 1 ]]; then
|
||||
log_setup "Starting interactive shell in VM"
|
||||
echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM."
|
||||
CURRENT_DIR="$(pwd)"
|
||||
vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l"
|
||||
exit "$KSFT_PASS"
|
||||
fi
|
||||
|
||||
cnt_pass=0
|
||||
cnt_fail=0
|
||||
cnt_skip=0
|
||||
cnt_total=0
|
||||
for arg in "${ARGS[@]}"; do
|
||||
run_test "${arg}" "${PARAMS}"
|
||||
rc=$?
|
||||
if [[ ${rc} -eq $KSFT_PASS ]]; then
|
||||
cnt_pass=$(( cnt_pass + 1 ))
|
||||
echo "ok ${cnt_total} ${arg}"
|
||||
elif [[ ${rc} -eq $KSFT_SKIP ]]; then
|
||||
cnt_skip=$(( cnt_skip + 1 ))
|
||||
echo "ok ${cnt_total} ${arg} # SKIP"
|
||||
elif [[ ${rc} -eq $KSFT_FAIL ]]; then
|
||||
cnt_fail=$(( cnt_fail + 1 ))
|
||||
echo "not ok ${cnt_total} ${arg} # exit=$rc"
|
||||
fi
|
||||
cnt_total=$(( cnt_total + 1 ))
|
||||
done
|
||||
|
||||
echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
|
||||
echo "Log: ${LOG}"
|
||||
|
||||
if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
|
||||
exit "$KSFT_PASS"
|
||||
else
|
||||
exit "$KSFT_FAIL"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue