Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package xpadneo for openSUSE:Factory checked 
in at 2026-04-01 19:51:25
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/xpadneo (Old)
 and      /work/SRC/openSUSE:Factory/.xpadneo.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "xpadneo"

Wed Apr  1 19:51:25 2026 rev:3 rq:1343916 version:0.10.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/xpadneo/xpadneo.changes  2026-03-08 
17:43:03.326237387 +0100
+++ /work/SRC/openSUSE:Factory/.xpadneo.new.21863/xpadneo.changes       
2026-04-01 19:52:19.785368051 +0200
@@ -1,0 +2,22 @@
+Tue Mar 31 13:11:58 UTC 2026 - Yunhe Guo <[email protected]>
+
+- Update to 0.10.1
+  * xpadneo, licensing: Link the licenses for convenience
+  * xpadneo, rumble: Remove rumble accumulation
+  * xpadneo, rumble: Rename private function `xpadneo_rumble_welcome`
+  * xpadneo, rumble: Introduce helper to calculate throttling delay
+  * xpadneo, rumble: Migrate to more modern scoped locks
+  * xpadneo, docs: Replace back ticks syntax for shell substitution
+  * xpadneo, rumble: Tighten the rumble throttle timing
+  * xpadneo, docs: Fix style of `disable_shift_mode`
+  * xpadneo, power: Fix header export signature
+  * xpadneo, debug: Add new debug mode to improve logging for new devices
+  * xpadneo, debug: Move HID report debugger to new debug module
+  * xpadneo, quirks: Add OUI flag checks for new GameSir Nova
+  * xpadneo, debug: Add Xbox Wireless Controller modern descriptor
+  * xpadneo, debug: Add Xbox Elite 2 Controller descriptor
+  * xpadneo, mouse: Allow disabling the mouse device completely
+  * xpadneo, rumble: Do not send rumble commands unless ready
+  * xpadneo, rumble: Properly save and restore IRQ state during locking
+
+-------------------------------------------------------------------

Old:
----
  xpadneo-0.10.tar.gz

New:
----
  xpadneo-0.10.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ xpadneo.spec ++++++
--- /var/tmp/diff_new_pack.p5H6Vy/_old  2026-04-01 19:52:20.601402054 +0200
+++ /var/tmp/diff_new_pack.p5H6Vy/_new  2026-04-01 19:52:20.605402220 +0200
@@ -18,7 +18,7 @@
 
 
 Name:           xpadneo
-Version:        0.10
+Version:        0.10.1
 Release:        0
 Summary:        Driver for Xbox Wireless Controller
 License:        GPL-3.0-only

++++++ xpadneo-0.10.tar.gz -> xpadneo-0.10.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/.github/ISSUE_TEMPLATE/bug_report.md 
new/xpadneo-0.10.1/.github/ISSUE_TEMPLATE/bug_report.md
--- old/xpadneo-0.10/.github/ISSUE_TEMPLATE/bug_report.md       2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/.github/ISSUE_TEMPLATE/bug_report.md     2026-03-25 
02:59:22.000000000 +0100
@@ -110,7 +110,7 @@
 
 <!-- Paste the output below the line prepended with # -->
 ```console
-# xxd -c20 -g1 
/sys/module/hid_xpadneo/drivers/hid:xpadneo/0005:045E:*/report_descriptor | tee 
>(cksum)
+# dmesg -H | egrep -i 'bt|bluetooth|hci|l2cap|att|xbox|045e|hid|input|xpadneo' 
| tee xpadneo-dmesg.txt
 
 ```
 
@@ -128,8 +128,6 @@
      - systemctl status bluetooth --no-pager
 -->
 
-<!-- Run `dmesg -H | egrep -i 
'bt|bluetooth|hci|l2cap|att|xbox|045e|hid|input|xpadneo' | tee 
xpadneo-dmesg.txt`. -->
-
 <!-- Run `lsusb` and pick the device number of your dongle. -->
 
 <!-- Run `lsusb -v -s## | tee xpadneo-lsusb.txt` where `##` is the device
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/LICENSE.md new/xpadneo-0.10.1/LICENSE.md
--- old/xpadneo-0.10/LICENSE.md 2026-03-08 07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/LICENSE.md       2026-03-25 02:59:22.000000000 +0100
@@ -21,8 +21,8 @@
 
 The full license texts are provided in:
 
-- `LICENSES/LICENSE.gpl-2.0.txt`
-- `LICENSES/LICENSE.gpl-3.0.txt`
+- [GPL-2.0-only](LICENSES/LICENSE.gpl-2.0.txt)
+- [GPL-3.0-or-later](LICENSES/LICENSE.gpl-3.0.txt)
 
 
 ## File-level license identifiers (SPDX)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/NEWS.md new/xpadneo-0.10.1/NEWS.md
--- old/xpadneo-0.10/NEWS.md    2026-03-08 07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/NEWS.md  2026-03-25 02:59:22.000000000 +0100
@@ -1,5 +1,55 @@
 <!-- SPDX-License-Identifier: GPL-2.0-only -->
 
+# Changes since v0.10 up to v0.10.1
+
+This is a focused maintenance release for the v0.10 series with an emphasis on 
stability and compatibility.
+
+The main work in v0.10.1 targets rumble reliability. We now advertise rumble 
support early while deferring actual
+rumble traffic until initialization is fully complete, reducing race 
conditions with userspace stacks that probe
+capabilities immediately after hotplug. In addition, rumble locking was 
corrected to properly preserve IRQ state,
+fixing a regression that could lead to hard lockups under force-feedback load.
+
+Beyond rumble, this update improves day-to-day usability and hardware 
coverage: the optional virtual mouse device can
+now be disabled completely, quirk detection was extended for newer GameSir 
Nova variants, and several
+documentation/debugging details were cleaned up.
+
+As a v0.10 maintenance release, this branch intentionally keeps the existing 
ff-memless-based architecture. The larger
+native-rumble rework remains part of the v0.11 development line.
+
+Thanks to everyone who reported regressions, provided traces, and helped 
validate fixes on real hardware and different
+userspace environments.
+
+
+## Headlines:
+
+  - xpadneo, docs: Replace back ticks syntax for shell substitution
+  - xpadneo, mouse: Allow disabling the mouse device completely
+  - xpadneo, quirks: Add OUI flag checks for new GameSir Nova
+  - xpadneo, rumble: Do not send rumble commands unless ready
+  - xpadneo, rumble: Properly save and restore IRQ state during locking
+
+```
+Kai Krakow (17):
+      xpadneo, licensing: Link the licenses for convenience
+      xpadneo, rumble: Remove rumble accumulation
+      xpadneo, rumble: Rename private function `xpadneo_rumble_welcome`
+      xpadneo, rumble: Introduce helper to calculate throttling delay
+      xpadneo, rumble: Migrate to more modern scoped locks
+      xpadneo, docs: Replace back ticks syntax for shell substitution
+      xpadneo, rumble: Tighten the rumble throttle timing
+      xpadneo, docs: Fix style of `disable_shift_mode`
+      xpadneo, power: Fix header export signature
+      xpadneo, debug: Add new debug mode to improve logging for new devices
+      xpadneo, debug: Move HID report debugger to new debug module
+      xpadneo, quirks: Add OUI flag checks for new GameSir Nova
+      xpadneo, debug: Add Xbox Wireless Controller modern descriptor
+      xpadneo, debug: Add Xbox Elite 2 Controller descriptor
+      xpadneo, mouse: Allow disabling the mouse device completely
+      xpadneo, rumble: Do not send rumble commands unless ready
+      xpadneo, rumble: Properly save and restore IRQ state during locking
+```
+
+
 # Changes since v0.9 up to v0.10
 
 *Code name:*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/VERSION new/xpadneo-0.10.1/VERSION
--- old/xpadneo-0.10/VERSION    2026-03-08 07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/VERSION  2026-03-25 02:59:22.000000000 +0100
@@ -1 +1 @@
-v0.10
+v0.10.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/azure-pipelines.yml 
new/xpadneo-0.10.1/azure-pipelines.yml
--- old/xpadneo-0.10/azure-pipelines.yml        2026-03-08 07:11:35.000000000 
+0100
+++ new/xpadneo-0.10.1/azure-pipelines.yml      2026-03-25 02:59:22.000000000 
+0100
@@ -19,7 +19,7 @@
 steps:
 - script: |
     sudo apt-get update
-    sudo apt-get install -y build-essential pkg-config linux-headers-`uname -r`
+    sudo apt-get install -y build-essential pkg-config linux-headers-$(uname 
-r)
   displayName: 'setup'
 - script: |
     sudo apt-get install -y dkms
@@ -37,3 +37,4 @@
     sudo apt-get install -y libncurses-dev
     make -C misc/examples/c_hidraw
   displayName: 'misc'
+s
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/docs/CONFIGURATION.md 
new/xpadneo-0.10.1/docs/CONFIGURATION.md
--- old/xpadneo-0.10/docs/CONFIGURATION.md      2026-03-08 07:11:35.000000000 
+0100
+++ new/xpadneo-0.10.1/docs/CONFIGURATION.md    2026-03-25 02:59:22.000000000 
+0100
@@ -38,10 +38,16 @@
     - `128` if your controller uses motor-enable bits in reverse
     - `256` if your controller uses motor-enable bits with trigger and main 
motors swapped
     - `512` to avoid having your controller misdetected by heuristics (please 
report a bug)
-- 'disable_shift_mode' (default 0)
+- `disable_shift_mode` (default 0)
   - Let's you disable Xbox logo button shift behavior
   - '0' Xbox logo button will be used as shift
   - '1' will pass through the Xbox logo button as is
+- `disable_mouse` (default 0)
+  - Let's you disable initialization of a mouse device through xpadneo, thus 
disabling mouse mode.
+  - '0' mouse device will be available
+  - '1' mouse device will be absent
+- `debug_descriptor` (default 0)
+  - Enable debug logging for HID descriptor parsing
 
 Some settings may need to be changed at loading time of the module, take a 
look at the following example to see how
 that works:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/docs/README.md 
new/xpadneo-0.10.1/docs/README.md
--- old/xpadneo-0.10/docs/README.md     2026-03-08 07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/docs/README.md   2026-03-25 02:59:22.000000000 +0100
@@ -302,7 +302,9 @@
 - B for <key>Escape</key>
 
 **Important:** The mouse profile won't work if you disabled the shift-mode of 
the Xbox logo button (module parameter
-`disable_shift_mode`).
+`disable_shift_mode`). If you set the new `disable_mouse` module parameter to 
`1`, xpadneo will no longer register a
+mouse device at all, so the Guide button combo will simply pass through to the 
desktop and no mouse emulation will be
+started.
 
 
 ## Getting Started
@@ -333,16 +335,16 @@
 - On **Arch** and Arch-based distributions (like **EndeavourOS**), try
   `sudo pacman -S dkms linux-headers bluez bluez-utils`
 - On **Debian** based systems (like Ubuntu) you can install those packages by 
running
-  ``sudo apt-get install dkms linux-headers-`uname -r` ``
+  `sudo apt-get install dkms linux-headers-$(uname -r)`
 - On **Fedora**, it is
-  ``sudo dnf install dkms make bluez bluez-tools kernel-devel-`uname -r` 
kernel-headers ``
+  `sudo dnf install dkms make bluez bluez-tools kernel-devel-$(uname -r) 
kernel-headers`
 - On **Manjaro** try
   `sudo pacman -S dkms linux-latest-headers bluez bluez-utils`
 - On **openSUSE** (tested on Tumbleweed, should work for Leap), it is
   `sudo zypper install dkms make bluez kernel-devel kernel-source`
 - On **OSMC** you will have to run the following commands
-  ``sudo apt-get install dkms rbp2-headers-`uname -r`\``
-  ``sudo ln -s "/usr/src/rbp2-headers-`uname -r`" "/lib/modules/`uname 
-r`/build"`` (as a [workaround](https://github.com/osmc/osmc/issues/471))
+  `sudo apt-get install dkms rbp2-headers-$(uname -r)`
+  `sudo ln -s "/usr/src/rbp2-headers-$(uname -r)" "/lib/modules/$(uname 
-r)/build"` (as a [workaround](https://github.com/osmc/osmc/issues/471))
 - On **Raspbian**, it is
   `sudo apt-get install dkms raspberrypi-kernel-headers`
   If you recently updated your firmware using `rpi-update` the above package 
may not yet include the header files for
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/docs/descriptors/gamesir_nova_2_lite.md 
new/xpadneo-0.10.1/docs/descriptors/gamesir_nova_2_lite.md
--- old/xpadneo-0.10/docs/descriptors/gamesir_nova_2_lite.md    1970-01-01 
01:00:00.000000000 +0100
+++ new/xpadneo-0.10.1/docs/descriptors/gamesir_nova_2_lite.md  2026-03-25 
02:59:22.000000000 +0100
@@ -0,0 +1,30 @@
+<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
+
+# HID Descriptor for GameSir Nova Lite 2 (Windows mode)
+
+Hex dump of the controller descriptor:
+```
+xpadneo 0005:045E:02E0.000C: report descriptor: length=306 crc16=0x8BC5
+xpadneo 0005:045E:02E0.000C: report descriptor: OUI 1F:45:F3 (0x1F) - 
locally-administered (LAA), multicast
+xpadneo 0005:045E:02E0.000C: report descriptor: dumping 306 unpatched bytes 
(crc16=0x8BC5) [LAA OUI]
+xpadneo hid-desc: 00000000: 05 01 09 05 a1 01 85 01 09 01 a1 00 09 30 09 31
+xpadneo hid-desc: 00000010: 15 00 27 ff ff 00 00 95 02 75 10 81 02 c0 09 01
+xpadneo hid-desc: 00000020: a1 00 09 33 09 34 15 00 27 ff ff 00 00 95 02 75
+xpadneo hid-desc: 00000030: 10 81 02 c0 05 01 09 32 15 00 26 ff 03 95 01 75
+xpadneo hid-desc: 00000040: 0a 81 02 15 00 25 00 75 06 95 01 81 03 05 01 09
+xpadneo hid-desc: 00000050: 35 15 00 26 ff 03 95 01 75 0a 81 02 15 00 25 00
+xpadneo hid-desc: 00000060: 75 06 95 01 81 03 05 01 09 39 15 01 25 08 35 00
+xpadneo hid-desc: 00000070: 46 3b 01 66 14 00 75 04 95 01 81 42 75 04 95 01
+xpadneo hid-desc: 00000080: 15 00 25 00 35 00 45 00 65 00 81 03 05 09 19 01
+xpadneo hid-desc: 00000090: 29 0a 15 00 25 01 75 01 95 0a 81 02 15 00 25 00
+xpadneo hid-desc: 000000a0: 75 06 95 01 81 03 05 01 09 80 85 02 a1 00 09 85
+xpadneo hid-desc: 000000b0: 15 00 25 01 95 01 75 01 81 02 15 00 25 00 75 07
+xpadneo hid-desc: 000000c0: 95 01 81 03 c0 05 0f 09 21 85 03 a1 02 09 97 15
+xpadneo hid-desc: 000000d0: 00 25 01 75 04 95 01 91 02 15 00 25 00 75 04 95
+xpadneo hid-desc: 000000e0: 01 91 03 09 70 15 00 25 64 75 08 95 04 91 02 09
+xpadneo hid-desc: 000000f0: 50 66 01 10 55 0e 15 00 26 ff 00 75 08 95 01 91
+xpadneo hid-desc: 00000100: 02 09 a7 15 00 26 ff 00 75 08 95 01 91 02 65 00
+xpadneo hid-desc: 00000110: 55 00 09 7c 15 00 26 ff 00 75 08 95 01 91 02 c0
+xpadneo hid-desc: 00000120: 85 04 05 06 09 20 15 00 26 ff 00 75 08 95 01 81
+xpadneo hid-desc: 00000130: 02 c0
+```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/docs/heuristics/gamesir-nova.md 
new/xpadneo-0.10.1/docs/heuristics/gamesir-nova.md
--- old/xpadneo-0.10/docs/heuristics/gamesir-nova.md    2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/docs/heuristics/gamesir-nova.md  2026-03-25 
02:59:22.000000000 +0100
@@ -8,14 +8,22 @@
 We also collect OUIs here which should not match the heuristics because we
 are seeing conflicts with some official vendors.
 
-Officially validated OUIs are marked with a "+".
+The best indicators we currently have are the LLA or multicast bits in the MAC
+address, one of which is always set. While the LLA bit is quite weak because
+it is technically allowed, the multicast bit is much stronger because it should
+never be used for controllers, and all other vendors follow that.
 
-| OUI          | Vendor    | Bit representation                      | 
descriptor length
+- Officially validated OUIs are flagged with a "+".
+- LAA OUIs are flagged with an "L".
+- Multicast OUIs are flagged with an "M".
+
+| OUI    Flag  | Vendor    | Bit representation                      | 
descriptor length
 | ------------ | --------- | --------------------------------------- | 
-------------------
-| 3E:42:6C     | GameSir   |   `0011 1110 : 0100 0010 : 0110 1100`   | 283
-| ED:BC:9A     | GameSir   |   `1110 1101 : 1011 1100 : 1001 1010`   | 283
-| 6A:07:14     | GameSir   |   `0110 1010 : 0000 0111 : 0001 0100`   | 283
-| **AND mask** |           | **`0010 1000 : 0000 0000 : 0000 0000`** | **mask 
0x28**
+| 1F:45:F3  LM | GameSir   |   `0001 1111 : 0100 0101 : 1111 0011`   | 306
+| 3E:42:6C  L  | GameSir   |   `0011 1110 : 0100 0010 : 0110 1100`   | 283
+| ED:BC:9A  M  | GameSir   |   `1110 1101 : 1011 1100 : 1001 1010`   | 283
+| 6A:07:14  L  | GameSir   |   `0110 1010 : 0000 0111 : 0001 0100`   | 283
+| **OR Mask**  |           | **`0000 0011 : 0000 0000 : 0000 0000`   | **mask 
0x03**
 | 3C:FA:06  +  | Microsoft |   `0011 1100 : 1111 1010 : 0000 0110`   | 283 
inval match
 | 44:16:22  +  | Microsoft |   `0100 0100 : 0001 0110 : 0010 0010`   |
 | 68:6C:E6  +  | Microsoft |   `0110 1000 : 0110 1100 : 1110 0110`   | 283 
inval match
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/dkms.conf.in 
new/xpadneo-0.10.1/hid-xpadneo/dkms.conf.in
--- old/xpadneo-0.10/hid-xpadneo/dkms.conf.in   2026-03-08 07:11:35.000000000 
+0100
+++ new/xpadneo-0.10.1/hid-xpadneo/dkms.conf.in 2026-03-25 02:59:22.000000000 
+0100
@@ -11,7 +11,7 @@
 MAKE[0]="make -C '${kernel_source_dir}' 
M='${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build/src' 
VERSION='${PACKAGE_VERSION}' modules"
 
 BUILD_EXCLUSIVE_KERNEL_MIN="5.12.0"
-BUILD_EXCLUSIVE_CONFIG="CONFIG_HID CONFIG_INPUT_FF_MEMLESS CONFIG_POWER_SUPPLY"
+BUILD_EXCLUSIVE_CONFIG="CONFIG_HID CONFIG_INPUT_FF_MEMLESS CONFIG_POWER_SUPPLY 
CONFIG_CRC16"
 
 AUTOINSTALL="yes"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/Makefile 
new/xpadneo-0.10.1/hid-xpadneo/src/Makefile
--- old/xpadneo-0.10/hid-xpadneo/src/Makefile   2026-03-08 07:11:35.000000000 
+0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/Makefile 2026-03-25 02:59:22.000000000 
+0100
@@ -7,6 +7,7 @@
 hid-xpadneo-y += \
        xpadneo/consumer.o \
        xpadneo/core.o \
+       xpadneo/debug.o \
        xpadneo/device.o \
        xpadneo/events.o \
        xpadneo/keyboard.o \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/debug.c 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/debug.c
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/debug.c    1970-01-01 
01:00:00.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/debug.c  2026-03-25 
02:59:22.000000000 +0100
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * xpadneo HID descriptor debug helpers
+ *
+ * Logs descriptor size and CRC-16 checksum unconditionally, and dumps the
+ * full unpatched HID descriptor (including OUI flags) automatically for
+ * locally-administered (LAA) addresses or when the debug_descriptor module
+ * parameter is set.
+ *
+ * Copyright (c) 2026 Kai Krakow <[email protected]>
+ */
+
+#include <linux/crc16.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "xpadneo.h"
+
+static bool param_debug_descriptor;
+module_param_named(debug_descriptor, param_debug_descriptor, bool, 0644);
+MODULE_PARM_DESC(debug_descriptor,
+                "(bool) Dump unpatched HID descriptor to dmesg. 0: auto, 1: 
always.");
+
+static bool param_debug_hid;
+module_param_named(debug_hid, param_debug_hid, bool, 0644);
+MODULE_PARM_DESC(debug_hid, "(bool) Debug HID reports. 0: disable, 1: 
enable.");
+
+struct crc_name {
+       u16 crc16;
+       char *name;
+};
+
+const struct crc_name known_checksums[] = {
+       { .crc16 = 0x6500, .name = "Xbox One Elite Series 2" },
+       { .crc16 = 0x8BC5, .name = "Xbox Wireless Controller (legacy)" },
+       { .crc16 = 0x931D, .name = "Xbox Wireless Controller (modern)" },
+};
+
+/*
+ * Parse the first three OUI bytes from a Bluetooth MAC address string of
+ * the form "aa:bb:cc:dd:ee:ff".  Returns 0 on success, -EINVAL otherwise.
+ */
+static int parse_oui(const char *uniq, u8 *b0, u8 *b1, u8 *b2)
+{
+       char tmp[3] = { };
+
+       /* need at least "XX:XX:XX" (8 chars) with ':' separators */
+       if (strnlen(uniq, 9) < 8 || uniq[2] != ':' || uniq[5] != ':')
+               return -EINVAL;
+
+       memcpy(tmp, uniq + 0, 2);
+       if (kstrtou8(tmp, 16, b0))
+               return -EINVAL;
+
+       memcpy(tmp, uniq + 3, 2);
+       if (kstrtou8(tmp, 16, b1))
+               return -EINVAL;
+
+       memcpy(tmp, uniq + 6, 2);
+       if (kstrtou8(tmp, 16, b2))
+               return -EINVAL;
+
+       return 0;
+}
+
+void xpadneo_debug_hid_report(struct hid_device *hdev, const struct 
xpadneo_rumble_report *r,
+                             const size_t len)
+{
+       if (likely(!param_debug_hid))
+               return;
+
+       if (unlikely(len == 0)) {
+               hid_err(hdev, "HID debug: invalid length %zu\n", len);
+               return;
+       }
+
+       switch (r->report_id) {
+       case 0x03:
+               if (unlikely(len != sizeof(*r))) {
+                       hid_info(hdev, "HID debug: len %zu malformed cmd 
0x%02x\n", len,
+                                r->report_id);
+               } else {
+                       hid_info(hdev,
+                                "HID debug: rumble cmd 0x%02x "
+                                "motors left %d right %d strong %d weak %d "
+                                "magnitude left %d right %d strong %d weak %d "
+                                "pulse sustain %dms release %dms loop %d\n",
+                                r->report_id,
+                                !!(r->data.enable & XBOX_RUMBLE_LEFT),
+                                !!(r->data.enable & XBOX_RUMBLE_RIGHT),
+                                !!(r->data.enable & XBOX_RUMBLE_STRONG),
+                                !!(r->data.enable & XBOX_RUMBLE_WEAK),
+                                r->data.magnitude_left, 
r->data.magnitude_right,
+                                r->data.magnitude_strong, 
r->data.magnitude_weak,
+                                r->data.pulse_sustain_10ms * 10,
+                                r->data.pulse_release_10ms * 10, 
r->data.loop_count);
+               }
+               break;
+       default:
+               hid_info(hdev, "HID debug: unhandled cmd 0x%02x\n", 
r->report_id);
+       }
+}
+
+/*
+ * xpadneo_debug_descriptor - log descriptor metadata and optionally hex-dump
+ * @hdev:   HID device whose descriptor is being examined
+ * @rdesc:  pointer to the *unpatched* report descriptor bytes
+ * @rsize:  length of the descriptor in bytes
+ *
+ * Must be called at the very start of report_fixup, before any in-place
+ * modifications, so that the logged data reflects the device's original
+ * descriptor.
+ *
+ * Unconditionally emits one line containing the descriptor byte-length and
+ * CRC-16 checksum (CRC-16/IBM, poly 0x8005, init 0), plus one line
+ * describing the OUI flags.
+ *
+ * Additionally performs a full hex-dump when:
+ *   - the module parameter debug_descriptor=1 is set, OR
+ *   - the device's Bluetooth OUI is locally administered (LAA).
+ */
+void xpadneo_debug_descriptor(struct hid_device *hdev, const __u8 *rdesc, 
unsigned int rsize)
+{
+       u8 oui0 = 0, oui1 = 0, oui2 = 0;
+       u16 crc = crc16(0, rdesc, rsize);
+       bool oui_valid = (parse_oui(hdev->uniq, &oui0, &oui1, &oui2) == 0);
+       bool is_laa = !!(oui0 & XPADNEO_OUI_IS_LAA);
+       bool is_multicast = !!(oui0 & XPADNEO_OUI_IS_MULTICAST);
+       bool do_dump;
+
+       hid_info(hdev, "report descriptor: length %u crc16 0x%04x\n", rsize, 
crc);
+
+       if (oui_valid) {
+               hid_info(hdev,
+                        "report descriptor: OUI %02X:%02X:%02X (0x%02X) - %s, 
%s\n",
+                        oui0, oui1, oui2, oui0,
+                        is_laa ? "locally-administered (LAA)" : 
"globally-assigned",
+                        is_multicast ? "multicast" : "unicast");
+       } else {
+               hid_info(hdev, "report descriptor: OUI unavailable 
(uniq='%.17s')\n", hdev->uniq);
+       }
+
+       do_dump = param_debug_descriptor;
+       if (!do_dump) {
+               for (int i = 0; i < ARRAY_SIZE(known_checksums); i++) {
+                       if (crc == known_checksums[i].crc16) {
+                               hid_info(hdev,
+                                        "report descriptor: known checksum 
crc16 0x%04x name '%s'\n",
+                                        crc, known_checksums[i].name);
+                               break;
+                       }
+               }
+       }
+
+       do_dump = param_debug_descriptor || (oui_valid && is_laa);
+       if (do_dump) {
+               hid_info(hdev,
+                        "report descriptor: dumping %u unpatched bytes crc16 
0x%04x%s\n", rsize,
+                        crc, param_debug_descriptor ? " [forced]" : " [LAA 
OUI]");
+               print_hex_dump(KERN_INFO, "xpadneo hid-desc: ", 
DUMP_PREFIX_OFFSET, 16, 1, rdesc,
+                              rsize, false);
+       }
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/device.c 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/device.c
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/device.c   2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/device.c 2026-03-25 
02:59:22.000000000 +0100
@@ -10,41 +10,12 @@
 
 #include "xpadneo.h"
 
-static bool param_debug_hid;
-module_param_named(debug_hid, param_debug_hid, bool, 0644);
-MODULE_PARM_DESC(debug_hid, "(bool) Debug HID reports. 0: disable, 1: 
enable.");
-
 int xpadneo_device_output_report(struct hid_device *hdev, __u8 *buf, size_t 
len)
 {
        struct xpadneo_rumble_report *r = (struct xpadneo_rumble_report *)buf;
 
-       if (unlikely(param_debug_hid && (len > 0))) {
-               switch (buf[0]) {
-               case 0x03:
-                       if (len >= sizeof(*r)) {
-                               hid_info(hdev,
-                                        "HID debug: len %zu rumble cmd 0x%02x "
-                                        "motors left %d right %d strong %d 
weak %d "
-                                        "magnitude left %d right %d strong %d 
weak %d "
-                                        "pulse sustain %dms release %dms loop 
%d\n",
-                                        len, r->report_id,
-                                        !!(r->data.enable & XBOX_RUMBLE_LEFT),
-                                        !!(r->data.enable & XBOX_RUMBLE_RIGHT),
-                                        !!(r->data.enable & 
XBOX_RUMBLE_STRONG),
-                                        !!(r->data.enable & XBOX_RUMBLE_WEAK),
-                                        r->data.magnitude_left, 
r->data.magnitude_right,
-                                        r->data.magnitude_strong, 
r->data.magnitude_weak,
-                                        r->data.pulse_sustain_10ms * 10,
-                                        r->data.pulse_release_10ms * 10, 
r->data.loop_count);
-                       } else {
-                               hid_info(hdev, "HID debug: len %zu malformed 
cmd 0x%02x\n", len,
-                                        buf[0]);
-                       }
-                       break;
-               default:
-                       hid_info(hdev, "HID debug: len %zu unhandled cmd 
0x%02x\n", len, buf[0]);
-               }
-       }
+       xpadneo_debug_hid_report(hdev, r, len);
+
        return hid_hw_output_report(hdev, buf, len);
 }
 
@@ -93,8 +64,11 @@
 {
        struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
 
+       /* preserve the original descriptor size for post-parse quirk 
heuristics */
        xdata->original_rsize = *rsize;
-       hid_info(hdev, "report descriptor size: %d bytes\n", *rsize);
+
+       /* log size/CRC and optionally hex-dump before any in-place patches */
+       xpadneo_debug_descriptor(hdev, rdesc, *rsize);
 
        /* fixup trailing NUL byte */
        if (rdesc[*rsize - 2] == 0xC0 && rdesc[*rsize - 1] == 0x00) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/events.c 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/events.c
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/events.c   2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/events.c 2026-03-25 
02:59:22.000000000 +0100
@@ -253,8 +253,8 @@
                }
                switch (usage->code) {
                case BTN_SELECT:
-                       if (value == 1)
-                               xpadneo_mouse_toggle(xdata);
+                       if ((value == 1) && xpadneo_mouse_toggle(xdata))
+                               xdata->profile_switched = true;
                        goto stop_processing;
                }
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/mouse.c 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/mouse.c
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/mouse.c    2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/mouse.c  2026-03-25 
02:59:22.000000000 +0100
@@ -6,21 +6,29 @@
  * Copyright (c) 2021 Kai Krakow <[email protected]>
  */
 
+#include <linux/module.h>
+
 #include "xpadneo.h"
 
 /* always include last */
 #include "compat.h"
 
+static bool param_disable_mouse;
+module_param_named(disable_mouse, param_disable_mouse, bool, 0444);
+MODULE_PARM_DESC(disable_mouse,
+                "(bool) Disable mouse device permanently. 0: allow mouse, 1: 
disallow mouse.");
+
 /* Mouse movement deadzone and trigger thresholds (raw values) */
 #define XPADNEO_MOUSE_MOVEMENT_DEADZONE   3072
 #define XPADNEO_TRIGGER_RELEASE_THRESHOLD 384
 #define XPADNEO_TRIGGER_PRESS_THRESHOLD   640
 
-void xpadneo_mouse_toggle(struct xpadneo_devdata *xdata)
+bool xpadneo_mouse_toggle(struct xpadneo_devdata *xdata)
 {
        if (!xdata->mouse.idev) {
                xdata->mouse_mode = false;
                hid_info(xdata->hdev, "mouse not available\n");
+               return false;
        } else if (xdata->mouse_mode) {
                xdata->mouse_mode = false;
                hid_info(xdata->hdev, "mouse mode disabled\n");
@@ -30,7 +38,7 @@
        }
 
        /* Indicate that a request was made */
-       xdata->profile_switched = true;
+       return true;
 }
 
 #define mouse_report_rel(a,v) if((v)!=0)input_report_rel(mouse,(a),(v))
@@ -91,7 +99,7 @@
        struct input_dev *consumer = xdata->consumer.idev;
        struct input_dev *keyboard = xdata->keyboard.idev;
 
-       if (!xdata->mouse_mode)
+       if (!xdata->mouse_mode || !xdata->mouse.idev)
                return 0;
 
        if (usage->type == EV_ABS) {
@@ -203,6 +211,12 @@
 {
        int ret;
 
+       if (param_disable_mouse) {
+               xdata->mouse.idev = NULL;
+               hid_info(xdata->hdev, "Mouse device disabled permanently.\n");
+               return 0;
+       }
+
        if (!xdata->mouse.idev) {
                ret = xpadneo_synthetic_init(xdata, "Mouse", &xdata->mouse);
                if (ret || !xdata->mouse.idev)
@@ -240,16 +254,25 @@
 
 void xpadneo_mouse_init_timer(struct xpadneo_devdata *xdata)
 {
+       if (param_disable_mouse)
+               return;
+
        timer_setup(&xdata->mouse_timer, xpadneo_mouse_report, 0);
        mod_timer(&xdata->mouse_timer, jiffies);
 }
 
 void xpadneo_mouse_remove_timer(struct xpadneo_devdata *xdata)
 {
+       if (param_disable_mouse)
+               return;
+
        timer_delete_sync(&xdata->mouse_timer);
 }
 
 void xpadneo_mouse_remove(struct xpadneo_devdata *xdata)
 {
+       if (param_disable_mouse)
+               return;
+
        xpadneo_synthetic_remove(xdata, "mouse", &xdata->mouse);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/quirks.c 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/quirks.c
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/quirks.c   2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/quirks.c 2026-03-25 
02:59:22.000000000 +0100
@@ -41,24 +41,16 @@
 };
 
 /* MAC OUI masks */
-#define XPADNEO_OUI_MASK(oui, mask)   (((oui)&(mask)) == (mask))
-#define XPADNEO_OUI_MASK_GAMESIR_NOVA 0x28
+#define XPADNEO_OUI_MASK_LAA_MULTICAST (XPADNEO_OUI_IS_MULTICAST | 
XPADNEO_OUI_IS_LAA)
 
 static const struct quirk quirks[] = {
-       DEVICE_OUI_QUIRK("28:EA:0B", XPADNEO_QUIRK_NO_HEURISTICS),
-       DEVICE_OUI_QUIRK("3C:FA:06", XPADNEO_QUIRK_NO_HEURISTICS),
-       DEVICE_OUI_QUIRK("68:6C:E6", XPADNEO_QUIRK_NO_HEURISTICS),
-       DEVICE_OUI_QUIRK("78:86:2E", XPADNEO_QUIRK_NO_HEURISTICS),
        DEVICE_OUI_QUIRK("98:B6:EA",
                         XPADNEO_QUIRK_NO_PULSE | 
XPADNEO_QUIRK_NO_TRIGGER_RUMBLE |
                         XPADNEO_QUIRK_REVERSE_MASK),
        DEVICE_OUI_QUIRK("98:B6:EC",
                         XPADNEO_QUIRK_SIMPLE_CLONE | 
XPADNEO_QUIRK_SWAPPED_MASK),
        DEVICE_OUI_QUIRK("A0:5A:5D", XPADNEO_QUIRK_NO_HAPTICS),
-       DEVICE_OUI_QUIRK("A8:8C:3E", XPADNEO_QUIRK_NO_HEURISTICS),
-       DEVICE_OUI_QUIRK("AC:8E:BD", XPADNEO_QUIRK_NO_HEURISTICS),
        DEVICE_OUI_QUIRK("E4:17:D8", XPADNEO_QUIRK_SIMPLE_CLONE),
-       DEVICE_OUI_QUIRK("EC:83:50", XPADNEO_QUIRK_NO_HEURISTICS),
 };
 
 int xpadneo_quirks_init(struct xpadneo_devdata *xdata)
@@ -66,7 +58,7 @@
        struct hid_device *hdev = xdata->hdev;
        struct input_dev *gamepad = xdata->gamepad.idev;
        u32 quirks_set = 0, quirks_unset = 0, quirks_override = U32_MAX;
-       u8 oui_byte;
+       u8 oui_byte = 0;
        char oui[3] = { };
 
        for (int i = 0; i < ARRAY_SIZE(quirks); i++) {
@@ -138,18 +130,25 @@
        }
 
        /*
+        * Check whether we should enable heuristics checks at all, and then
         * copy the first two characters from the uniq ID (MAC address) and
         * expect it being too big to copy, then `kstrtou8()` converts the
-        * uniq ID "aa:bb:cc:dd:ee:ff" to u8, so we get the first OUI byte
+        * uniq ID "aa:bb:cc:dd:ee:ff" to u8, so we get the first OUI byte.
         */
-       if ((xdata->original_rsize == 283)
-           && ((xdata->quirks & XPADNEO_QUIRK_NO_HEURISTICS) == 0)
+       if (((xdata->quirks & XPADNEO_QUIRK_NO_HEURISTICS) == 0)
            && ((xdata->quirks & XPADNEO_QUIRK_SIMPLE_CLONE) == 0)
            && (strscpy(oui, gamepad->uniq, sizeof(oui)) == -E2BIG)
-           && (kstrtou8(oui, 16, &oui_byte) == 0)
-           && XPADNEO_OUI_MASK(oui_byte, XPADNEO_OUI_MASK_GAMESIR_NOVA)) {
-               hid_info(hdev, "enabling heuristic GameSir Nova quirks\n");
-               xdata->quirks |= XPADNEO_QUIRK_SIMPLE_CLONE;
+           && (kstrtou8(oui, 16, &oui_byte) == 0)) {
+               /*
+                * All known GameSir devices at least one of the LAA or
+                * multicast bits set, and a descriptor length of 283 or 306
+                * bytes.
+                */
+               if (((xdata->original_rsize == 283) || (xdata->original_rsize 
== 306))
+                   && ((oui_byte & XPADNEO_OUI_MASK_LAA_MULTICAST) > 0)) {
+                       hid_info(hdev, "enabling heuristic GameSir quirks\n");
+                       xdata->quirks |= XPADNEO_QUIRK_SIMPLE_CLONE;
+               }
        }
 
        /* handle quirk flags which remove a behavior after heuristics */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/rumble.c 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/rumble.c
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/rumble.c   2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/rumble.c 2026-03-25 
02:59:22.000000000 +0100
@@ -9,12 +9,13 @@
 
 #include <linux/delay.h>
 #include <linux/module.h>
+#include <linux/smp.h>
 
 #include "xpadneo.h"
 #include "helpers.h"
 
 /* timing of rumble commands to work around firmware crashes */
-#define RUMBLE_THROTTLE_DELAY   msecs_to_jiffies(50)
+#define RUMBLE_THROTTLE_DELAY   (msecs_to_jiffies(10)+1)
 #define RUMBLE_THROTTLE_JIFFIES (jiffies + RUMBLE_THROTTLE_DELAY)
 
 /* module parameter "trigger_rumble_mode" */
@@ -39,6 +40,21 @@
 
 static struct workqueue_struct *rumble_wq;
 
+static inline unsigned long calculate_throttling_delay(struct xpadneo_devdata 
*xdata)
+{
+       unsigned long rumble_run_at = jiffies, rumble_throttle_until = 
xdata->rumble.throttle_until;
+
+       if (time_before(rumble_run_at, rumble_throttle_until)) {
+               /* last rumble was recently executed */
+               long delay_work = (long)(rumble_throttle_until - rumble_run_at);
+
+               return clamp(delay_work, 0L, (long)RUMBLE_THROTTLE_DELAY);
+       }
+
+       /* the firmware is ready */
+       return 0;
+}
+
 static void rumble_worker(struct work_struct *work)
 {
        struct xpadneo_devdata *xdata =
@@ -46,7 +62,6 @@
        struct hid_device *hdev = xdata->hdev;
        struct xpadneo_rumble_report *r = xdata->rumble.output_report_dmabuf;
        int ret;
-       unsigned long flags;
 
        memset(r, 0, sizeof(*r));
        r->report_id = XPADNEO_XBOX_RUMBLE_REPORT;
@@ -68,90 +83,94 @@
                r->data.loop_count = 0xEB;
        }
 
-       spin_lock_irqsave(&xdata->rumble.lock, flags);
+       scoped_guard(spinlock_irqsave, &xdata->rumble.lock) {
 
-       /* let our scheduler know we've been called */
-       xdata->rumble.scheduled = false;
+               /* let our scheduler know we've been called */
+               xdata->rumble.scheduled = false;
 
-       if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_TRIGGER_RUMBLE)) {
-               /* do not send these bits if not supported */
-               r->data.enable &= ~XBOX_RUMBLE_TRIGGERS;
-       } else {
-               /* trigger motors */
-               r->data.magnitude_left = xdata->rumble.data.magnitude_left;
-               r->data.magnitude_right = xdata->rumble.data.magnitude_right;
+               /* only proceed once initialization data is globally visible */
+               if (unlikely(!smp_load_acquire(&xdata->rumble.enabled)))
+                       return;
+
+               if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_TRIGGER_RUMBLE)) {
+                       /* do not send these bits if not supported */
+                       r->data.enable &= ~XBOX_RUMBLE_TRIGGERS;
+               } else {
+                       /* trigger motors */
+                       r->data.magnitude_left = 
xdata->rumble.data.magnitude_left;
+                       r->data.magnitude_right = 
xdata->rumble.data.magnitude_right;
+               }
+
+               /* main motors */
+               r->data.magnitude_strong = xdata->rumble.data.magnitude_strong;
+               r->data.magnitude_weak = xdata->rumble.data.magnitude_weak;
+
+               /* do not reprogram motors that have not changed */
+               if (unlikely(xdata->rumble.shadow.magnitude_strong == 
r->data.magnitude_strong))
+                       r->data.enable &= ~XBOX_RUMBLE_STRONG;
+               if (unlikely(xdata->rumble.shadow.magnitude_weak == 
r->data.magnitude_weak))
+                       r->data.enable &= ~XBOX_RUMBLE_WEAK;
+               if (likely(xdata->rumble.shadow.magnitude_left == 
r->data.magnitude_left))
+                       r->data.enable &= ~XBOX_RUMBLE_LEFT;
+               if (likely(xdata->rumble.shadow.magnitude_right == 
r->data.magnitude_right))
+                       r->data.enable &= ~XBOX_RUMBLE_RIGHT;
+
+               /* do not send a report if nothing changed */
+               if (unlikely(r->data.enable == XBOX_RUMBLE_NONE))
+                       return;
+
+               /* shadow our current rumble values for the next cycle */
+               memcpy(&xdata->rumble.shadow, &xdata->rumble.data, 
sizeof(xdata->rumble.data));
+
+               /* set all bits if not supported (some clones require these 
set) */
+               if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK))
+                       r->data.enable = XBOX_RUMBLE_ALL;
+
+               /* reverse the bits for trigger and main motors */
+               if (unlikely(xdata->quirks & XPADNEO_QUIRK_REVERSE_MASK))
+                       r->data.enable = SWAP_BITS(SWAP_BITS(r->data.enable, 1, 
2), 0, 3);
+
+               /* swap the bits of trigger and main motors */
+               if (unlikely(xdata->quirks & XPADNEO_QUIRK_SWAPPED_MASK))
+                       r->data.enable = SWAP_BITS(SWAP_BITS(r->data.enable, 0, 
2), 1, 3);
        }
 
-       /* main motors */
-       r->data.magnitude_strong = xdata->rumble.data.magnitude_strong;
-       r->data.magnitude_weak = xdata->rumble.data.magnitude_weak;
-
-       /* do not reprogram motors that have not changed */
-       if (unlikely(xdata->rumble.shadow.magnitude_strong == 
r->data.magnitude_strong))
-               r->data.enable &= ~XBOX_RUMBLE_STRONG;
-       if (unlikely(xdata->rumble.shadow.magnitude_weak == 
r->data.magnitude_weak))
-               r->data.enable &= ~XBOX_RUMBLE_WEAK;
-       if (likely(xdata->rumble.shadow.magnitude_left == 
r->data.magnitude_left))
-               r->data.enable &= ~XBOX_RUMBLE_LEFT;
-       if (likely(xdata->rumble.shadow.magnitude_right == 
r->data.magnitude_right))
-               r->data.enable &= ~XBOX_RUMBLE_RIGHT;
-
-       /* do not send a report if nothing changed */
-       if (unlikely(r->data.enable == XBOX_RUMBLE_NONE)) {
-               spin_unlock_irqrestore(&xdata->rumble.lock, flags);
-               return;
-       }
-
-       /* shadow our current rumble values for the next cycle */
-       memcpy(&xdata->rumble.shadow, &xdata->rumble.data, 
sizeof(xdata->rumble.data));
-
-       /* clear the magnitudes to properly accumulate the maximum values */
-       xdata->rumble.data.magnitude_left = 0;
-       xdata->rumble.data.magnitude_right = 0;
-       xdata->rumble.data.magnitude_weak = 0;
-       xdata->rumble.data.magnitude_strong = 0;
+       ret = xpadneo_device_output_report(hdev, (__u8 *) r, sizeof(*r));
+       if (ret < 0)
+               hid_warn(hdev, "failed to send FF report: %d\n", ret);
 
        /*
         * throttle next command submission, the firmware doesn't like us to
         * send rumble data any faster
         */
-       xdata->rumble.throttle_until = RUMBLE_THROTTLE_JIFFIES;
-
-       spin_unlock_irqrestore(&xdata->rumble.lock, flags);
-
-       /* set all bits if not supported (some clones require these set) */
-       if (unlikely(xdata->quirks & XPADNEO_QUIRK_NO_MOTOR_MASK))
-               r->data.enable = XBOX_RUMBLE_ALL;
-
-       /* reverse the bits for trigger and main motors */
-       if (unlikely(xdata->quirks & XPADNEO_QUIRK_REVERSE_MASK))
-               r->data.enable = SWAP_BITS(SWAP_BITS(r->data.enable, 1, 2), 0, 
3);
-
-       /* swap the bits of trigger and main motors */
-       if (unlikely(xdata->quirks & XPADNEO_QUIRK_SWAPPED_MASK))
-               r->data.enable = SWAP_BITS(SWAP_BITS(r->data.enable, 0, 2), 1, 
3);
+       scoped_guard(spinlock_irqsave, &xdata->rumble.lock) {
+               xdata->rumble.throttle_until = RUMBLE_THROTTLE_JIFFIES;
+               if (xdata->rumble.scheduled) {
+                       unsigned long delay_work = 
calculate_throttling_delay(xdata);
 
-       ret = xpadneo_device_output_report(hdev, (__u8 *) r, sizeof(*r));
-       if (ret < 0)
-               hid_warn(hdev, "failed to send FF report: %d\n", ret);
+                       mod_delayed_work(rumble_wq, &xdata->rumble.worker, 
delay_work);
+               }
+       }
 }
 
-static inline void update_magnitude(u8 *m, u8 v)
+static inline u8 calculate_magnitude(s32 magnitude, int fraction)
 {
-       *m = v > 0 ? max(*m, v) : 0;
+       return (u8)((magnitude * fraction + S16_MAX) / U16_MAX);
 }
 
 static int rumble_play(struct input_dev *dev, void *data, struct ff_effect 
*effect)
 {
-       unsigned long flags, rumble_run_at, rumble_throttle_until;
-       long delay_work;
        int fraction_TL, fraction_TR, fraction_MAIN, percent_TRIGGERS, 
percent_MAIN;
        s32 weak, strong, max_main;
 
        struct hid_device *hdev = input_get_drvdata(dev);
        struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
 
-       if (effect->type != FF_RUMBLE)
+       /* do not let FF clients run before rumble state is ready */
+       if (unlikely(!smp_load_acquire(&xdata->rumble.enabled)))
+               return 0;
+
+       if (unlikely(effect->type != FF_RUMBLE))
                return 0;
 
        /* copy data from effect structure at the very beginning */
@@ -186,47 +205,35 @@
         */
        max_main = max(weak, strong);
 
-       spin_lock_irqsave(&xdata->rumble.lock, flags);
-
-       /* calculate the physical magnitudes, scale from 16 bit to 0..100 */
-       update_magnitude(&xdata->rumble.data.magnitude_strong,
-                        (u8)((strong * fraction_MAIN + S16_MAX) / U16_MAX));
-       update_magnitude(&xdata->rumble.data.magnitude_weak,
-                        (u8)((weak * fraction_MAIN + S16_MAX) / U16_MAX));
-
-       /* calculate the physical magnitudes, scale from 16 bit to 0..100 */
-       update_magnitude(&xdata->rumble.data.magnitude_left,
-                        (u8)((max_main * fraction_TL + S16_MAX) / U16_MAX));
-       update_magnitude(&xdata->rumble.data.magnitude_right,
-                        (u8)((max_main * fraction_TR + S16_MAX) / U16_MAX));
-
-       /* synchronize: is our worker still scheduled? */
-       if (xdata->rumble.scheduled) {
-               /* the worker is still guarding rumble programming */
-               hid_notice_once(hdev, "throttling rumble reprogramming\n");
-               goto unlock_and_return;
-       }
+       scoped_guard(spinlock_irqsave, &xdata->rumble.lock) {
+               unsigned long delay_work;
 
-       /* we want to run now but may be throttled */
-       rumble_run_at = jiffies;
-       rumble_throttle_until = xdata->rumble.throttle_until;
-       if (time_before(rumble_run_at, rumble_throttle_until)) {
-               /* last rumble was recently executed */
-               delay_work = (long)rumble_throttle_until - (long)rumble_run_at;
-               delay_work = clamp(delay_work, 0L, (long)HZ);
-       } else {
-               /* the firmware is ready */
-               delay_work = 0;
-       }
-
-       /* schedule writing a rumble report to the controller */
-       if (queue_delayed_work(rumble_wq, &xdata->rumble.worker, delay_work))
+               /* calculate the physical magnitudes, scale from 16 bit to 
0..100 */
+               xdata->rumble.data.magnitude_strong = 
calculate_magnitude(strong, fraction_MAIN);
+               xdata->rumble.data.magnitude_weak = calculate_magnitude(weak, 
fraction_MAIN);
+
+               /* calculate the physical magnitudes, scale from 16 bit to 
0..100 */
+               xdata->rumble.data.magnitude_left = 
calculate_magnitude(max_main, fraction_TL);
+               xdata->rumble.data.magnitude_right = 
calculate_magnitude(max_main, fraction_TR);
+
+               /* synchronize: is our worker still scheduled? */
+               if (xdata->rumble.scheduled) {
+                       /* the worker is still guarding rumble programming */
+                       hid_notice_once(hdev, "throttling rumble 
reprogramming\n");
+                       break;
+               }
+
+               /* we want to run now but may be throttled */
+               delay_work = calculate_throttling_delay(xdata);
+
+               /* schedule writing a rumble report to the controller */
+               if (mod_delayed_work(rumble_wq, &xdata->rumble.worker, 
delay_work)) {
+                       /* this should never happen */
+                       hid_err(hdev, "rumble_playback: unexpected scheduling 
state\n");
+               }
                xdata->rumble.scheduled = true;
-       else
-               hid_err(hdev, "lost rumble packet\n");
+       }
 
-unlock_and_return:
-       spin_unlock_irqrestore(&xdata->rumble.lock, flags);
        return 0;
 }
 
@@ -302,7 +309,7 @@
        mdelay(30);
 }
 
-static void xpadneo_rumble_welcome(struct hid_device *hdev)
+static void rumble_welcome(struct hid_device *hdev)
 {
        struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
        struct xpadneo_rumble_report pck = { };
@@ -345,6 +352,10 @@
 {
        struct xpadneo_devdata *xdata = hid_get_drvdata(hdev);
        struct input_dev *gamepad = xdata->gamepad.idev;
+       int ret;
+
+       /* publish that rumble is not ready until init finishes */
+       smp_store_release(&xdata->rumble.enabled, false);
 
        spin_lock_init(&xdata->rumble.lock);
        INIT_DELAYED_WORK(&xdata->rumble.worker, rumble_worker);
@@ -357,14 +368,22 @@
        if (param_trigger_rumble_mode == PARAM_TRIGGER_RUMBLE_DISABLE)
                xdata->quirks |= XPADNEO_QUIRK_NO_TRIGGER_RUMBLE;
 
-       if (param_ff_connect_notify)
-               xpadneo_benchmark(xpadneo_rumble_welcome, hdev);
-
        /* initialize our rumble command throttle */
        xdata->rumble.throttle_until = RUMBLE_THROTTLE_JIFFIES;
 
+       /* set capabilities */
        input_set_capability(gamepad, EV_FF, FF_RUMBLE);
-       return input_ff_create_memless(gamepad, NULL, rumble_play);
+       ret = input_ff_create_memless(gamepad, NULL, rumble_play);
+       if (ret)
+               return ret;
+
+       if (param_ff_connect_notify)
+               xpadneo_benchmark(rumble_welcome, hdev);
+
+       /* publish readiness once all rumble state is initialized */
+       smp_store_release(&xdata->rumble.enabled, true);
+
+       return 0;
 }
 
 int xpadneo_rumble_init_workqueue(void)
@@ -387,5 +406,8 @@
 
 void xpadneo_rumble_remove(struct xpadneo_devdata *xdata)
 {
+       /* disable rumble before removable to prevent queueing new data */
+       smp_store_release(&xdata->rumble.enabled, false);
+
        cancel_delayed_work_sync(&xdata->rumble.worker);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/xpadneo-0.10/hid-xpadneo/src/xpadneo/xpadneo.h 
new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/xpadneo.h
--- old/xpadneo-0.10/hid-xpadneo/src/xpadneo/xpadneo.h  2026-03-08 
07:11:35.000000000 +0100
+++ new/xpadneo-0.10.1/hid-xpadneo/src/xpadneo/xpadneo.h        2026-03-25 
02:59:22.000000000 +0100
@@ -20,6 +20,10 @@
 #include <linux/timer.h>
 #include <linux/workqueue.h>
 
+/* detect locally administered unicast MAC addresses */
+#define XPADNEO_OUI_IS_MULTICAST       (1 << 0)
+#define XPADNEO_OUI_IS_LAA             (1 << 1)
+
 /* button aliases */
 #define BTN_SHARE KEY_F12
 #define BTN_XBOX  BTN_MODE
@@ -169,7 +173,7 @@
                spinlock_t lock;
                struct delayed_work worker;
                unsigned long throttle_until;
-               bool scheduled;
+               bool scheduled, enabled;
                struct xpadneo_rumble_data data;
                struct xpadneo_rumble_data shadow;
                void *output_report_dmabuf;
@@ -184,6 +188,11 @@
 extern void xpadneo_synthetic_remove(struct xpadneo_devdata *, const char *,
                                     struct xpadneo_subdevice *);
 
+/* xpadneo descriptor debug helpers */
+extern void xpadneo_debug_hid_report(struct hid_device *, const struct 
xpadneo_rumble_report *,
+                                    const size_t);
+extern void xpadneo_debug_descriptor(struct hid_device *, const __u8 *, 
unsigned int);
+
 /* xpadneo core device functions */
 extern void xpadneo_device_report(struct hid_device *, struct hid_report *);
 extern void xpadneo_device_missing(struct xpadneo_devdata *, u32);
@@ -209,7 +218,7 @@
 extern int xpadneo_mouse_init(struct xpadneo_devdata *);
 extern void xpadneo_mouse_init_timer(struct xpadneo_devdata *);
 extern void xpadneo_mouse_report(struct timer_list *);
-extern void xpadneo_mouse_toggle(struct xpadneo_devdata *);
+extern bool xpadneo_mouse_toggle(struct xpadneo_devdata *);
 extern int xpadneo_mouse_event(struct xpadneo_devdata *, struct hid_usage *, 
__s32);
 extern int xpadneo_mouse_raw_event(struct xpadneo_devdata *, struct hid_report 
*, u8 *, int);
 extern void xpadneo_mouse_remove_timer(struct xpadneo_devdata *);
@@ -218,7 +227,7 @@
 /* battery and power functions */
 extern int xpadneo_power_init(struct xpadneo_devdata *);
 extern void xpadneo_power_update(struct xpadneo_devdata *, u8);
-extern void xpadneo_power_remove(struct xpadneo_devdata *xdata);
+extern void xpadneo_power_remove(struct xpadneo_devdata *);
 
 /* driver quirks handling */
 extern int xpadneo_quirks_init(struct xpadneo_devdata *);

Reply via email to