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:
Linus Torvalds 2025-10-04 15:38:04 -07:00
commit 54ba6d9b13
34 changed files with 3166 additions and 1317 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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