* Re: [PATCH 0/2] PCI: Remove no_pci_devices
From: Maciej W. Rozycki @ 2026-04-10 14:13 UTC (permalink / raw)
To: Bjorn Helgaas
Cc: Heiner Kallweit, Dmitry Torokhov, Bjorn Helgaas,
open list:HID CORE LAYER, linux-pci@vger.kernel.org
In-Reply-To: <20260407194812.GA250485@bhelgaas>
On Tue, 7 Apr 2026, Bjorn Helgaas wrote:
> [+cc Maciej]
>
> On Fri, Apr 03, 2026 at 12:15:34AM +0200, Heiner Kallweit wrote:
> > Remove the last user of no_pci_devices(), and then the function itself.
> >
> > Heiner Kallweit (2):
> > input: pc110pad: change PCI check to get rid of orphaned
> > no_pci_devices
> > PCI: Remove no_pci_devices
>
> Applied Dmitry's patch to remove pc110pad and your patch to remove
> no_pci_devices() to pci/enumeration for v7.1, thanks!
What can I say?
This:
static int pc110pad_io = 0x15e0;
seems to be a reasonably safe location in the PC/AT architecture to poke
at blindly, especially while guarded with request_region(). Ralf Brown's
"Ports List" doesn't list anything at location 0x1e0 modulo 0x400, and for
PCI/e we never assign locations 0x100-0x300 modulo 0x400 anyway. With PCI
systems featuring a southbridge ISA is subtractively decoded, so unclaimed
accesses will simply time out.
There could be an issue though with preventing the claimed IRQ from being
used for a legitimate device, and the call to no_pci_devices() only pushes
the problem to non-PCI systems. Poking at the handshake port and checking
for it being fixed at all-ones might be a way to figure out whether there
is anything actually wired there.
Overall this seems a case for a platform device, but these are hard to
wire in arch/x86 given how the platform has evolved. I imagine there's no
way to probe for a PC-110 system, as back in the day stuff used to rely on
manual configuration, though there could be a vendor BIOS call available
for guesswork. A kernel parameter to tell the various odd systems apart
would IMHO be a reasonable approach if this part of the kernel were being
implemented now.
Then I have nothing to say as to the removal of the driver itself; if I
do manage to find time to revive 486 support via emulation as I outlined
last year, then whoever wants the driver they can bring it back too; it's
not like the most complex piece of code I've ever seen.
Thanks for looping me in.
Maciej
^ permalink raw reply
* Re: [PATCH 04/61] ext4: Prefer IS_ERR_OR_NULL over manual NULL check
From: Theodore Ts'o @ 2026-04-10 15:18 UTC (permalink / raw)
To: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs, Philipp Hahn
Cc: Theodore Ts'o, Andreas Dilger
In-Reply-To: <20260310-b4-is_err_or_null-v1-4-bd63b656022d@avm.de>
On Tue, 10 Mar 2026 12:48:30 +0100, Philipp Hahn wrote:
> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
>
> Change generated with coccinelle.
Applied, thanks!
[04/61] ext4: Prefer IS_ERR_OR_NULL over manual NULL check
commit: 1d749e110277ce4103f27bd60d6181e52c0cc1e3
Best regards,
--
Theodore Ts'o <tytso@mit.edu>
^ permalink raw reply
* Re: [PATCH] HID: logitech-dj: fix wrong detection of bad DJ_SHORT output report
From: Jiri Kosina @ 2026-04-10 15:23 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Filipe Laíns, Lee Jones, linux-input, linux-kernel
In-Reply-To: <20260410-fix-logitech-dj-v1-1-5533381f8d1b@kernel.org>
On Fri, 10 Apr 2026, Benjamin Tissoires wrote:
> commit b6a57912854e ("HID: logitech-dj: Prevent REPORT_ID_DJ_SHORT
> related user initiated OOB write") assumed that all HID devices attached
> to the logitech-dj driver was having an output report of DJ_SHORT.
>
> However, on the receiver itself, we have 2 other HID device we attach
> here: the mouse emulation and the keyboard emulation. For those devices
> the value of rep is NULL and we are triggered a segfault here.
>
> This is doubly required because logitech-dj also handles non DJ devices
> that might not have the DJ collection.
>
> Fixes: b6a57912854e ("HID: logitech-dj: Prevent REPORT_ID_DJ_SHORT related user initiated OOB write")
> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
Thanks a lot Benjamin, your CI be praised!
Applied.
--
Jiri Kosina
SUSE Labs
^ permalink raw reply
* Re: [PATCH v3 09/11] dt-bindings: input: Document hid-over-spi DT schema
From: Conor Dooley @ 2026-04-10 17:35 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Jingyuan Liang, Jiri Kosina, Benjamin Tissoires, Jonathan Corbet,
Mark Brown, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, linux-input,
linux-doc, linux-kernel, linux-spi, linux-trace-kernel,
devicetree, hbarnor, tfiga, Dmitry Antipov, Jarrett Schultz
In-Reply-To: <adfdkwq_bF9dirAq@google.com>
[-- Attachment #1: Type: text/plain, Size: 6021 bytes --]
On Thu, Apr 09, 2026 at 10:16:46AM -0700, Dmitry Torokhov wrote:
> On Thu, Apr 09, 2026 at 05:02:11PM +0100, Conor Dooley wrote:
> > On Thu, Apr 02, 2026 at 01:59:46AM +0000, Jingyuan Liang wrote:
> > > Documentation describes the required and optional properties for
> > > implementing Device Tree for a Microsoft G6 Touch Digitizer that
> > > supports HID over SPI Protocol 1.0 specification.
> > >
> > > The properties are common to HID over SPI.
> > >
> > > Signed-off-by: Dmitry Antipov <dmanti@microsoft.com>
> > > Signed-off-by: Jarrett Schultz <jaschultz@microsoft.com>
> > > Signed-off-by: Jingyuan Liang <jingyliang@chromium.org>
> > > ---
> > > .../devicetree/bindings/input/hid-over-spi.yaml | 126 +++++++++++++++++++++
> > > 1 file changed, 126 insertions(+)
> > >
> > > diff --git a/Documentation/devicetree/bindings/input/hid-over-spi.yaml b/Documentation/devicetree/bindings/input/hid-over-spi.yaml
> > > new file mode 100644
> > > index 000000000000..d1b0a2e26c32
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/input/hid-over-spi.yaml
> > > @@ -0,0 +1,126 @@
> > > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/input/hid-over-spi.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: HID over SPI Devices
> > > +
> > > +maintainers:
> > > + - Benjamin Tissoires <benjamin.tissoires@redhat.com>
> > > + - Jiri Kosina <jkosina@suse.cz>
> >
> > Why them and not you, the developers of the series?
> >
> > > +
> > > +description: |+
> > > + HID over SPI provides support for various Human Interface Devices over the
> > > + SPI bus. These devices can be for example touchpads, keyboards, touch screens
> > > + or sensors.
> > > +
> > > + The specification has been written by Microsoft and is currently available
> > > + here: https://www.microsoft.com/en-us/download/details.aspx?id=103325
> > > +
> > > + If this binding is used, the kernel module spi-hid will handle the
> > > + communication with the device and the generic hid core layer will handle the
> > > + protocol.
> >
> > This is not relevant to the binding, please remove it.
> >
> > > +
> > > +allOf:
> > > + - $ref: /schemas/input/touchscreen/touchscreen.yaml#
> > > +
> > > +properties:
> > > + compatible:
> > > + oneOf:
> > > + - items:
> > > + - enum:
> > > + - microsoft,g6-touch-digitizer
> > > + - const: hid-over-spi
> > > + - description: Just "hid-over-spi" alone is allowed, but not recommended.
> > > + const: hid-over-spi
> >
> > Why is it allowed but not recommended? Seems to me like we should
> > require device-specific compatibles.
>
> Why would we want to change the driver code to add a new compatible each
> time a vendor decides to create a chip that is fully hid-spi-protocol
> compliant? Or is the plan to still allow "hid-over-spi" fallback but
> require device-specific compatible that will be ignored unless there is
> device-specific quirk needed?
This has nothing to do with the driver, just the oddity of having a
comment saying that not having a device specific compatible was
permitted by not recommended in a binding. Requiring device-specific
compatibles is the norm after all and a comment like this makes draws
more attention to the fact that this is abnormal. Regardless of what the
driver does, device-specific compatibles should be required.
> > > +
> > > + reg:
> > > + maxItems: 1
> > > +
> > > + interrupts:
> > > + maxItems: 1
> > > +
> > > + reset-gpios:
> > > + maxItems: 1
> > > + description:
> > > + GPIO specifier for the digitizer's reset pin (active low). The line must
> > > + be flagged with GPIO_ACTIVE_LOW.
> > > +
> > > + vdd-supply:
> > > + description:
> > > + Regulator for the VDD supply voltage.
> > > +
> > > + input-report-header-address:
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + minimum: 0
> > > + maximum: 0xffffff
> > > + description:
> > > + A value to be included in the Read Approval packet, listing an address of
> > > + the input report header to be put on the SPI bus. This address has 24
> > > + bits.
> > > +
> > > + input-report-body-address:
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + minimum: 0
> > > + maximum: 0xffffff
> > > + description:
> > > + A value to be included in the Read Approval packet, listing an address of
> > > + the input report body to be put on the SPI bus. This address has 24 bits.
> > > +
> > > + output-report-address:
> > > + $ref: /schemas/types.yaml#/definitions/uint32
> > > + minimum: 0
> > > + maximum: 0xffffff
> > > + description:
> > > + A value to be included in the Output Report sent by the host, listing an
> > > + address where the output report on the SPI bus is to be written to. This
> > > + address has 24 bits.
> > > +
> > > + read-opcode:
> > > + $ref: /schemas/types.yaml#/definitions/uint8
> > > + description:
> > > + Value to be used in Read Approval packets. 1 byte.
> > > +
> > > + write-opcode:
> > > + $ref: /schemas/types.yaml#/definitions/uint8
> > > + description:
> > > + Value to be used in Write Approval packets. 1 byte.
> >
> > Why can none of these things be determined from the device's compatible?
> > On the surface, they like the kinds of things that could/should be.
>
> Why would we want to keep tables of these values in the kernel and again
> have to update the driver for each new chip?
That's pretty normal though innit? It's what match data does.
If someone wants to have properties that communicate data that
can be determined from the compatible, they need to provide
justification why it is being done.
> It also probably firmware-dependent.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* [dtor-input:master] BUILD SUCCESS 875115b82c295277b81b6dfee7debc725f44e854
From: kernel test robot @ 2026-04-10 18:10 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git master
branch HEAD: 875115b82c295277b81b6dfee7debc725f44e854 Input: ims-pcu - fix heap-buffer-overflow in ims_pcu_process_data()
elapsed time: 730m
configs tested: 55
configs skipped: 1
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig gcc-15.2.0
arm allnoconfig clang-23
arm allyesconfig gcc-15.2.0
arm64 allmodconfig clang-19
arm64 allnoconfig gcc-15.2.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
hexagon allmodconfig clang-17
hexagon allnoconfig clang-23
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allyesconfig gcc-14
loongarch allmodconfig clang-19
loongarch allnoconfig clang-23
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig gcc-11.5.0
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig gcc-15.2.0
parisc allyesconfig gcc-15.2.0
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig gcc-15.2.0
riscv allmodconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
s390 allmodconfig clang-18
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig gcc-15.2.0
sh allyesconfig gcc-15.2.0
sparc allnoconfig gcc-15.2.0
sparc64 allmodconfig clang-23
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allyesconfig clang-20
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig gcc-15.2.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] HID: usbhid: replace strlcat with better alternatives
From: Mahad Ibrahim @ 2026-04-10 19:24 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-usb, linux-input, linux-hardening, linux-kernel-mentees,
Shuah Khan, linux-kernel, Mahad Ibrahim
In preparation for the removal of the strlcat() API as per the KSPP,
replace the string concatenation logic in hid-core, usbkbd, and
usbmouse with struct seq_buf, which tracks the current write position
and remaining space internally. The changes implemented include:
- Replace device name and phys concatenation with seq_buf_puts().
- Include Struct seq_buf and its initialization.
- Include header file of seq_buf.
- Replace strlen() with seq_buf_used() on the string buffer which was
tracked by seq_buf to increase speed.
- Add size_t len in files which did not have it.
- Use of strscpy with length in place of strlcat.
Testing: This driver was compiled as a module as well as in-built in
QEMU with the QEMU basic mouse, and QEMU basic keyboard. The testing was
done in the following steps.
- Add Hardware Mouse in QEMU checking the usbhid module.
- Verify dmesg string name of mouse.
- Blacklist hidusb module from auto-loading, and removing the module via
rmmod.
- Load usbmouse module, and reattach QEMU mouse.
- Verify dmesg string name of mouse.
- Repeat same procedure on usbkbd module.
This aligns the driver with KSPP security guidelines.
Link: https://github.com/KSPP/linux/issues/370
Signed-off-by: Mahad Ibrahim <mahad.ibrahim.dev@gmail.com>
---
drivers/hid/usbhid/hid-core.c | 17 ++++++++++-------
drivers/hid/usbhid/usbkbd.c | 15 ++++++++++-----
drivers/hid/usbhid/usbmouse.c | 15 ++++++++++-----
3 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index ddd5d77fb5a5..476308378e90 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -27,6 +27,7 @@
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/string.h>
+#include <linux/seq_buf.h>
#include <linux/usb.h>
@@ -1365,6 +1366,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
struct usb_device *dev = interface_to_usbdev(intf);
struct usbhid_device *usbhid;
struct hid_device *hid;
+ struct seq_buf hid_name;
unsigned int n, has_in = 0;
size_t len;
int ret;
@@ -1399,7 +1401,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
hid->product = le16_to_cpu(dev->descriptor.idProduct);
hid->version = le16_to_cpu(dev->descriptor.bcdDevice);
- hid->name[0] = 0;
+ seq_buf_init(&hid_name, hid->name, sizeof(hid->name));
if (intf->cur_altsetting->desc.bInterfaceProtocol ==
USB_INTERFACE_PROTOCOL_MOUSE)
hid->type = HID_TYPE_USBMOUSE;
@@ -1407,22 +1409,23 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *
hid->type = HID_TYPE_USBNONE;
if (dev->manufacturer)
- strscpy(hid->name, dev->manufacturer, sizeof(hid->name));
+ seq_buf_puts(&hid_name, dev->manufacturer);
if (dev->product) {
if (dev->manufacturer)
- strlcat(hid->name, " ", sizeof(hid->name));
- strlcat(hid->name, dev->product, sizeof(hid->name));
+ seq_buf_puts(&hid_name, " ");
+ seq_buf_puts(&hid_name, dev->product);
}
- if (!strlen(hid->name))
+ if (!seq_buf_used(&hid_name))
snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, hid->phys, sizeof(hid->phys));
- strlcat(hid->phys, "/input", sizeof(hid->phys));
- len = strlen(hid->phys);
+ len = strnlen(hid->phys, sizeof(hid->phys));
+ strscpy(hid->phys + len, "/input", sizeof(hid->phys) - len);
+ len = strnlen(hid->phys, sizeof(hid->phys));
if (len < sizeof(hid->phys) - 1)
snprintf(hid->phys + len, sizeof(hid->phys) - len,
"%d", intf->altsetting[0].desc.bInterfaceNumber);
diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c
index 6b33e6ad0846..83d4df0d7a45 100644
--- a/drivers/hid/usbhid/usbkbd.c
+++ b/drivers/hid/usbhid/usbkbd.c
@@ -20,6 +20,7 @@
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
+#include <linux/seq_buf.h>
/*
* Version Information
@@ -266,8 +267,10 @@ static int usb_kbd_probe(struct usb_interface *iface,
struct usb_endpoint_descriptor *endpoint;
struct usb_kbd *kbd;
struct input_dev *input_dev;
+ struct seq_buf kbd_name;
int i, pipe, maxp;
int error = -ENOMEM;
+ size_t len;
interface = iface->cur_altsetting;
@@ -292,24 +295,26 @@ static int usb_kbd_probe(struct usb_interface *iface,
kbd->usbdev = dev;
kbd->dev = input_dev;
spin_lock_init(&kbd->leds_lock);
+ seq_buf_init(&kbd_name, kbd->name, sizeof(kbd->name));
if (dev->manufacturer)
- strscpy(kbd->name, dev->manufacturer, sizeof(kbd->name));
+ seq_buf_puts(&kbd_name, dev->manufacturer);
if (dev->product) {
if (dev->manufacturer)
- strlcat(kbd->name, " ", sizeof(kbd->name));
- strlcat(kbd->name, dev->product, sizeof(kbd->name));
+ seq_buf_puts(&kbd_name, " ");
+ seq_buf_puts(&kbd_name, dev->product);
}
- if (!strlen(kbd->name))
+ if (!seq_buf_used(&kbd_name))
snprintf(kbd->name, sizeof(kbd->name),
"USB HIDBP Keyboard %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
- strlcat(kbd->phys, "/input0", sizeof(kbd->phys));
+ len = strnlen(kbd->phys, sizeof(kbd->phys));
+ strscpy(kbd->phys + len, "/input0", sizeof(kbd->phys) - len);
input_dev->name = kbd->name;
input_dev->phys = kbd->phys;
diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c
index 7cc4f9558e5f..b3b2abeee614 100644
--- a/drivers/hid/usbhid/usbmouse.c
+++ b/drivers/hid/usbhid/usbmouse.c
@@ -18,6 +18,7 @@
#include <linux/init.h>
#include <linux/usb/input.h>
#include <linux/hid.h>
+#include <linux/seq_buf.h>
/* for apple IDs */
#ifdef CONFIG_USB_HID_MODULE
@@ -110,8 +111,10 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i
struct usb_endpoint_descriptor *endpoint;
struct usb_mouse *mouse;
struct input_dev *input_dev;
+ struct seq_buf mouse_name;
int pipe, maxp;
int error = -ENOMEM;
+ size_t len;
interface = intf->cur_altsetting;
@@ -140,24 +143,26 @@ static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_i
mouse->usbdev = dev;
mouse->dev = input_dev;
+ seq_buf_init(&mouse_name, mouse->name, sizeof(mouse->name));
if (dev->manufacturer)
- strscpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
+ seq_buf_puts(&mouse_name, dev->manufacturer);
if (dev->product) {
if (dev->manufacturer)
- strlcat(mouse->name, " ", sizeof(mouse->name));
- strlcat(mouse->name, dev->product, sizeof(mouse->name));
+ seq_buf_puts(&mouse_name, " ");
+ seq_buf_puts(&mouse_name, dev->product);
}
- if (!strlen(mouse->name))
+ if (!seq_buf_used(&mouse_name))
snprintf(mouse->name, sizeof(mouse->name),
"USB HIDBP Mouse %04x:%04x",
le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
- strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
+ len = strnlen(mouse->phys, sizeof(mouse->phys));
+ strscpy(mouse->phys + len, "/input0", sizeof(mouse->phys) - len);
input_dev->name = mouse->name;
input_dev->phys = mouse->phys;
--
2.39.5
^ permalink raw reply related
* Re: [PATCH v2 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Silvan Jegen @ 2026-04-10 19:44 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <a2b087ef-e814-444a-9c1e-716310ed1948@endrift.com>
Vicki Pfau <vi@endrift.com> wrote:
> Replies inline
>
> On 4/8/26 12:51, Silvan Jegen wrote:
> > Heyhey!
> >
> > Vicki Pfau <vi@endrift.com> wrote:
> >> Hi,
> >>
> >> Replies inline
> >>
> >> On 4/2/26 12:09 PM, Silvan Jegen wrote:
> >>> Hi
> >>>
> >>> Thanks for the patch!
> >>>
> >>> Just some comments and questions inline below.
> >>>
> >>> Vicki Pfau <vi@endrift.com> wrote:
> >>>> This adds a new driver for the Switch 2 controllers. The Switch 2 uses an
> >>>> unusual split-interface design such that input and rumble occur on the main
> >>>> HID interface, but all other communication occurs over a "configuration"
> >>>> interface. This is the case on both USB and Bluetooth, so this new driver
> >>>> uses a split-driver design with the HID interface being the "main" driver
> >>>> and the configuration interface is a secondary driver that looks up to the
> >>>> HID interface, sharing resources on a common struct.
> >>>>
> >>>> Due to using a non-standard pairing interface as well as Bluetooth
> >>>> communications being extremely limited in the kernel, a custom interface
> >>>> between userspace and the kernel will need to be design, along with bringup
I missed this one the first time around but
s/design/designed/
> >>>> in BlueZ. That is beyond the scope of this initial patch, which only
> >>>> contains the generic HID and USB configuration interface drivers.
> >>>>
> >>>> This initial work supports general input for the Joy-Con 2, Pro Controller
> >>>> 2, and GameCube NSO controllers. IMU, rumble and battery support is not yet
> >>>> present.
> >>>>
> >>>> Signed-off-by: Vicki Pfau <vi@endrift.com>
> >>>> ---
> >>>> MAINTAINERS | 1 +
> >>>> drivers/hid/Kconfig | 11 +-
> >>>> drivers/hid/hid-ids.h | 4 +
> >>>> drivers/hid/hid-nintendo.c | 1194 ++++++++++++++++-
> >>>> drivers/hid/hid-nintendo.h | 72 +
> >>>> drivers/input/joystick/Kconfig | 11 +
> >>>> drivers/input/joystick/Makefile | 1 +
> >>>> drivers/input/joystick/nintendo-switch2-usb.c | 353 +++++
> >>>> 8 files changed, 1637 insertions(+), 10 deletions(-)
> >>>> create mode 100644 drivers/hid/hid-nintendo.h
> >>>> create mode 100644 drivers/input/joystick/nintendo-switch2-usb.c
> >>>>
> >>>> diff --git a/MAINTAINERS b/MAINTAINERS
> >>>> index 7b277d5bf3d12..4d1a28df5fd24 100644
> >>>> --- a/MAINTAINERS
> >>>> +++ b/MAINTAINERS
> >>>> @@ -18743,6 +18743,7 @@ F: drivers/scsi/nsp32*
> >>>>
> >>>> NINTENDO HID DRIVER
> >>>> M: Daniel J. Ogorchock <djogorchock@gmail.com>
> >>>> +M: Vicki Pfau <vi@endrift.com>
> >>>> L: linux-input@vger.kernel.org
> >>>> S: Maintained
> >>>> F: drivers/hid/hid-nintendo*
> >>>> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> >>>> index c1d9f7c6a5f23..1a293a6c02c26 100644
> >>>> --- a/drivers/hid/Kconfig
> >>>> +++ b/drivers/hid/Kconfig
> >>>> @@ -826,10 +826,13 @@ config HID_NINTENDO
> >>>> depends on LEDS_CLASS
> >>>> select POWER_SUPPLY
> >>>> help
> >>>> - Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
> >>>> - All controllers support bluetooth, and the Pro Controller also supports
> >>>> - its USB mode. This also includes support for the Nintendo Switch Online
> >>>> - Controllers which include the NES, Genesis, SNES, and N64 controllers.
> >>>> + Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller, as
> >>>> + well as Nintendo Switch 2 Joy-Cons, Pro Controller, and NSO GameCube
> >>>> + controllers. All Switch controllers support bluetooth, and the Pro
> >>>> + Controller also supports its USB mode. This also includes support for
> >>>> + the Nintendo Switch Online Controllers which include the NES, Genesis,
> >>>> + SNES, and N64 controllers. Switch 2 controllers currently only support
> >>>> + USB mode.
> >>>>
> >>>> To compile this driver as a module, choose M here: the
> >>>> module will be called hid-nintendo.
> >>>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> >>>> index 4ab7640b119ac..a794dad7980f3 100644
> >>>> --- a/drivers/hid/hid-ids.h
> >>>> +++ b/drivers/hid/hid-ids.h
> >>>> @@ -1073,6 +1073,10 @@
> >>>> #define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
> >>>> #define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
> >>>> #define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
> >>>> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONR 0x2066
> >>>> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONL 0x2067
> >>>> +#define USB_DEVICE_ID_NINTENDO_NS2_PROCON 0x2069
> >>>> +#define USB_DEVICE_ID_NINTENDO_NS2_GCCON 0x2073
> >>>>
> >>>> #define USB_VENDOR_ID_NOVATEK 0x0603
> >>>> #define USB_DEVICE_ID_NOVATEK_PCT 0x0600
> >>>> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> >>>> index 29008c2cc5304..4ab8d4e7558a1 100644
> >>>> --- a/drivers/hid/hid-nintendo.c
> >>>> +++ b/drivers/hid/hid-nintendo.c
> >>>> @@ -1,11 +1,13 @@
> >>>> // SPDX-License-Identifier: GPL-2.0+
> >>>> /*
> >>>> - * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
> >>>> + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers, as well as
> >>>> + * Nintendo Switch 2 Joy-Cons, Pro Controller, and GameCube Controller
> >>>> *
> >>>> * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
> >>>> * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
> >>>> * Copyright (c) 2022 Emily Strickland <linux@emily.st>
> >>>> * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
> >>>> + * Copyright (c) 2026 Valve Software
> >>>> *
> >>>> * The following resources/projects were referenced for this driver:
> >>>> * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
> >>>> @@ -13,6 +15,8 @@
> >>>> * https://github.com/FrotBot/SwitchProConLinuxUSB
> >>>> * https://github.com/MTCKC/ProconXInput
> >>>> * https://github.com/Davidobot/BetterJoyForCemu
> >>>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
> >>>> + * https://github.com/ndeadly/switch2_controller_research
> >>>> * hid-wiimote kernel hid driver
> >>>> * hid-logitech-hidpp driver
> >>>> * hid-sony driver
> >>>> @@ -29,6 +33,7 @@
> >>>> */
> >>>>
> >>>> #include "hid-ids.h"
> >>>> +#include "hid-nintendo.h"
> >>>> #include <linux/unaligned.h>
> >>>> #include <linux/delay.h>
> >>>> #include <linux/device.h>
> >>>> @@ -41,6 +46,8 @@
> >>>> #include <linux/module.h>
> >>>> #include <linux/power_supply.h>
> >>>> #include <linux/spinlock.h>
> >>>> +#include <linux/usb.h>
> >>>> +#include "usbhid/usbhid.h"
> >>>>
> >>>> /*
> >>>> * Reference the url below for the following HID report defines:
> >>>> @@ -2614,7 +2621,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
> >>>> return ret;
> >>>> }
> >>>>
> >>>> -static int nintendo_hid_event(struct hid_device *hdev,
> >>>> +static int joycon_event(struct hid_device *hdev,
> >>>> struct hid_report *report, u8 *raw_data, int size)
> >>>> {
> >>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> >>>> @@ -2625,7 +2632,7 @@ static int nintendo_hid_event(struct hid_device *hdev,
> >>>> return joycon_ctlr_handle_event(ctlr, raw_data, size);
> >>>> }
> >>>>
> >>>> -static int nintendo_hid_probe(struct hid_device *hdev,
> >>>> +static int joycon_probe(struct hid_device *hdev,
> >>>> const struct hid_device_id *id)
> >>>> {
> >>>> int ret;
> >>>> @@ -2729,7 +2736,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
> >>>> return ret;
> >>>> }
> >>>>
> >>>> -static void nintendo_hid_remove(struct hid_device *hdev)
> >>>> +static void joycon_remove(struct hid_device *hdev)
> >>>> {
> >>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> >>>> unsigned long flags;
> >>>> @@ -2748,7 +2755,9 @@ static void nintendo_hid_remove(struct hid_device *hdev)
> >>>> hid_hw_stop(hdev);
> >>>> }
> >>>>
> >>>> -static int nintendo_hid_resume(struct hid_device *hdev)
> >>>> +#ifdef CONFIG_PM
> >>>> +
> >>>> +static int joycon_resume(struct hid_device *hdev)
> >>>> {
> >>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> >>>> int ret;
> >>>> @@ -2771,7 +2780,7 @@ static int nintendo_hid_resume(struct hid_device *hdev)
> >>>> return ret;
> >>>> }
> >>>>
> >>>> -static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> >>>> +static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
> >>>> {
> >>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
> >>>>
> >>>> @@ -2790,7 +2799,1120 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> >>>> return 0;
> >>>> }
> >>>>
> >>>> +#endif
> >>>> +
> >>>> +/*
> >>>> + * =============================================================================
> >>>> + * Switch 2 support
> >>>> + * =============================================================================
> >>>> + */
> >>>> +#define NS2_BTNR_B BIT(0)
> >>>> +#define NS2_BTNR_A BIT(1)
> >>>> +#define NS2_BTNR_Y BIT(2)
> >>>> +#define NS2_BTNR_X BIT(3)
> >>>> +#define NS2_BTNR_R BIT(4)
> >>>> +#define NS2_BTNR_ZR BIT(5)
> >>>> +#define NS2_BTNR_PLUS BIT(6)
> >>>> +#define NS2_BTNR_RS BIT(7)
> >>>> +
> >>>> +#define NS2_BTNL_DOWN BIT(0)
> >>>> +#define NS2_BTNL_RIGHT BIT(1)
> >>>> +#define NS2_BTNL_LEFT BIT(2)
> >>>> +#define NS2_BTNL_UP BIT(3)
> >>>> +#define NS2_BTNL_L BIT(4)
> >>>> +#define NS2_BTNL_ZL BIT(5)
> >>>> +#define NS2_BTNL_MINUS BIT(6)
> >>>> +#define NS2_BTNL_LS BIT(7)
> >>>> +
> >>>> +#define NS2_BTN3_C BIT(4)
> >>>> +#define NS2_BTN3_SR BIT(6)
> >>>> +#define NS2_BTN3_SL BIT(7)
> >>>> +
> >>>> +#define NS2_BTN_JCR_HOME BIT(0)
> >>>> +#define NS2_BTN_JCR_GR BIT(2)
> >>>> +#define NS2_BTN_JCR_C NS2_BTN3_C
> >>>> +#define NS2_BTN_JCR_SR NS2_BTN3_SR
> >>>> +#define NS2_BTN_JCR_SL NS2_BTN3_SL
> >>>> +
> >>>> +#define NS2_BTN_JCL_CAPTURE BIT(0)
> >>>> +#define NS2_BTN_JCL_GL BIT(2)
> >>>> +#define NS2_BTN_JCL_SR NS2_BTN3_SR
> >>>> +#define NS2_BTN_JCL_SL NS2_BTN3_SL
> >>>> +
> >>>> +#define NS2_BTN_PRO_HOME BIT(0)
> >>>> +#define NS2_BTN_PRO_CAPTURE BIT(1)
> >>>> +#define NS2_BTN_PRO_GR BIT(2)
> >>>> +#define NS2_BTN_PRO_GL BIT(3)
> >>>> +#define NS2_BTN_PRO_C NS2_BTN3_C
> >>>> +
> >>>> +#define NS2_BTN_GC_HOME BIT(0)
> >>>> +#define NS2_BTN_GC_CAPTURE BIT(1)
> >>>> +#define NS2_BTN_GC_C NS2_BTN3_C
> >>>> +
> >>>> +#define NS2_TRIGGER_RANGE 4095
> >>>> +#define NS2_AXIS_MIN -32768
> >>>> +#define NS2_AXIS_MAX 32767
> >>>> +
> >>>> +#define NS2_MAX_PLAYER_ID 8
> >>>> +
> >>>> +#define NS2_MAX_INIT_RETRIES 4
> >>>> +
> >>>> +#define NS2_FLASH_ADDR_SERIAL 0x13002
> >>>> +#define NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB 0x130a8
> >>>> +#define NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB 0x130e8
> >>>> +#define NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB 0x13140
> >>>> +#define NS2_FLASH_ADDR_USER_PRIMARY_CALIB 0x1fc040
> >>>> +#define NS2_FLASH_ADDR_USER_SECONDARY_CALIB 0x1fc080
> >>>> +
> >>>> +#define NS2_FLASH_SIZE_SERIAL 0x10
> >>>> +#define NS2_FLASH_SIZE_FACTORY_AXIS_CALIB 9
> >>>> +#define NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB 2
> >>>> +#define NS2_FLASH_SIZE_USER_AXIS_CALIB 11
> >>>> +
> >>>> +#define NS2_USER_CALIB_MAGIC 0xa1b2
> >>>> +
> >>>> +#define NS2_FEATURE_BUTTONS BIT(0)
> >>>> +#define NS2_FEATURE_ANALOG BIT(1)
> >>>> +#define NS2_FEATURE_IMU BIT(2)
> >>>> +#define NS2_FEATURE_MOUSE BIT(4)
> >>>> +#define NS2_FEATURE_RUMBLE BIT(5)
> >>>> +#define NS2_FEATURE_MAGNETO BIT(7)
> >>>> +
> >>>> +enum switch2_subcmd_flash {
> >>>> + NS2_SUBCMD_FLASH_READ_BLOCK = 0x01,
> >>>> + NS2_SUBCMD_FLASH_WRITE_BLOCK = 0x02,
> >>>> + NS2_SUBCMD_FLASH_ERASE_BLOCK = 0x03,
> >>>> + NS2_SUBCMD_FLASH_READ = 0x04,
> >>>> + NS2_SUBCMD_FLASH_WRITE = 0x05,
> >>>> +};
> >>>> +
> >>>> +enum switch2_subcmd_init {
> >>>> + NS2_SUBCMD_INIT_SELECT_REPORT = 0xa,
> >>>> + NS2_SUBCMD_INIT_USB = 0xd,
> >>>> +};
> >>>> +
> >>>> +enum switch2_subcmd_feature_select {
> >>>> + NS2_SUBCMD_FEATSEL_GET_INFO = 0x1,
> >>>> + NS2_SUBCMD_FEATSEL_SET_MASK = 0x2,
> >>>> + NS2_SUBCMD_FEATSEL_CLEAR_MASK = 0x3,
> >>>> + NS2_SUBCMD_FEATSEL_ENABLE = 0x4,
> >>>> + NS2_SUBCMD_FEATSEL_DISABLE = 0x5,
> >>>> +};
> >>>> +
> >>>> +enum switch2_subcmd_grip {
> >>>> + NS2_SUBCMD_GRIP_GET_INFO = 0x1,
> >>>> + NS2_SUBCMD_GRIP_ENABLE_BUTTONS = 0x2,
> >>>> + NS2_SUBCMD_GRIP_GET_INFO_EXT = 0x3,
> >>>> +};
> >>>> +
> >>>> +enum switch2_subcmd_led {
> >>>> + NS2_SUBCMD_LED_P1 = 0x1,
> >>>> + NS2_SUBCMD_LED_P2 = 0x2,
> >>>> + NS2_SUBCMD_LED_P3 = 0x3,
> >>>> + NS2_SUBCMD_LED_P4 = 0x4,
> >>>> + NS2_SUBCMD_LED_ALL_ON = 0x5,
> >>>> + NS2_SUBCMD_LED_ALL_OFF = 0x6,
> >>>> + NS2_SUBCMD_LED_PATTERN = 0x7,
> >>>> + NS2_SUBCMD_LED_BLINK = 0x8,
> >>>> +};
> >>>> +
> >>>> +enum switch2_subcmd_fw_info {
> >>>> + NS2_SUBCMD_FW_INFO_GET = 0x1,
> >>>> +};
> >>>> +
> >>>> +enum switch2_ctlr_type {
> >>>> + NS2_CTLR_TYPE_JCL = 0x00,
> >>>> + NS2_CTLR_TYPE_JCR = 0x01,
> >>>> + NS2_CTLR_TYPE_PRO = 0x02,
> >>>> + NS2_CTLR_TYPE_GC = 0x03,
> >>>> +};
> >>>> +
> >>>> +enum switch2_report_id {
> >>>> + NS2_REPORT_UNIFIED = 0x05,
> >>>> + NS2_REPORT_JCL = 0x07,
> >>>> + NS2_REPORT_JCR = 0x08,
> >>>> + NS2_REPORT_PRO = 0x09,
> >>>> + NS2_REPORT_GC = 0x0a,
> >>>> +};
> >>>> +
> >>>> +enum switch2_init_step {
> >>>> + NS2_INIT_READ_SERIAL,
> >>>> + NS2_INIT_GET_FIRMWARE_INFO,
> >>>> + NS2_INIT_READ_FACTORY_PRIMARY_CALIB,
> >>>> + NS2_INIT_READ_FACTORY_SECONDARY_CALIB,
> >>>> + NS2_INIT_READ_FACTORY_TRIGGER_CALIB,
> >>>> + NS2_INIT_READ_USER_PRIMARY_CALIB,
> >>>> + NS2_INIT_READ_USER_SECONDARY_CALIB,
> >>>> + NS2_INIT_SET_FEATURE_MASK,
> >>>> + NS2_INIT_ENABLE_FEATURES,
> >>>> + NS2_INIT_GRIP_BUTTONS,
> >>>> + NS2_INIT_REPORT_FORMAT,
> >>>> + NS2_INIT_SET_PLAYER_LEDS,
> >>>> + NS2_INIT_INPUT,
> >>>> + NS2_INIT_FINISH,
> >>>> + NS2_INIT_DONE,
> >>>> +};
> >>>> +
> >>>> +struct switch2_version_info {
> >>>> + uint8_t major;
> >>>> + uint8_t minor;
> >>>> + uint8_t patch;
> >>>> + uint8_t ctlr_type;
> >>>> + __le32 unk;
> >>>> + int8_t dsp_major;
> >>>> + int8_t dsp_minor;
> >>>> + int8_t dsp_patch;
> >>>> + int8_t dsp_type;
> >>>> +};
> >>>> +
> >>>> +struct switch2_axis_calibration {
> >>>> + uint16_t neutral;
> >>>> + uint16_t negative;
> >>>> + uint16_t positive;
> >>>> +};
> >>>> +
> >>>> +struct switch2_stick_calibration {
> >>>> + struct switch2_axis_calibration x;
> >>>> + struct switch2_axis_calibration y;
> >>>> +};
> >>>> +
> >>>> +struct switch2_controller {
> >>>> + struct hid_device *hdev;
> >>>> + struct switch2_cfg_intf *cfg;
> >>>> +
> >>>> + char name[64];
> >>>> + char phys[64];
> >>>> + struct list_head entry;
> >>>> + struct mutex lock;
> >>>> +
> >>>> + enum switch2_ctlr_type ctlr_type;
> >>>> + enum switch2_init_step init_step;
> >>>> + struct input_dev __rcu *input;
> >>>> + char serial[NS2_FLASH_SIZE_SERIAL + 1];
> >>>> + struct switch2_version_info version;
> >>>> +
> >>>> + struct switch2_stick_calibration stick_calib[2];
> >>>> + uint8_t lt_zero;
> >>>> + uint8_t rt_zero;
> >>>> +
> >>>> + uint32_t player_id;
> >>>> + struct led_classdev leds[4];
> >>>> +};
> >>>> +
> >>>> +static DEFINE_MUTEX(switch2_controllers_lock);
> >>>> +static LIST_HEAD(switch2_controllers);
> >>>> +
> >>>> +struct switch2_ctlr_button_mapping {
> >>>> + uint32_t code;
> >>>> + int byte;
> >>>> + uint32_t bit;
> >>>> +};
> >>>> +
> >>>> +static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[] = {
> >>>> + { BTN_DPAD_LEFT, 0, NS2_BTNL_LEFT, },
> >>>> + { BTN_DPAD_UP, 0, NS2_BTNL_UP, },
> >>>> + { BTN_DPAD_DOWN, 0, NS2_BTNL_DOWN, },
> >>>> + { BTN_DPAD_RIGHT, 0, NS2_BTNL_RIGHT, },
> >>>> + { BTN_TL, 0, NS2_BTNL_L, },
> >>>> + { BTN_TL2, 0, NS2_BTNL_ZL, },
> >>>> + { BTN_SELECT, 0, NS2_BTNL_MINUS, },
> >>>> + { BTN_THUMBL, 0, NS2_BTNL_LS, },
> >>>> + { KEY_RECORD, 1, NS2_BTN_JCL_CAPTURE, },
> >>>> + { BTN_GRIPR, 1, NS2_BTN_JCL_SL, },
> >>>> + { BTN_GRIPR2, 1, NS2_BTN_JCL_SR, },
> >>>> + { BTN_GRIPL, 1, NS2_BTN_JCL_GL, },
> >>>> + { /* sentinel */ },
> >>>> +};
> >>>> +
> >>>> +static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings[] = {
> >>>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
> >>>> + { BTN_EAST, 0, NS2_BTNR_B, },
> >>>> + { BTN_NORTH, 0, NS2_BTNR_X, },
> >>>> + { BTN_WEST, 0, NS2_BTNR_Y, },
> >>>> + { BTN_TR, 0, NS2_BTNR_R, },
> >>>> + { BTN_TR2, 0, NS2_BTNR_ZR, },
> >>>> + { BTN_START, 0, NS2_BTNR_PLUS, },
> >>>> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
> >>>> + { BTN_C, 1, NS2_BTN_JCR_C, },
> >>>> + { BTN_MODE, 1, NS2_BTN_JCR_HOME, },
> >>>> + { BTN_GRIPL2, 1, NS2_BTN_JCR_SL, },
> >>>> + { BTN_GRIPL, 1, NS2_BTN_JCR_SR, },
> >>>> + { BTN_GRIPR, 1, NS2_BTN_JCR_GR, },
> >>>> + { /* sentinel */ },
> >>>> +};
> >>>> +
> >>>> +static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
> >>>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
> >>>> + { BTN_EAST, 0, NS2_BTNR_B, },
> >>>> + { BTN_NORTH, 0, NS2_BTNR_X, },
> >>>> + { BTN_WEST, 0, NS2_BTNR_Y, },
> >>>> + { BTN_TL, 1, NS2_BTNL_L, },
> >>>> + { BTN_TR, 0, NS2_BTNR_R, },
> >>>> + { BTN_TL2, 1, NS2_BTNL_ZL, },
> >>>> + { BTN_TR2, 0, NS2_BTNR_ZR, },
> >>>> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
> >>>> + { BTN_START, 0, NS2_BTNR_PLUS, },
> >>>> + { BTN_THUMBL, 1, NS2_BTNL_LS, },
> >>>> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
> >>>> + { BTN_MODE, 2, NS2_BTN_PRO_HOME },
> >>>> + { KEY_RECORD, 2, NS2_BTN_PRO_CAPTURE },
> >>>> + { BTN_GRIPR, 2, NS2_BTN_PRO_GR },
> >>>> + { BTN_GRIPL, 2, NS2_BTN_PRO_GL },
> >>>> + { BTN_C, 2, NS2_BTN_PRO_C },
> >>>> + { /* sentinel */ },
> >>>> +};
> >>>> +
> >>>> +static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
> >>>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
> >>>> + { BTN_EAST, 0, NS2_BTNR_B, },
> >>>> + { BTN_NORTH, 0, NS2_BTNR_X, },
> >>>> + { BTN_WEST, 0, NS2_BTNR_Y, },
> >>>> + { BTN_TL2, 1, NS2_BTNL_L, },
> >>>> + { BTN_TR2, 0, NS2_BTNR_R, },
> >>>> + { BTN_TL, 1, NS2_BTNL_ZL, },
> >>>> + { BTN_TR, 0, NS2_BTNR_ZR, },
> >>>> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
> >>>> + { BTN_START, 0, NS2_BTNR_PLUS, },
> >>>> + { BTN_MODE, 2, NS2_BTN_GC_HOME },
> >>>> + { KEY_RECORD, 2, NS2_BTN_GC_CAPTURE },
> >>>> + { BTN_C, 2, NS2_BTN_GC_C },
> >>>> + { /* sentinel */ },
> >>>> +};
> >>>> +
> >>>> +static const uint8_t switch2_init_cmd_data[] = {
> >>>> + /*
> >>>> + * The last 6 bytes of this packet are the MAC address of
> >>>> + * the console, but we don't need that for USB
> >>>> + */
> >>>> + 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
> >>>> +};
> >>>> +
> >>>> +static const uint8_t switch2_one_data[] = { 0x01, 0x00, 0x00, 0x00 };
> >>>> +
> >>>> +static const uint8_t switch2_feature_mask[] = {
> >>>> + NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU,
> >>>> + 0x00, 0x00, 0x00
> >>>> +};
> >>>> +
> >>>> +static void switch2_init_step_done(struct switch2_controller *ns2, enum switch2_init_step step)
> >>>> +{
> >>>> + if (ns2->init_step != step)
> >>>> + return;
> >>>> +
> >>>> + ns2->init_step++;
> >>>> +}
> >>>> +
> >>>> +static inline bool switch2_ctlr_is_joycon(enum switch2_ctlr_type type)
> >>>> +{
> >>>> + return type == NS2_CTLR_TYPE_JCL || type == NS2_CTLR_TYPE_JCR;
> >>>> +}
> >>>> +
> >>>> +static int switch2_set_leds(struct switch2_controller *ns2)
> >>>> +{
> >>>> + int i;
> >>>> + uint8_t message[8] = { 0 };
> >>>> +
> >>>> + for (i = 0; i < JC_NUM_LEDS; i++)
> >>>> + message[0] |= (!!ns2->leds[i].brightness) << i;
> >>>> +
> >>>> + if (!ns2->cfg)
> >>>> + return -ENOTCONN;
> >>>> + return ns2->cfg->send_command(NS2_CMD_LED, NS2_SUBCMD_LED_PATTERN,
> >>>> + &message, sizeof(message),
> >>>> + ns2->cfg);
> >>>> +}
> >>>> +
> >>>> +static int switch2_player_led_brightness_set(struct led_classdev *led,
> >>>> + enum led_brightness brightness)
> >>>> +{
> >>>> + struct device *dev = led->dev->parent;
> >>>> + struct hid_device *hdev = to_hid_device(dev);
> >>>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> >>>> +
> >>>> + if (!ns2)
> >>>> + return -ENODEV;
> >>>> +
> >>>> + guard(mutex)(&ns2->lock);
> >>>> + return switch2_set_leds(ns2);
> >>>> +}
> >>>> +
> >>>> +static void switch2_leds_create(struct switch2_controller *ns2)
> >>>> +{
> >>>> + struct hid_device *hdev = ns2->hdev;
> >>>> + struct led_classdev *led;
> >>>> + int i;
> >>>> + int player_led_pattern;
> >>>> +
> >>>> + player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
> >>>> + hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
> >>>> +
> >>>> + for (i = 0; i < JC_NUM_LEDS; i++) {
> >>>> + led = &ns2->leds[i];
> >>>> + led->brightness = joycon_player_led_patterns[player_led_pattern][i];
> >>>> + led->max_brightness = 1;
> >>>> + led->brightness_set_blocking = switch2_player_led_brightness_set;
> >>>> + led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
> >>>> + }
> >>>> +}
> >>>> +
> >>>> +static void switch2_config_buttons(struct input_dev *idev,
> >>>> + const struct switch2_ctlr_button_mapping button_mappings[])
> >>>> +{
> >>>> + const struct switch2_ctlr_button_mapping *button;
> >>>> +
> >>>> + for (button = button_mappings; button->code; button++)
> >>>> + input_set_capability(idev, EV_KEY, button->code);
> >>>> +}
> >>>> +
> >>>> +static int switch2_init_input(struct switch2_controller *ns2)
> >>>> +{
> >>>> + struct input_dev *input;
> >>>> + struct hid_device *hdev = ns2->hdev;
> >>>> + int i;
> >>>> + int ret;
> >>>> +
> >>>> + switch2_init_step_done(ns2, NS2_INIT_FINISH);
> >>>> +
> >>>> + rcu_read_lock();
> >>>> + input = rcu_dereference(ns2->input);
> >>>> + rcu_read_unlock();
> >>>> +
> >>>> + if (input)
> >>>> + return 0;
> >>>> +
> >>>> + input = devm_input_allocate_device(&hdev->dev);
> >>>> + if (!input)
> >>>> + return -ENOMEM;
> >>>> +
> >>>> + input_set_drvdata(input, ns2);
> >>>> + input->dev.parent = &hdev->dev;
> >>>> + input->id.bustype = hdev->bus;
> >>>> + input->id.vendor = hdev->vendor;
> >>>> + input->id.product = hdev->product;
> >>>> + input->id.version = hdev->version;
> >>>> + input->uniq = ns2->serial;
> >>>> + input->name = ns2->name;
> >>>> + input->phys = hdev->phys;
> >>>> +
> >>>> + switch (ns2->ctlr_type) {
> >>>> + case NS2_CTLR_TYPE_JCL:
> >>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + switch2_config_buttons(input, ns2_left_joycon_button_mappings);
> >>>> + break;
> >>>> + case NS2_CTLR_TYPE_JCR:
> >>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + switch2_config_buttons(input, ns2_right_joycon_button_mappings);
> >>>> + break;
> >>>> + case NS2_CTLR_TYPE_GC:
> >>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_Z, 0, NS2_TRIGGER_RANGE, 32, 128);
> >>>> + input_set_abs_params(input, ABS_RZ, 0, NS2_TRIGGER_RANGE, 32, 128);
> >>>> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
> >>>> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
> >>>> + switch2_config_buttons(input, ns2_gccon_mappings);
> >>>> + break;
> >>>> + case NS2_CTLR_TYPE_PRO:
> >>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
> >>>> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
> >>>> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
> >>>> + switch2_config_buttons(input, ns2_procon_mappings);
> >>>> + break;
> >>>> + default:
> >>>> + input_free_device(input);
> >>>> + return -EINVAL;
> >>>> + }
> >>>> +
> >>>> + hid_info(ns2->hdev, "Firmware version %u.%u.%u (type %i)\n", ns2->version.major,
> >>>> + ns2->version.minor, ns2->version.patch, ns2->version.ctlr_type);
> >>>> + if (ns2->version.dsp_type >= 0)
> >>>> + hid_info(ns2->hdev, "DSP version %u.%u.%u\n", ns2->version.dsp_major,
> >>>> + ns2->version.dsp_minor, ns2->version.dsp_patch);
> >>>> +
> >>>> + ret = input_register_device(input);
> >>>> + if (ret < 0) {
> >>>> + hid_err(ns2->hdev, "Failed to register input; ret=%d\n", ret);
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + for (i = 0; i < JC_NUM_LEDS; i++) {
> >>>> + struct led_classdev *led = &ns2->leds[i];
> >>>> + char *name = devm_kasprintf(&input->dev, GFP_KERNEL, "%s:%s:%s",
> >>>> + dev_name(&input->dev),
> >>>> + "green",
> >>>> + joycon_player_led_names[i]);
> >>>> +
> >>>> + if (!name)
> >>>> + return -ENOMEM;
> >>>> +
> >>>> + led->name = name;
> >>>> + ret = devm_led_classdev_register(&input->dev, led);
> >>>> + if (ret < 0) {
> >>>> + dev_err(&input->dev, "Failed to register player %d LED; ret=%d\n",
> >>>> + i + 1, ret);
> >>>> + input_unregister_device(input);
> >>>> + return ret;
> >>>> + }
> >>>> + }
> >>>> +
> >>>> + rcu_assign_pointer(ns2->input, input);
> >>>> + synchronize_rcu();
> >>>> + return 0;
> >>>> +}
> >>>> +
> >>>> +static struct switch2_controller *switch2_get_controller(const char *phys)
> >>>> +{
> >>>> + struct switch2_controller *ns2;
> >>>> +
> >>>> + guard(mutex)(&switch2_controllers_lock);
> >>>> + list_for_each_entry(ns2, &switch2_controllers, entry) {
> >>>> + if (strncmp(ns2->phys, phys, sizeof(ns2->phys)) == 0)
> >>>> + return ns2;
> >>>> + }
> >>>> + ns2 = kzalloc(sizeof(*ns2), GFP_KERNEL);
> >>>> + if (!ns2)
> >>>> + return ERR_PTR(-ENOMEM);
> >>>> +
> >>>> + mutex_init(&ns2->lock);
> >>>> + INIT_LIST_HEAD(&ns2->entry);
> >>>> + list_add(&ns2->entry, &switch2_controllers);
> >>>> + strscpy(ns2->phys, phys, sizeof(ns2->phys));
> >>>> + return ns2;
> >>>> +}
> >>>> +
> >>>> +static void switch2_controller_put(struct switch2_controller *ns2)
> >>>> +{
> >>>> + struct input_dev *input;
> >>>> + bool do_free;
> >>>> +
> >>>> + guard(mutex)(&switch2_controllers_lock);
> >>>> + mutex_lock(&ns2->lock);
> >>>> +
> >>>> + rcu_read_lock();
> >>>> + input = rcu_dereference(ns2->input);
> >>>> + rcu_read_unlock();
> >>>> +
> >>>> + rcu_assign_pointer(ns2->input, NULL);
> >>>> + synchronize_rcu();
> >>>> +
> >>>> + ns2->init_step = 0;
> >>>> + do_free = !ns2->hdev && !ns2->cfg;
> >>>> + mutex_unlock(&ns2->lock);
> >>>> +
> >>>> + if (input)
> >>>> + input_unregister_device(input);
> >>>> +
> >>>> + if (do_free) {
> >>>> + list_del_init(&ns2->entry);
> >>>> + mutex_destroy(&ns2->lock);
> >>>> + kfree(ns2);
> >>>> + }
> >>>> +}
> >>>> +
> >>>> +static bool switch2_parse_stick_calibration(struct switch2_stick_calibration *calib,
> >>>> + const uint8_t *data)
> >>>> +{
> >>>> + static const uint8_t UNCALIBRATED[9] = {
> >>>> + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
> >>>> + };
> >>>> + if (memcmp(UNCALIBRATED, data, sizeof(UNCALIBRATED)) == 0)
> >>>> + return false;
> >>>> +
> >>>> + calib->x.neutral = data[0];
> >>>> + calib->x.neutral |= (data[1] & 0x0F) << 8;
> >>>> +
> >>>> + calib->y.neutral = data[1] >> 4;
> >>>> + calib->y.neutral |= data[2] << 4;
> >>>> +
> >>>> + calib->x.positive = data[3];
> >>>> + calib->x.positive |= (data[4] & 0x0F) << 8;
> >>>> +
> >>>> + calib->y.positive = data[4] >> 4;
> >>>> + calib->y.positive |= data[5] << 4;
> >>>> +
> >>>> + calib->x.negative = data[6];
> >>>> + calib->x.negative |= (data[7] & 0x0F) << 8;
> >>>> +
> >>>> + calib->y.negative = data[7] >> 4;
> >>>> + calib->y.negative |= data[8] << 4;
> >>>> +
> >>>> + return true;
> >>>> +}
> >>>> +
> >>>> +static void switch2_handle_flash_read(struct switch2_controller *ns2, uint8_t size,
> >>>> + uint32_t address, const uint8_t *data)
> >>>> +{
> >>>> + bool ok;
> >>>> +
> >>>> + switch (address) {
> >>>> + case NS2_FLASH_ADDR_SERIAL:
> >>>> + if (size != NS2_FLASH_SIZE_SERIAL)
> >>>> + return;
> >>>> + memcpy(ns2->serial, data, size);
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_SERIAL);
> >>>> + break;
> >>>> + case NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB:
> >>>> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
> >>>> + return;
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_PRIMARY_CALIB);
> >>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], data);
> >>>> + if (ok) {
> >>>> + hid_dbg(ns2->hdev, "Got factory primary stick calibration:\n");
> >>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> >>>> + ns2->stick_calib[0].x.negative,
> >>>> + ns2->stick_calib[0].x.neutral,
> >>>> + ns2->stick_calib[0].x.positive);
> >>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> >>>> + ns2->stick_calib[0].y.negative,
> >>>> + ns2->stick_calib[0].y.neutral,
> >>>> + ns2->stick_calib[0].y.positive);
> >>>> + } else {
> >>>> + hid_dbg(ns2->hdev, "Factory primary stick calibration not present\n");
> >>>> + }
> >>>> + break;
> >>>> + case NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB:
> >>>> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
> >>>> + return;
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
> >>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], data);
> >>>> + if (ok) {
> >>>> + hid_dbg(ns2->hdev, "Got factory secondary stick calibration:\n");
> >>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> >>>> + ns2->stick_calib[1].x.negative,
> >>>> + ns2->stick_calib[1].x.neutral,
> >>>> + ns2->stick_calib[1].x.positive);
> >>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> >>>> + ns2->stick_calib[1].y.negative,
> >>>> + ns2->stick_calib[1].y.neutral,
> >>>> + ns2->stick_calib[1].y.positive);
> >>>> + } else {
> >>>> + hid_dbg(ns2->hdev, "Factory secondary stick calibration not present\n");
> >>>> + }
> >>>> + break;
> >>>> + case NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB:
> >>>> + if (size != NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB)
> >>>> + return;
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
> >>>> + if (data[0] != 0xFF && data[1] != 0xFF) {
> >>>> + ns2->lt_zero = data[0];
> >>>> + ns2->rt_zero = data[1];
> >>>> +
> >>>> + hid_dbg(ns2->hdev, "Got factory trigger calibration:\n");
> >>>> + hid_dbg(ns2->hdev, "Left zero point: %i\n", ns2->lt_zero);
> >>>> + hid_dbg(ns2->hdev, "Right zero point: %i\n", ns2->rt_zero);
> >>>> + } else {
> >>>> + hid_dbg(ns2->hdev, "Factory trigger calibration not present\n");
> >>>> + }
> >>>> + break;
> >>>> + case NS2_FLASH_ADDR_USER_PRIMARY_CALIB:
> >>>> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
> >>>> + return;
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_PRIMARY_CALIB);
> >>>> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
> >>>> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], &data[2]);
> >>>> + if (ok) {
> >>>> + hid_dbg(ns2->hdev, "Got user primary stick calibration:\n");
> >>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> >>>> + ns2->stick_calib[0].x.negative,
> >>>> + ns2->stick_calib[0].x.neutral,
> >>>> + ns2->stick_calib[0].x.positive);
> >>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> >>>> + ns2->stick_calib[0].y.negative,
> >>>> + ns2->stick_calib[0].y.neutral,
> >>>> + ns2->stick_calib[0].y.positive);
> >>>> + } else {
> >>>> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
> >>>> + }
> >>>> + break;
> >>>> + case NS2_FLASH_ADDR_USER_SECONDARY_CALIB:
> >>>> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
> >>>> + return;
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
> >>>> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
> >>>> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], &data[2]);
> >>>> + if (ok) {
> >>>> + hid_dbg(ns2->hdev, "Got user secondary stick calibration:\n");
> >>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
> >>>> + ns2->stick_calib[1].x.negative,
> >>>> + ns2->stick_calib[1].x.neutral,
> >>>> + ns2->stick_calib[1].x.positive);
> >>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
> >>>> + ns2->stick_calib[1].y.negative,
> >>>> + ns2->stick_calib[1].y.neutral,
> >>>> + ns2->stick_calib[1].y.positive);
> >>>> + } else {
> >>>> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
> >>>> + }
> >>>> + break;
> >>>> + }
> >>>> +}
> >>>> +
> >>>> +static void switch2_report_buttons(struct input_dev *input, const uint8_t *bytes,
> >>>> + const struct switch2_ctlr_button_mapping button_mappings[])
> >>>> +{
> >>>> + const struct switch2_ctlr_button_mapping *button;
> >>>> +
> >>>> + for (button = button_mappings; button->code; button++)
> >>>> + input_report_key(input, button->code, bytes[button->byte] & button->bit);
> >>>> +}
> >>>> +
> >>>> +static void switch2_report_axis(struct input_dev *input, struct switch2_axis_calibration *calib,
> >>>> + int axis, bool invert, int value)
> >>>> +{
> >>>> + if (calib && calib->neutral && calib->negative && calib->positive) {
> >>>> + value -= calib->neutral;
> >>>> + value *= NS2_AXIS_MAX + 1;
> >>>> + if (value < 0)
> >>>> + value /= calib->negative;
> >>>> + else
> >>>> + value /= calib->positive;
> >>>> + } else {
> >>>> + value = (value - 2048) * 16;
> >>>> + }
> >>>> +
> >>>> + if (invert)
> >>>> + value = -value;
> >>>> + input_report_abs(input, axis,
> >>>> + clamp(value, NS2_AXIS_MIN, NS2_AXIS_MAX));
> >>>> +}
> >>>> +
> >>>> +static void switch2_report_stick(struct input_dev *input, struct switch2_stick_calibration *calib,
> >>>> + int x, bool invert_x, int y, bool invert_y, const uint8_t *data)
> >>>> +{
> >>>> + switch2_report_axis(input, &calib->x, x, invert_x, data[0] | ((data[1] & 0x0F) << 8));
> >>>> + switch2_report_axis(input, &calib->y, y, invert_y, (data[1] >> 4) | (data[2] << 4));
> >>>> +}
> >>>> +
> >>>> +static void switch2_report_trigger(struct input_dev *input, uint8_t zero, int abs, uint8_t data)
> >>>> +{
> >>>> + int value = (NS2_TRIGGER_RANGE + 1) * (data - zero) / (232 - zero);
> >>>> +
> >>>> + input_report_abs(input, abs, clamp(value, 0, NS2_TRIGGER_RANGE));
> >>>> +}
> >>>> +
> >>>> +static int switch2_event(struct hid_device *hdev, struct hid_report *report, uint8_t *raw_data,
> >>>> + int size)
> >>>> +{
> >>>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> >>>> + struct input_dev *input;
> >>>> +
> >>>> + if (report->type != HID_INPUT_REPORT)
> >>>> + return 0;
> >>>> +
> >>>> + if (size < 15)
> >>>> + return -EINVAL;
> >>>> +
> >>>> + guard(rcu)();
> >>>> + input = rcu_dereference(ns2->input);
> >>>> +
> >>>> + if (!input)
> >>>> + return 0;
> >>>> +
> >>>> + switch (report->id) {
> >>>> + case NS2_REPORT_UNIFIED:
> >>>> + /*
> >>>> + * TODO
> >>>> + * This won't be sent unless the report type gets changed via command
> >>>> + * 03-0A, but we should support it at some point regardless.
> >>>> + */
> >>>> + break;
> >>>> + case NS2_REPORT_JCL:
> >>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> >>>> + ABS_Y, true, &raw_data[6]);
> >>>> + switch2_report_buttons(input, &raw_data[3], ns2_left_joycon_button_mappings);
> >>>> + break;
> >>>> + case NS2_REPORT_JCR:
> >>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> >>>> + ABS_Y, true, &raw_data[6]);
> >>>> + switch2_report_buttons(input, &raw_data[3], ns2_right_joycon_button_mappings);
> >>>> + break;
> >>>> + case NS2_REPORT_GC:
> >>>> + input_report_abs(input, ABS_HAT0X,
> >>>> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
> >>>> + !!(raw_data[4] & NS2_BTNL_LEFT));
> >>>> + input_report_abs(input, ABS_HAT0Y,
> >>>> + !!(raw_data[4] & NS2_BTNL_DOWN) -
> >>>> + !!(raw_data[4] & NS2_BTNL_UP));
> >>>> + switch2_report_buttons(input, &raw_data[3], ns2_gccon_mappings);
> >>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> >>>> + ABS_Y, true, &raw_data[6]);
> >>>> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
> >>>> + ABS_RY, true, &raw_data[9]);
> >>>> + switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[13]);
> >>>> + switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[14]);
> >>>> + break;
> >>>> + case NS2_REPORT_PRO:
> >>>> + input_report_abs(input, ABS_HAT0X,
> >>>> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
> >>>> + !!(raw_data[4] & NS2_BTNL_LEFT));
> >>>> + input_report_abs(input, ABS_HAT0Y,
> >>>> + !!(raw_data[4] & NS2_BTNL_DOWN) -
> >>>> + !!(raw_data[4] & NS2_BTNL_UP));
> >>>> + switch2_report_buttons(input, &raw_data[3], ns2_procon_mappings);
> >>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
> >>>> + ABS_Y, true, &raw_data[6]);
> >>>> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
> >>>> + ABS_RY, true, &raw_data[9]);
> >>>> + break;
> >>>> + default:
> >>>> + return -EINVAL;
> >>>> + }
> >>>> +
> >>>> + input_sync(input);
> >>>> + return 0;
> >>>> +}
> >>>> +
> >>>> +static int switch2_features_enable(struct switch2_controller *ns2, int features)
> >>>> +{
> >>>> + __le32 feature_bits = __cpu_to_le32(features);
> >>>> +
> >>>> + if (!ns2->cfg)
> >>>> + return -ENOTCONN;
> >>>> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_ENABLE,
> >>>> + &feature_bits, sizeof(feature_bits),
> >>>> + ns2->cfg);
> >>>> +}
> >>>> +
> >>>> +static int switch2_read_flash(struct switch2_controller *ns2, uint32_t address,
> >>>> + uint8_t size)
> >>>> +{
> >>>> + uint8_t message[8] = { size, 0x7e };
> >>>> +
> >>>> + if (!ns2->cfg)
> >>>> + return -ENOTCONN;
> >>>> + *(__le32 *)&message[4] = __cpu_to_le32(address);
> >>>> + return ns2->cfg->send_command(NS2_CMD_FLASH, NS2_SUBCMD_FLASH_READ, message,
> >>>> + sizeof(message), ns2->cfg);
> >>>> +}
> >>>> +
> >>>> +static int switch2_set_player_id(struct switch2_controller *ns2, uint32_t player_id)
> >>>> +{
> >>>> + int i;
> >>>> + int player_led_pattern = player_id % JC_NUM_LED_PATTERNS;
> >>>> +
> >>>> + for (i = 0; i < JC_NUM_LEDS; i++)
> >>>> + ns2->leds[i].brightness = joycon_player_led_patterns[player_led_pattern][i];
> >>>> +
> >>>> + return switch2_set_leds(ns2);
> >>>> +}
> >>>> +
> >>>> +static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
> >>>> +{
> >>>> + __le32 format_id = __cpu_to_le32(fmt);
> >>>> +
> >>>> + if (!ns2->cfg)
> >>>> + return -ENOTCONN;
> >>>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
> >>>> + &format_id, sizeof(format_id),
> >>>> + ns2->cfg);
> >>>> +}
> >>>> +
> >>>> +static int switch2_init_controller(struct switch2_controller *ns2)
> >>>
> >>> This is now a recursive call while in v1 it wasn't. I think I preferred
> >>> the non-recursive version as there was one place where init_step
> >>> state was changed while now I am not sure where it happens (and whether
> >>> there is a code path where we end up in an infinite recursion)
> >>>
> >>> What is the advantage of the recursive version compared to the
> >>> non-recursive one?
> > >
> >>
> >> The old version incremented the step regardless of whether or not it
> >> could confirm it had happened. Since the confirmation is now handled
> >> with an external step, calling into switch2_init_step_done, the loop
> >> condition would become somewhat complicated.
> >> I replaced it with explicit tail calls since that make the
> >> control flow simplier, and it is always matched with a call to
> >> switch2_init_step_done to ensure that the state is always advanced. As
> >
> > From what I can tell switch2_init_step_done currently only advances
> > the state if the current state is the expected one. This seems fine,
> > but it also means that if the state is not the expected one, the
> > state is not advanced and the recursive call continues anyway (in the
> > NS2_INIT_READ_USER_SECONDARY_CALIB case, for example). I assume this
> > should never happen but if we end up in this case for some reason we
> > will recurse forever.
>
> That's correct, and the only way it would happen forever is if there's a
> bug. The same would be true in a loop version if it doesn't advance the
> state properly either, fwiw, which happened during development of this
> version. Regardless, I can reduce the chance of introducing such a bug
> by passing ns2->init_step instead of a constant, so I'll make that
> change in v4.
>
> >
> > The same case also potentially calls switch2_read_flash and it isn't
> > clear to me if this means that the initialisation is done (as there
> > is no switch2_init_step_done call and we are not in the FINISH state
> > either). There is also the possibility of switch2_read_flash calling
> > switch2_init_controller again, which one then has to check ... (note
> > that this is not the case here though)
>
> switch2_handle_flash_read will advance the state once it's verified that
> the read actually happened. If the step failed for whatever reason, this
> same codepath will retry the specific read, as the caller
> (switch2_receive_command) will always call into switch2_init_controller
> if setup isn't done. This is how the retry logic works.
Ah, so the call chain looks something like the below?
switch2_read_flash->
switch2_usb_send_cmd->
switch2_usb_message_in_work (?)->
switch2_receive_command->
switch2_handle_flash_read->
switch2_init_step_done
>
> >
> > To me it seems like it would be clearer to do a `ns2->init_step++` and
> > then `continue` to make the progress of the state more visible and to
> > do an explicit `break` when we are supposed to stop the initialisation.
> >
>
> The problem with the loop approach, in my opinion is due to the fact
> that the loop is the *exception*, not the rule. The loop idiom makes it
> look like a loop is expected. Further the ns2->init_step++ in the
> previous version means that the verification does not occur, so in the
> case of any sort of failure it'll plow ahead anyway instead of retrying.
> The point of this approach is to avoid that.
In my mind a while-loop like you mentioned it above would make the state
changes more obvious (since they could all be done in the loop body), while
still allowing for retries. Something like the below, perhaps (untested).
while (ns2->init_step < NS2_INIT_DONE) {
switch (ns2->init_step) {
...
case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
ns->init_step++
continue;
}
ret = switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
if (ret) {
// if it makes sense to retry here
continue;
}
ns->init_step++
break;
case ...
}
}
>
> >
> >> such, the number recursive calls is explicitly limited, and the fact
> >> that they're tail calls should mean that it doesn't increase the stack
> >> depth (unless there's something I don't know about how the kernel
> >> is compiled, which is possible in the wake of things like retpoline
> >> protections).
> >
> > I actually wasn't aware that the tail call optimisation means that no
> > new stack frame will be added, neat!
> >
> > I don't know how or if retpoline has an impact on this optimisation
> > either, I am afraid.
> >
> >
> >> I suppose I could replace it with a loop, but the condition would be
> >> the same so the only real difference would be a `while (ns2->init_step
> >> < NS2_INIT_DONE)` instead of the tail calls; the tail calls themselves
> >> would just become break statements. There would be no functional
> >> difference.
> >
> > hm, wouldn't the tail calls become "continue" statements (as the loop
> > version would just start a new iteration there)?
>
> Yes, it'd be (roughly) equivalent. It'd just read differently.
Yes, I agree.
>
> >
> > I agree that both versions can definitely be implemented in an equivalent
> > manner with no functional differences, but I do have my preference :)
> >
> > This is not my call to make anyways though, so let's wait for others to
> > chime in.
> >
> >
> >>
> >>>> +{
> >>>> + if (ns2->init_step == NS2_INIT_DONE)
> >>>> + return 0;
> >>>> +
> >>>> + if (!ns2->cfg)
> >>>> + return -ENOTCONN;
> >>>> +
> >>>> + switch (ns2->init_step) {
> >>>> + case NS2_INIT_READ_SERIAL:
> >>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_SERIAL,
> >>>> + NS2_FLASH_SIZE_SERIAL);
> >>>> + case NS2_INIT_GET_FIRMWARE_INFO:
> >>>> + return ns2->cfg->send_command(NS2_CMD_FW_INFO, NS2_SUBCMD_FW_INFO_GET,
> >>>> + NULL, 0, ns2->cfg);
> >>>> + case NS2_INIT_READ_FACTORY_PRIMARY_CALIB:
> >>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB,
> >>>> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
> >>>> + case NS2_INIT_READ_FACTORY_SECONDARY_CALIB:
> >>>> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
> >>>> + return switch2_init_controller(ns2);
> >>>> + }
> >>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB,
> >>>> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
> >>>> + case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
> >>>> + if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
> >>>> + return switch2_init_controller(ns2);
> >>>> + }
> >>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
> >>>> + NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
> >>>> + case NS2_INIT_READ_USER_PRIMARY_CALIB:
> >>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_PRIMARY_CALIB,
> >>>> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
> >>>> + case NS2_INIT_READ_USER_SECONDARY_CALIB:
> >>>> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
> >>>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
> >>>> + return switch2_init_controller(ns2);
> >>>> + }
> >>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_SECONDARY_CALIB,
> >>>> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
> >>>> + case NS2_INIT_SET_FEATURE_MASK:
> >>>> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_SET_MASK,
> >>>> + switch2_feature_mask, sizeof(switch2_feature_mask), ns2->cfg);
> >>>> + case NS2_INIT_ENABLE_FEATURES:
> >>>> + return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG);
> >>>> + case NS2_INIT_GRIP_BUTTONS:
> >>>> + if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
> >>>> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
> >>>> + return switch2_init_controller(ns2);
> >>>> + }
> >>>> + return ns2->cfg->send_command(NS2_CMD_GRIP, NS2_SUBCMD_GRIP_ENABLE_BUTTONS,
> >>>> + switch2_one_data, sizeof(switch2_one_data),
> >>>> + ns2->cfg);
> >>>> + case NS2_INIT_REPORT_FORMAT:
> >>>> + switch (ns2->ctlr_type) {
> >>>> + case NS2_CTLR_TYPE_JCL:
> >>>> + return switch2_set_report_format(ns2, NS2_REPORT_JCL);
> >>>> + case NS2_CTLR_TYPE_JCR:
> >>>> + return switch2_set_report_format(ns2, NS2_REPORT_JCR);
> >>>> + case NS2_CTLR_TYPE_PRO:
> >>>> + return switch2_set_report_format(ns2, NS2_REPORT_PRO);
> >>>> + case NS2_CTLR_TYPE_GC:
> >>>> + return switch2_set_report_format(ns2, NS2_REPORT_GC);
> >>>> + default:
> >>>> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
> >>>> + return switch2_init_controller(ns2);
> >>>> + }
> >>>> + case NS2_INIT_SET_PLAYER_LEDS:
> >>>> + return switch2_set_player_id(ns2, ns2->player_id);
> >>>> + case NS2_INIT_INPUT:
> >>>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_USB,
> >>>> + switch2_init_cmd_data, sizeof(switch2_init_cmd_data), ns2->cfg);
> >>>> + case NS2_INIT_FINISH:
> >>>> + if (ns2->hdev)
> >>>
> >>> If this is not set we skip the switch2_init_input call but don't error
> >>> out. Is this intentional (are we expecting ns2->hdev to be populated at
> >>> a later time and this step retried, perhaps)?
> >>
> >> This is intentional, yes. There are a handful of places this can
> >> get called, including the function that sets ns2->hdev in the first
> >> place (switch2_probe). It's to ensure the steps start as soon as
> >> possible (when the cfg pointer gets set) but it can't finish until the
> >> hdev gets set, which can be either before or after, depending on the
> >> ordering the interfaces get enumerated.
> >
> > I'm probably not correctly following the logic in this case, but if the
> > steps can only finish once ns2->hdev is set and we are `break;`-ing here
> > in case it's not, doesn't that mean we never retry (since there is no
> > recursive call after)?
>
> We retry in switch2_probe. It calls switch2_init_controller at the end
> of it, so we hit that step again, and now since ns2->hdev is necessarily
> filled it continues.
That means the retry is kicked-off from a different part of the code in
this case, got it!
>
> >
> >>
> >>>
> >>>> + return switch2_init_input(ns2);
> >>>> + break;
> >>>> + default:
> >>>> + WARN_ON_ONCE(1);
> >>>> + break;
> >>>> + }
> >>>> + return 0;
> >>>> +}
> >>>> +
> >>>> +int switch2_receive_command(struct switch2_controller *ns2,
> >>>> + const uint8_t *message, size_t length)
> >>>> +{
> >>>> + const struct switch2_cmd_header *header;
> >>>> + int ret = 0;
> >>>> +
> >>>> + if (length < 8)
> >>>> + return -EINVAL;
> >>>> +
> >>>> + print_hex_dump_debug("got cmd: ", DUMP_PREFIX_OFFSET, 16, 1, message, length, false);
> >>>> +
> >>>> + guard(mutex)(&ns2->lock);
> >>>> +
> >>>> + header = (const struct switch2_cmd_header *)message;
> >>>> + if (!(header->flags & NS2_FLAG_OK)) {
> >>>> + ret = -EIO;
> >>>> + goto exit;
> >>>> + }
> >>>> + message = &message[8];
> >>>> + switch (header->command) {
> >>>> + case NS2_CMD_FLASH:
> >>>> + if (header->subcommand == NS2_SUBCMD_FLASH_READ) {
> >>>> + uint8_t read_size;
> >>>> + uint32_t read_address;
> >>>> +
> >>>> + if (length < 16) {
> >>>> + ret = -EINVAL;
> >>>> + goto exit;
> >>>> + }
> >>>> + read_size = message[0];
> >>>> + read_address = __le32_to_cpu(*(__le32 *)&message[4]);
> >>>> + if (length < read_size + 16) {
> >>>> + ret = -EINVAL;
> >>>> + goto exit;
> >>>> + }
> >>>> + switch2_handle_flash_read(ns2, read_size, read_address, &message[8]);
> >>>> + }
> >>>> + break;
> >>>> + case NS2_CMD_INIT:
> >>>> + if (header->subcommand == NS2_SUBCMD_INIT_USB)
> >>>> + switch2_init_step_done(ns2, NS2_INIT_INPUT);
> >>>> + else if (header->subcommand == NS2_SUBCMD_INIT_SELECT_REPORT)
> >>>> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
> >>>> + break;
> >>>> + case NS2_CMD_GRIP:
> >>>> + if (header->subcommand == NS2_SUBCMD_GRIP_ENABLE_BUTTONS)
> >>>> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
> >>>> + break;
> >>>> + case NS2_CMD_LED:
> >>>> + if (header->subcommand == NS2_SUBCMD_LED_PATTERN)
> >>>> + switch2_init_step_done(ns2, NS2_INIT_SET_PLAYER_LEDS);
> >>>> + break;
> >>>> + case NS2_CMD_FEATSEL:
> >>>> + if (header->subcommand == NS2_SUBCMD_FEATSEL_SET_MASK)
> >>>> + switch2_init_step_done(ns2, NS2_INIT_SET_FEATURE_MASK);
> >>>> + else if (header->subcommand == NS2_SUBCMD_FEATSEL_ENABLE)
> >>>> + switch2_init_step_done(ns2, NS2_INIT_ENABLE_FEATURES);
> >>>> + break;
> >>>> + case NS2_CMD_FW_INFO:
> >>>> + if (header->subcommand == NS2_SUBCMD_FW_INFO_GET) {
> >>>> + if (length < sizeof(ns2->version)) {
> >>>> + ret = -EINVAL;
> >>>> + goto exit;
> >>>> + }
> >>>> + memcpy(&ns2->version, message, sizeof(ns2->version));
> >>>> + ns2->ctlr_type = ns2->version.ctlr_type;
> >>>> + switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
> >>>> + }
> >>>> + break;
> >>>> + default:
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> +exit:
> >>>> + if (ns2->init_step < NS2_INIT_DONE)
> >>>> + switch2_init_controller(ns2);
> >>>> +
> >>>> + return ret;
> >>>> +}
> >>>> +EXPORT_SYMBOL_GPL(switch2_receive_command);
> >>>> +
> >>>> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg)
> >>>> +{
> >>>> + struct switch2_controller *ns2 = switch2_get_controller(phys);
> >>>> +
> >>>> + if (IS_ERR(ns2))
> >>>> + return PTR_ERR(ns2);
> >>>> +
> >>>> + cfg->parent = ns2;
> >>>> +
> >>>> + guard(mutex)(&ns2->lock);
> >>>> + WARN_ON(ns2->cfg);
> >>>> + ns2->cfg = cfg;
> >>>> +
> >>>> + if (ns2->hdev)
> >>>> + return switch2_init_controller(ns2);
> >>>> + return 0;
> >>>> +}
> >>>> +EXPORT_SYMBOL_GPL(switch2_controller_attach_cfg);
> >>>> +
> >>>> +void switch2_controller_detach_cfg(struct switch2_controller *ns2)
> >>>> +{
> >>>> + mutex_lock(&ns2->lock);
> >>>> + WARN_ON(ns2 != ns2->cfg->parent);
> >>>> + ns2->cfg = NULL;
> >>>> + mutex_unlock(&ns2->lock);
> >>>> + switch2_controller_put(ns2);
> >>>> +}
> >>>> +EXPORT_SYMBOL_GPL(switch2_controller_detach_cfg);
> >>>> +
> >>>> +static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
> >>>> +{
> >>>> + struct switch2_controller *ns2;
> >>>> + struct usb_device *udev;
> >>>> + char phys[64];
> >>>> + int ret;
> >>>> +
> >>>> + if (!hid_is_usb(hdev))
> >>>> + return -ENODEV;
> >>>> +
> >>>> + udev = hid_to_usb_dev(hdev);
> >>>> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
> >>>> + return -EINVAL;
> >>>> +
> >>>> + ret = hid_parse(hdev);
> >>>> + if (ret) {
> >>>> + hid_err(hdev, "parse failed %d\n", ret);
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> >>>> + if (ret) {
> >>>> + hid_err(hdev, "hw_start failed %d\n", ret);
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + ret = hid_hw_open(hdev);
> >>>> + if (ret) {
> >>>> + hid_err(hdev, "hw_open failed %d\n", ret);
> >>>> + goto err_stop;
> >>>> + }
> >>>
> >>> For the Switch 1 controllers we are calling hid_device_io_start after
> >>> hid_hw_open. Is this not necessary in this case?
> >>
> >> Since we don't do any HID I/O during probe it's not needed; the
> >> startup configuration is on the cfg interface instead.
> >
> > Ah, I can see that for the Switch 1 controllers we are sending USB
> > HID packets during probe, while we don't do this for the Switch 2
> > controllers. That explains why this call is not needed here.
> >
> >>
> >>>
> >>>> +
> >>>> + ns2 = switch2_get_controller(phys);
> >>>> + if (!ns2) {
> >>>
> >>> switch2_get_controller returns an err pointer in case of ENOMEM, not
> >>> NULL so I think this check has to be changed.
> >>
> >> Fixed locally, thanks. I'll send that out with v4 (this was actually
> >> v3 but I thought it was v2, oops).
> >>
> >>>
> >>>> + ret = -ENOMEM;
> >>>> + goto err_close;
> >>>> + }
> >>>> +
> >>>> + guard(mutex)(&ns2->lock);
> >>>> + WARN_ON(ns2->hdev);
> >>>> + ns2->hdev = hdev;
> >>>> + switch (hdev->product | (hdev->vendor << 16)) {
> >>>> + default:
> >>>> + strscpy(ns2->name, hdev->name, sizeof(ns2->name));
> >>>> + break;
> >>>> + /* Some controllers have slightly wrong names so we override them */
> >>>> + case USB_DEVICE_ID_NINTENDO_NS2_JOYCONR | (USB_VENDOR_ID_NINTENDO << 16):
> >>>> + /* Missing the "2" in the name */
> >>>> + strscpy(ns2->name, "Nintendo Joy-Con 2 (R)", sizeof(ns2->name));
> >>>> + break;
> >>>> + case USB_DEVICE_ID_NINTENDO_NS2_GCCON | (USB_VENDOR_ID_NINTENDO << 16):
> >>>> + /* Has "Nintendo" in the name twice */
> >>>> + strscpy(ns2->name, "Nintendo GameCube Controller", sizeof(ns2->name));
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> + ns2->player_id = U32_MAX;
> >>>> + ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
> >>>> + if (ret < 0)
> >>>> + hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
> >>>> + else
> >>>> + ns2->player_id = ret;
> >>>> +
> >>>> + switch2_leds_create(ns2);
> >>>> +
> >>>> + hid_set_drvdata(hdev, ns2);
> >>>> +
> >>>> + if (ns2->cfg)
> >>>> + return switch2_init_controller(ns2);
> >>>> +
> >>>> + return 0;
> >>>> +
> >>>> +err_close:
> >>>> + hid_hw_close(hdev);
> >>>> +err_stop:
> >>>> + hid_hw_stop(hdev);
> >>>> +
> >>>> + return ret;
> >>>> +}
> >>>> +
> >>>> +static void switch2_remove(struct hid_device *hdev)
> >>>> +{
> >>>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> >>>> +
> >>>> + hid_hw_close(hdev);
> >>>> + mutex_lock(&ns2->lock);
> >>>> + WARN_ON(ns2->hdev != hdev);
> >>>> + ns2->hdev = NULL;
> >>>> + mutex_unlock(&ns2->lock);
> >>>> + ida_free(&nintendo_player_id_allocator, ns2->player_id);
> >>>> + switch2_controller_put(ns2);
> >>>> + hid_hw_stop(hdev);
> >>>> +}
> >>>> +
> >>>> static const struct hid_device_id nintendo_hid_devices[] = {
> >>>> + /* Switch devices */
> >>>> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> USB_DEVICE_ID_NINTENDO_PROCON) },
> >>>> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> @@ -2813,10 +3935,69 @@ static const struct hid_device_id nintendo_hid_devices[] = {
> >>>> USB_DEVICE_ID_NINTENDO_GENCON) },
> >>>> { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> USB_DEVICE_ID_NINTENDO_N64CON) },
> >>>> + /* Switch 2 devices */
> >>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
> >>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
> >>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> + USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
> >>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
> >>>> + USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
> >>>> { }
> >>>> };
> >>>> MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
> >>>>
> >>>> +static bool nintendo_is_switch2(struct hid_device *hdev)
> >>>> +{
> >>>> + return hdev->vendor == USB_VENDOR_ID_NINTENDO &&
> >>>> + hdev->product >= USB_DEVICE_ID_NINTENDO_NS2_JOYCONR;
> >>>> +}
> >>>> +
> >>>> +static void nintendo_hid_remove(struct hid_device *hdev)
> >>>> +{
> >>>> + if (nintendo_is_switch2(hdev))
> >>>> + switch2_remove(hdev);
> >>>> + else
> >>>> + joycon_remove(hdev);
> >>>> +}
> >>>> +
> >>>> +static int nintendo_hid_event(struct hid_device *hdev,
> >>>> + struct hid_report *report, u8 *raw_data, int size)
> >>>> +{
> >>>> + if (nintendo_is_switch2(hdev))
> >>>> + return switch2_event(hdev, report, raw_data, size);
> >>>> + else
> >>>> + return joycon_event(hdev, report, raw_data, size);
> >>>> +}
> >>>> +
> >>>> +static int nintendo_hid_probe(struct hid_device *hdev,
> >>>> + const struct hid_device_id *id)
> >>>> +{
> >>>> + if (nintendo_is_switch2(hdev))
> >>>> + return switch2_probe(hdev, id);
> >>>> + else
> >>>> + return joycon_probe(hdev, id);
> >>>> +}
> >>>> +
> >>>> +#ifdef CONFIG_PM
> >>>> +static int nintendo_hid_resume(struct hid_device *hdev)
> >>>> +{
> >>>> + if (nintendo_is_switch2(hdev))
> >>>> + return 0;
> >>>> + else
> >>>> + return joycon_resume(hdev);
> >>>> +}
> >>>> +
> >>>> +static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
> >>>> +{
> >>>> + if (nintendo_is_switch2(hdev))
> >>>> + return 0;
> >>>> + else
> >>>> + return joycon_suspend(hdev, message);
> >>>> +}
> >>>> +#endif
> >>>> +
> >>>> static struct hid_driver nintendo_hid_driver = {
> >>>> .name = "nintendo",
> >>>> .id_table = nintendo_hid_devices,
> >>>> @@ -2844,4 +4025,5 @@ MODULE_LICENSE("GPL");
> >>>> MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
> >>>> MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
> >>>> MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
> >>>> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
> >>>> MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
> >>>> diff --git a/drivers/hid/hid-nintendo.h b/drivers/hid/hid-nintendo.h
> >>>> new file mode 100644
> >>>> index 0000000000000..7aff22f302661
> >>>> --- /dev/null
> >>>> +++ b/drivers/hid/hid-nintendo.h
> >>>> @@ -0,0 +1,72 @@
> >>>> +/* SPDX-License-Identifier: GPL-2.0+ */
> >>>> +/*
> >>>> + * HID driver for Nintendo Switch 2 controllers
> >>>> + *
> >>>> + * Copyright (c) 2025 Valve Software
> >>>> + *
> >>>> + * This driver is based on the following work:
> >>>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
> >>>> + * https://github.com/ndeadly/switch2_controller_research
> >>>> + */
> >>>> +
> >>>> +#ifndef __HID_NINTENDO_H
> >>>> +#define __HID_NINTENDO_H
> >>>> +
> >>>> +#include <linux/bits.h>
> >>>> +
> >>>> +#define NS2_FLAG_OK BIT(0)
> >>>> +#define NS2_FLAG_NACK BIT(2)
> >>>> +
> >>>> +enum switch2_cmd {
> >>>> + NS2_CMD_NFC = 0x01,
> >>>> + NS2_CMD_FLASH = 0x02,
> >>>> + NS2_CMD_INIT = 0x03,
> >>>> + NS2_CMD_GRIP = 0x08,
> >>>> + NS2_CMD_LED = 0x09,
> >>>> + NS2_CMD_VIBRATE = 0x0a,
> >>>> + NS2_CMD_BATTERY = 0x0b,
> >>>> + NS2_CMD_FEATSEL = 0x0c,
> >>>> + NS2_CMD_FW_UPD = 0x0d,
> >>>> + NS2_CMD_FW_INFO = 0x10,
> >>>> + NS2_CMD_BT_PAIR = 0x15,
> >>>> +};
> >>>> +
> >>>> +enum switch2_direction {
> >>>> + NS2_DIR_IN = 0x00,
> >>>> + NS2_DIR_OUT = 0x90,
> >>>> +};
> >>>> +
> >>>> +enum switch2_transport {
> >>>> + NS2_TRANS_USB = 0x00,
> >>>> + NS2_TRANS_BT = 0x01,
> >>>> +};
> >>>> +
> >>>> +struct switch2_cmd_header {
> >>>> + uint8_t command;
> >>>> + uint8_t flags;
> >>>> + uint8_t transport;
> >>>> + uint8_t subcommand;
> >>>> + uint8_t unk1;
> >>>> + uint8_t length;
> >>>> + uint16_t unk2;
> >>>> +};
> >>>> +static_assert(sizeof(struct switch2_cmd_header) == 8);
> >>>> +
> >>>> +struct device;
> >>>> +struct switch2_controller;
> >>>> +struct switch2_cfg_intf {
> >>>> + struct switch2_controller *parent;
> >>>> + struct device *dev;
> >>>> +
> >>>> + int (*send_command)(enum switch2_cmd command, uint8_t subcommand,
> >>>> + const void *message, size_t length,
> >>>> + struct switch2_cfg_intf *intf);
> >>>> +};
> >>>> +
> >>>> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg);
> >>>> +void switch2_controller_detach_cfg(struct switch2_controller *controller);
> >>>> +
> >>>> +int switch2_receive_command(struct switch2_controller *controller,
> >>>> + const uint8_t *message, size_t length);
> >>>> +
> >>>> +#endif
> >>>> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
> >>>> index 7755e5b454d2c..868262c6ccd9a 100644
> >>>> --- a/drivers/input/joystick/Kconfig
> >>>> +++ b/drivers/input/joystick/Kconfig
> >>>> @@ -422,4 +422,15 @@ config JOYSTICK_SEESAW
> >>>> To compile this driver as a module, choose M here: the module will be
> >>>> called adafruit-seesaw.
> >>>>
> >>>> +config JOYSTICK_NINTENDO_SWITCH2_USB
> >>>> + tristate "Wired Nintendo Switch 2 controller support"
> >>>> + depends on HID_NINTENDO
> >>>> + depends on USB
> >>>> + help
> >>>> + Say Y here if you want to enable support for wired Nintendo Switch 2
> >>>> + controllers.
> >>>> +
> >>>> + To compile this driver as a module, choose M here: the
> >>>> + module will be called nintendo-switch2-usb.
> >>>> +
> >>>> endif
> >>>> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
> >>>> index 9976f596a9208..8f92900ae8856 100644
> >>>> --- a/drivers/input/joystick/Makefile
> >>>> +++ b/drivers/input/joystick/Makefile
> >>>> @@ -34,6 +34,7 @@ obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
> >>>> obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o
> >>>> obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o
> >>>> obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o
> >>>> +obj-$(CONFIG_JOYSTICK_NINTENDO_SWITCH2_USB) += nintendo-switch2-usb.o
> >>>> obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o
> >>>> obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
> >>>> obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
> >>>> diff --git a/drivers/input/joystick/nintendo-switch2-usb.c b/drivers/input/joystick/nintendo-switch2-usb.c
> >>>> new file mode 100644
> >>>> index 0000000000000..ebd89d852e21a
> >>>> --- /dev/null
> >>>> +++ b/drivers/input/joystick/nintendo-switch2-usb.c
> >>>> @@ -0,0 +1,353 @@
> >>>> +// SPDX-License-Identifier: GPL-2.0+
> >>>> +/*
> >>>> + * USB driver for Nintendo Switch 2 controllers configuration interface
> >>>> + *
> >>>> + * Copyright (c) 2025 Valve Software
> >>>> + *
> >>>> + * This driver is based on the following work:
> >>>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
> >>>> + * https://github.com/ndeadly/switch2_controller_research
> >>>> + */
> >>>> +
> >>>> +#include "../../hid/hid-ids.h"
> >>>> +#include "../../hid/hid-nintendo.h"
> >>>> +#include <linux/module.h>
> >>>> +#include <linux/usb/input.h>
> >>>> +
> >>>> +#define NS2_BULK_SIZE 64
> >>>> +#define NS2_IN_URBS 2
> >>>> +#define NS2_OUT_URBS 4
> >>>> +
> >>>> +static struct usb_driver switch2_usb;
> >>>> +
> >>>> +struct switch2_urb {
> >>>> + struct urb *urb;
> >>>> + uint8_t *data;
> >>>> + bool active;
> >>>> +};
> >>>> +
> >>>> +struct switch2_usb {
> >>>> + struct switch2_cfg_intf cfg;
> >>>> + struct usb_device *udev;
> >>>> +
> >>>> + struct switch2_urb bulk_in[NS2_IN_URBS];
> >>>> + struct usb_anchor bulk_in_anchor;
> >>>> + spinlock_t bulk_in_lock;
> >>>> +
> >>>> + struct switch2_urb bulk_out[NS2_OUT_URBS];
> >>>> + struct usb_anchor bulk_out_anchor;
> >>>> + spinlock_t bulk_out_lock;
> >>>> +
> >>>> + int message_in;
> >>>> + struct work_struct message_in_work;
> >>>> +};
> >>>> +
> >>>> +static void switch2_bulk_in(struct urb *urb)
> >>>> +{
> >>>> + struct switch2_usb *ns2_usb = urb->context;
> >>>> + int i;
> >>>> + bool schedule = false;
> >>>> + unsigned long flags;
> >>>> +
> >>>> + switch (urb->status) {
> >>>> + case 0:
> >>>> + schedule = true;
> >>>> + break;
> >>>> + case -ECONNRESET:
> >>>> + case -ENOENT:
> >>>> + case -ESHUTDOWN:
> >>>> + dev_dbg(&ns2_usb->udev->dev, "shutting down input urb: %d\n", urb->status);
> >>>> + return;
> >>>> + default:
> >>>> + dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
> >>>> + break;
> >>>> + }
> >>>> +
> >>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> >>>> + for (i = 0; i < NS2_IN_URBS; i++) {
> >>>> + int err;
> >>>> + struct switch2_urb *ns2_urb;
> >>>> +
> >>>> + if (ns2_usb->bulk_in[i].urb == urb) {
> >>>> + ns2_usb->message_in = i;
> >>>> + continue;
> >>>> + }
> >>>> +
> >>>> + if (ns2_usb->bulk_in[i].active)
> >>>> + continue;
> >>>> +
> >>>> + ns2_urb = &ns2_usb->bulk_in[i];
> >>>> + usb_anchor_urb(ns2_urb->urb, &ns2_usb->bulk_in_anchor);
> >>>> + err = usb_submit_urb(ns2_urb->urb, GFP_ATOMIC);
> >>>> + if (err) {
> >>>> + usb_unanchor_urb(ns2_urb->urb);
> >>>> + dev_dbg(&ns2_usb->udev->dev, "failed to queue input urb: %d\n", err);
> >>>> + } else {
> >>>> + ns2_urb->active = true;
> >>>> + }
> >>>> + }
> >>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> >>>> +
> >>>> + if (schedule)
> >>>> + schedule_work(&ns2_usb->message_in_work);
> >>>> +}
> >>>> +
> >>>> +static void switch2_bulk_out(struct urb *urb)
> >>>> +{
> >>>> + struct switch2_usb *ns2_usb = urb->context;
> >>>> + int i;
> >>>> +
> >>>> + guard(spinlock_irqsave)(&ns2_usb->bulk_out_lock);
> >>>> +
> >>>> + switch (urb->status) {
> >>>> + case 0:
> >>>> + break;
> >>>> + case -ECONNRESET:
> >>>> + case -ENOENT:
> >>>> + case -ESHUTDOWN:
> >>>> + dev_dbg(&ns2_usb->udev->dev, "shutting down output urb: %d\n", urb->status);
> >>>> + return;
> >>>> + default:
> >>>> + dev_dbg(&ns2_usb->udev->dev, "unknown output urb status: %d\n", urb->status);
> >>>> + return;
> >>>> + }
> >>>> +
> >>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
> >>>> + if (ns2_usb->bulk_out[i].urb != urb)
> >>>> + continue;
> >>>> +
> >>>> + ns2_usb->bulk_out[i].active = false;
> >>>> + break;
> >>>> + }
> >>>> +}
> >>>> +
> >>>> +static int switch2_usb_send_cmd(enum switch2_cmd command, uint8_t subcommand,
> >>>> + const void *message, size_t size, struct switch2_cfg_intf *cfg)
> >>>> +{
> >>>> + struct switch2_usb *ns2_usb = (struct switch2_usb *)cfg;
> >>>> + struct switch2_urb *urb = NULL;
> >>>> + int i;
> >>>> + int ret;
> >>>> + unsigned long flags;
> >>>> +
> >>>> + struct switch2_cmd_header header = {
> >>>> + command, NS2_DIR_OUT | NS2_FLAG_OK, NS2_TRANS_USB, subcommand, 0, size
> >>>> + };
> >>>> +
> >>>> + if (WARN_ON(size > 56))
> >>>> + return -EINVAL;
> >>>> +
> >>>> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> >>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
> >>>> + if (ns2_usb->bulk_out[i].active)
> >>>> + continue;
> >>>> +
> >>>> + urb = &ns2_usb->bulk_out[i];
> >>>> + urb->active = true;
> >>>> + break;
> >>>> + }
> >>>> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> >>>> +
> >>>> + if (!urb) {
> >>>> + dev_warn(&ns2_usb->udev->dev, "output queue full, dropping message\n");
> >>>> + return -ENOBUFS;
> >>>> + }
> >>>> +
> >>>> + memcpy(urb->data, &header, sizeof(header));
> >>>> + if (message && size)
> >>>> + memcpy(&urb->data[8], message, size);
> >>>> + urb->urb->transfer_buffer_length = size + sizeof(header);
> >>>> +
> >>>> + print_hex_dump_debug("sending cmd: ", DUMP_PREFIX_OFFSET, 16, 1, urb->data,
> >>>> + size + sizeof(header), false);
> >>>> +
> >>>> + usb_anchor_urb(urb->urb, &ns2_usb->bulk_out_anchor);
> >>>> + ret = usb_submit_urb(urb->urb, GFP_ATOMIC);
> >>>> + if (ret) {
> >>>> + if (ret != -ENODEV)
> >>>> + dev_warn(&ns2_usb->udev->dev, "failed to submit output urb: %i", ret);
> >>>> + urb->active = false;
> >>>> + usb_unanchor_urb(urb->urb);
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + return 0;
> >>>> +}
> >>>> +
> >>>> +static void switch2_usb_message_in_work(struct work_struct *work)
> >>>> +{
> >>>> + struct switch2_usb *ns2_usb = container_of(work, struct switch2_usb, message_in_work);
> >>>> + struct switch2_urb *urb;
> >>>> + int err;
> >>>> + unsigned long flags;
> >>>> +
> >>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> >>>> + urb = &ns2_usb->bulk_in[ns2_usb->message_in];
> >>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> >>>> +
> >>>> + err = switch2_receive_command(ns2_usb->cfg.parent, urb->urb->transfer_buffer,
> >>>> + urb->urb->actual_length);
> >>>> + if (err)
> >>>> + dev_dbg(&ns2_usb->udev->dev, "receive command failed: %d\n", err);
> >>>> +
> >>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> >>>> + urb->active = false;
> >>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> >>>> +}
> >>>> +
> >>>> +static int switch2_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
> >>>> +{
> >>>> + struct switch2_usb *ns2_usb;
> >>>> + struct usb_device *udev;
> >>>> + struct usb_endpoint_descriptor *bulk_in, *bulk_out;
> >>>> + char phys[64];
> >>>> + int ret;
> >>>> + int i;
> >>>> +
> >>>> + udev = interface_to_usbdev(intf);
> >>>> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
> >>>> + return -EINVAL;
> >>>> +
> >>>> + ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in, &bulk_out, NULL, NULL);
> >>>> + if (ret) {
> >>>> + dev_err(&intf->dev, "failed to find bulk EPs\n");
> >>>> + return ret;
> >>>> + }
> >>>> +
> >>>> + ns2_usb = devm_kzalloc(&intf->dev, sizeof(*ns2_usb), GFP_KERNEL);
> >>>> + if (!ns2_usb)
> >>>> + return -ENOMEM;
> >>>> +
> >>>> + ns2_usb->udev = udev;
> >>>> + for (i = 0; i < NS2_IN_URBS; i++) {
> >>>> + ns2_usb->bulk_in[i].urb = usb_alloc_urb(0, GFP_KERNEL);
> >>>> + if (!ns2_usb->bulk_in[i].urb) {
> >>>> + ret = -ENOMEM;
> >>>> + goto err_free_in;
> >>>> + }
> >>>> +
> >>>> + ns2_usb->bulk_in[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
> >>>> + &ns2_usb->bulk_in[i].urb->transfer_dma);
> >>>> + if (!ns2_usb->bulk_in[i].data) {
> >>>> + ret = -ENOMEM;
> >>>> + goto err_free_in;
> >>>> + }
> >>>> +
> >>>> + usb_fill_bulk_urb(ns2_usb->bulk_in[i].urb, udev,
> >>>> + usb_rcvbulkpipe(udev, bulk_in->bEndpointAddress),
> >>>> + ns2_usb->bulk_in[i].data, NS2_BULK_SIZE, switch2_bulk_in, ns2_usb);
> >>>> + ns2_usb->bulk_in[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> >>>> + }
> >>>> +
> >>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
> >>>> + ns2_usb->bulk_out[i].urb = usb_alloc_urb(0, GFP_KERNEL);
> >>>> + if (!ns2_usb->bulk_out[i].urb) {
> >>>> + ret = -ENOMEM;
> >>>> + goto err_free_out;
> >>>> + }
> >>>> +
> >>>> + ns2_usb->bulk_out[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
> >>>> + &ns2_usb->bulk_out[i].urb->transfer_dma);
> >>>> + if (!ns2_usb->bulk_out[i].data) {
> >>>> + ret = -ENOMEM;
> >>>> + goto err_free_out;
> >>>> + }
> >>>> +
> >>>> + usb_fill_bulk_urb(ns2_usb->bulk_out[i].urb, udev,
> >>>> + usb_sndbulkpipe(udev, bulk_out->bEndpointAddress),
> >>>> + ns2_usb->bulk_out[i].data, NS2_BULK_SIZE, switch2_bulk_out, ns2_usb);
> >>>> + ns2_usb->bulk_out[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> >>>> + }
> >>>> +
> >>>> + ns2_usb->bulk_in[0].active = true;
> >>>> + ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
> >>>> + if (ret < 0)
> >>>> + goto err_free_out;
> >>>> +
> >>>> + init_usb_anchor(&ns2_usb->bulk_out_anchor);
> >>>> + spin_lock_init(&ns2_usb->bulk_out_lock);
> >>>> + init_usb_anchor(&ns2_usb->bulk_in_anchor);
> >>>> + spin_lock_init(&ns2_usb->bulk_in_lock);
> >>>> + INIT_WORK(&ns2_usb->message_in_work, switch2_usb_message_in_work);
> >>>> +
> >>>> + usb_set_intfdata(intf, ns2_usb);
> >>>> +
> >>>> + ns2_usb->cfg.dev = &ns2_usb->udev->dev;
> >>>> + ns2_usb->cfg.send_command = switch2_usb_send_cmd;
> >>>> +
> >>>> + ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
> >>>> + if (ret < 0)
> >>>> + goto err_kill_urb;
> >>>> +
> >>>> + return 0;
> >>>> +
> >>>> +err_kill_urb:
> >>>> + usb_kill_urb(ns2_usb->bulk_in[0].urb);
> >>>> +err_free_out:
> >>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
> >>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
> >>>> + ns2_usb->bulk_out[i].urb->transfer_dma);
> >>>> + usb_free_urb(ns2_usb->bulk_out[i].urb);
> >>>> + }
> >>>> +err_free_in:
> >>>> + for (i = 0; i < NS2_IN_URBS; i++) {
> >>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
> >>>> + ns2_usb->bulk_in[i].urb->transfer_dma);
> >>>> + usb_free_urb(ns2_usb->bulk_in[i].urb);
> >>>> + }
> >>>> + devm_kfree(&intf->dev, ns2_usb);
> >>>> +
> >>>> + return ret;
> >>>> +}
> >>>> +
> >>>> +static void switch2_usb_disconnect(struct usb_interface *intf)
> >>>> +{
> >>>> + struct switch2_usb *ns2_usb = usb_get_intfdata(intf);
> >>>> + unsigned long flags;
> >>>> + int i;
> >>>> +
> >>>> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> >>>> + usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
> >>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
> >>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
> >>>> + ns2_usb->bulk_out[i].urb->transfer_dma);
> >>>> + usb_free_urb(ns2_usb->bulk_out[i].urb);
> >>>> + }
> >>>> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> >>>> +
> >>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> >>>> + usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
> >>>> + cancel_work_sync(&ns2_usb->message_in_work);
> >>>> + for (i = 0; i < NS2_IN_URBS; i++) {
> >>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
> >>>> + ns2_usb->bulk_in[i].urb->transfer_dma);
> >>>> + usb_free_urb(ns2_usb->bulk_in[i].urb);
> >>>> + }
> >>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> >>>> +
> >>>> + switch2_controller_detach_cfg(ns2_usb->cfg.parent);
> >>>
> >>> As we have allocated ns2_usb with devm_kzalloc, don't we need to free it
> >>> with devm_kfree again?
> >>
> >> Devres will automatically free it when the device is freed. As this is
> >> the callback for the device going away, it's freed directly after this
> >> function exits. That's why I'm using devm_kzalloc instead of kzalloc.
> >
> > Makes sense!
> >
> >
> > Thanks for helping me to understand the code better!
> >
> > Cheers,
> > Silvan
> >
> >>
> >>>
> >>> Cheers,
> >>> Silvan
> >>>
> >>>> +}
> >>>> +
> >>>> +#define SWITCH2_CONTROLLER(vend, prod) \
> >>>> + USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, 0, 0)
> >>>> +
> >>>> +static const struct usb_device_id switch2_usb_devices[] = {
> >>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
> >>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
> >>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
> >>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
> >>>> + { }
> >>>> +};
> >>>> +MODULE_DEVICE_TABLE(usb, switch2_usb_devices);
> >>>> +
> >>>> +static struct usb_driver switch2_usb = {
> >>>> + .name = "switch2",
> >>>> + .id_table = switch2_usb_devices,
> >>>> + .probe = switch2_usb_probe,
> >>>> + .disconnect = switch2_usb_disconnect,
> >>>> +};
> >>>> +module_usb_driver(switch2_usb);
> >>>> +
> >>>> +MODULE_LICENSE("GPL");
> >>>> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
> >>>> +MODULE_DESCRIPTION("Driver for Nintendo Switch 2 Controllers");
> >>>
> >>>
> >>
> >> Vicki
> >
> >
>
> Unless anyone else chimes in with more suggestions I'll probably submit
> v4 sometime next week with these two changes.
Sounds good to me, thanks!
Cheers,
Silvan
>
> Vicki
^ permalink raw reply
* [PATCH] HID: sony: fix incorrect force-feedback check in sony_suspend()
From: Rosalie Wanders @ 2026-04-10 19:53 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit fixes the incorrect force-feedback check in sony_suspend(),
without this the check will always be true due to checking a constant
define that is never 0.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 83e82a0a3327..9cfea6f40ec2 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -2456,11 +2456,10 @@ static void sony_remove(struct hid_device *hdev)
static int sony_suspend(struct hid_device *hdev, pm_message_t message)
{
#ifdef CONFIG_SONY_FF
+ struct sony_sc *sc = hid_get_drvdata(hdev);
/* On suspend stop any running force-feedback events */
- if (SONY_FF_SUPPORT) {
- struct sony_sc *sc = hid_get_drvdata(hdev);
-
+ if (sc->quirks & SONY_FF_SUPPORT) {
sc->left = sc->right = 0;
sony_send_output_report(sc);
}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Vicki Pfau @ 2026-04-10 22:30 UTC (permalink / raw)
To: Silvan Jegen
Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <3GLFHF2SBGI6U.3O3D6XCXP4074@homearch.localdomain>
On 4/10/26 12:44, Silvan Jegen wrote:
> Vicki Pfau <vi@endrift.com> wrote:
>> Replies inline
>>
>> On 4/8/26 12:51, Silvan Jegen wrote:
>>> Heyhey!
>>>
>>> Vicki Pfau <vi@endrift.com> wrote:
>>>> Hi,
>>>>
>>>> Replies inline
>>>>
>>>> On 4/2/26 12:09 PM, Silvan Jegen wrote:
>>>>> Hi
>>>>>
>>>>> Thanks for the patch!
>>>>>
>>>>> Just some comments and questions inline below.
>>>>>
>>>>> Vicki Pfau <vi@endrift.com> wrote:
>>>>>> This adds a new driver for the Switch 2 controllers. The Switch 2 uses an
>>>>>> unusual split-interface design such that input and rumble occur on the main
>>>>>> HID interface, but all other communication occurs over a "configuration"
>>>>>> interface. This is the case on both USB and Bluetooth, so this new driver
>>>>>> uses a split-driver design with the HID interface being the "main" driver
>>>>>> and the configuration interface is a secondary driver that looks up to the
>>>>>> HID interface, sharing resources on a common struct.
>>>>>>
>>>>>> Due to using a non-standard pairing interface as well as Bluetooth
>>>>>> communications being extremely limited in the kernel, a custom interface
>>>>>> between userspace and the kernel will need to be design, along with bringup
>
> I missed this one the first time around but
>
> s/design/designed/
>
Fixed
>>>>>> in BlueZ. That is beyond the scope of this initial patch, which only
>>>>>> contains the generic HID and USB configuration interface drivers.
>>>>>>
>>>>>> This initial work supports general input for the Joy-Con 2, Pro Controller
>>>>>> 2, and GameCube NSO controllers. IMU, rumble and battery support is not yet
>>>>>> present.
>>>>>>
>>>>>> Signed-off-by: Vicki Pfau <vi@endrift.com>
>>>>>> ---
>>>>>> MAINTAINERS | 1 +
>>>>>> drivers/hid/Kconfig | 11 +-
>>>>>> drivers/hid/hid-ids.h | 4 +
>>>>>> drivers/hid/hid-nintendo.c | 1194 ++++++++++++++++-
>>>>>> drivers/hid/hid-nintendo.h | 72 +
>>>>>> drivers/input/joystick/Kconfig | 11 +
>>>>>> drivers/input/joystick/Makefile | 1 +
>>>>>> drivers/input/joystick/nintendo-switch2-usb.c | 353 +++++
>>>>>> 8 files changed, 1637 insertions(+), 10 deletions(-)
>>>>>> create mode 100644 drivers/hid/hid-nintendo.h
>>>>>> create mode 100644 drivers/input/joystick/nintendo-switch2-usb.c
>>>>>>
>>>>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>>>>> index 7b277d5bf3d12..4d1a28df5fd24 100644
>>>>>> --- a/MAINTAINERS
>>>>>> +++ b/MAINTAINERS
>>>>>> @@ -18743,6 +18743,7 @@ F: drivers/scsi/nsp32*
>>>>>>
>>>>>> NINTENDO HID DRIVER
>>>>>> M: Daniel J. Ogorchock <djogorchock@gmail.com>
>>>>>> +M: Vicki Pfau <vi@endrift.com>
>>>>>> L: linux-input@vger.kernel.org
>>>>>> S: Maintained
>>>>>> F: drivers/hid/hid-nintendo*
>>>>>> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
>>>>>> index c1d9f7c6a5f23..1a293a6c02c26 100644
>>>>>> --- a/drivers/hid/Kconfig
>>>>>> +++ b/drivers/hid/Kconfig
>>>>>> @@ -826,10 +826,13 @@ config HID_NINTENDO
>>>>>> depends on LEDS_CLASS
>>>>>> select POWER_SUPPLY
>>>>>> help
>>>>>> - Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller.
>>>>>> - All controllers support bluetooth, and the Pro Controller also supports
>>>>>> - its USB mode. This also includes support for the Nintendo Switch Online
>>>>>> - Controllers which include the NES, Genesis, SNES, and N64 controllers.
>>>>>> + Adds support for the Nintendo Switch Joy-Cons, NSO, Pro Controller, as
>>>>>> + well as Nintendo Switch 2 Joy-Cons, Pro Controller, and NSO GameCube
>>>>>> + controllers. All Switch controllers support bluetooth, and the Pro
>>>>>> + Controller also supports its USB mode. This also includes support for
>>>>>> + the Nintendo Switch Online Controllers which include the NES, Genesis,
>>>>>> + SNES, and N64 controllers. Switch 2 controllers currently only support
>>>>>> + USB mode.
>>>>>>
>>>>>> To compile this driver as a module, choose M here: the
>>>>>> module will be called hid-nintendo.
>>>>>> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
>>>>>> index 4ab7640b119ac..a794dad7980f3 100644
>>>>>> --- a/drivers/hid/hid-ids.h
>>>>>> +++ b/drivers/hid/hid-ids.h
>>>>>> @@ -1073,6 +1073,10 @@
>>>>>> #define USB_DEVICE_ID_NINTENDO_SNESCON 0x2017
>>>>>> #define USB_DEVICE_ID_NINTENDO_GENCON 0x201e
>>>>>> #define USB_DEVICE_ID_NINTENDO_N64CON 0x2019
>>>>>> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONR 0x2066
>>>>>> +#define USB_DEVICE_ID_NINTENDO_NS2_JOYCONL 0x2067
>>>>>> +#define USB_DEVICE_ID_NINTENDO_NS2_PROCON 0x2069
>>>>>> +#define USB_DEVICE_ID_NINTENDO_NS2_GCCON 0x2073
>>>>>>
>>>>>> #define USB_VENDOR_ID_NOVATEK 0x0603
>>>>>> #define USB_DEVICE_ID_NOVATEK_PCT 0x0600
>>>>>> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
>>>>>> index 29008c2cc5304..4ab8d4e7558a1 100644
>>>>>> --- a/drivers/hid/hid-nintendo.c
>>>>>> +++ b/drivers/hid/hid-nintendo.c
>>>>>> @@ -1,11 +1,13 @@
>>>>>> // SPDX-License-Identifier: GPL-2.0+
>>>>>> /*
>>>>>> - * HID driver for Nintendo Switch Joy-Cons and Pro Controllers
>>>>>> + * HID driver for Nintendo Switch Joy-Cons and Pro Controllers, as well as
>>>>>> + * Nintendo Switch 2 Joy-Cons, Pro Controller, and GameCube Controller
>>>>>> *
>>>>>> * Copyright (c) 2019-2021 Daniel J. Ogorchock <djogorchock@gmail.com>
>>>>>> * Portions Copyright (c) 2020 Nadia Holmquist Pedersen <nadia@nhp.sh>
>>>>>> * Copyright (c) 2022 Emily Strickland <linux@emily.st>
>>>>>> * Copyright (c) 2023 Ryan McClelland <rymcclel@gmail.com>
>>>>>> + * Copyright (c) 2026 Valve Software
>>>>>> *
>>>>>> * The following resources/projects were referenced for this driver:
>>>>>> * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
>>>>>> @@ -13,6 +15,8 @@
>>>>>> * https://github.com/FrotBot/SwitchProConLinuxUSB
>>>>>> * https://github.com/MTCKC/ProconXInput
>>>>>> * https://github.com/Davidobot/BetterJoyForCemu
>>>>>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
>>>>>> + * https://github.com/ndeadly/switch2_controller_research
>>>>>> * hid-wiimote kernel hid driver
>>>>>> * hid-logitech-hidpp driver
>>>>>> * hid-sony driver
>>>>>> @@ -29,6 +33,7 @@
>>>>>> */
>>>>>>
>>>>>> #include "hid-ids.h"
>>>>>> +#include "hid-nintendo.h"
>>>>>> #include <linux/unaligned.h>
>>>>>> #include <linux/delay.h>
>>>>>> #include <linux/device.h>
>>>>>> @@ -41,6 +46,8 @@
>>>>>> #include <linux/module.h>
>>>>>> #include <linux/power_supply.h>
>>>>>> #include <linux/spinlock.h>
>>>>>> +#include <linux/usb.h>
>>>>>> +#include "usbhid/usbhid.h"
>>>>>>
>>>>>> /*
>>>>>> * Reference the url below for the following HID report defines:
>>>>>> @@ -2614,7 +2621,7 @@ static int joycon_ctlr_handle_event(struct joycon_ctlr *ctlr, u8 *data,
>>>>>> return ret;
>>>>>> }
>>>>>>
>>>>>> -static int nintendo_hid_event(struct hid_device *hdev,
>>>>>> +static int joycon_event(struct hid_device *hdev,
>>>>>> struct hid_report *report, u8 *raw_data, int size)
>>>>>> {
>>>>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>>>>>> @@ -2625,7 +2632,7 @@ static int nintendo_hid_event(struct hid_device *hdev,
>>>>>> return joycon_ctlr_handle_event(ctlr, raw_data, size);
>>>>>> }
>>>>>>
>>>>>> -static int nintendo_hid_probe(struct hid_device *hdev,
>>>>>> +static int joycon_probe(struct hid_device *hdev,
>>>>>> const struct hid_device_id *id)
>>>>>> {
>>>>>> int ret;
>>>>>> @@ -2729,7 +2736,7 @@ static int nintendo_hid_probe(struct hid_device *hdev,
>>>>>> return ret;
>>>>>> }
>>>>>>
>>>>>> -static void nintendo_hid_remove(struct hid_device *hdev)
>>>>>> +static void joycon_remove(struct hid_device *hdev)
>>>>>> {
>>>>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>>>>>> unsigned long flags;
>>>>>> @@ -2748,7 +2755,9 @@ static void nintendo_hid_remove(struct hid_device *hdev)
>>>>>> hid_hw_stop(hdev);
>>>>>> }
>>>>>>
>>>>>> -static int nintendo_hid_resume(struct hid_device *hdev)
>>>>>> +#ifdef CONFIG_PM
>>>>>> +
>>>>>> +static int joycon_resume(struct hid_device *hdev)
>>>>>> {
>>>>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>>>>>> int ret;
>>>>>> @@ -2771,7 +2780,7 @@ static int nintendo_hid_resume(struct hid_device *hdev)
>>>>>> return ret;
>>>>>> }
>>>>>>
>>>>>> -static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>>>>>> +static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
>>>>>> {
>>>>>> struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>>>>>>
>>>>>> @@ -2790,7 +2799,1120 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>>>>>> return 0;
>>>>>> }
>>>>>>
>>>>>> +#endif
>>>>>> +
>>>>>> +/*
>>>>>> + * =============================================================================
>>>>>> + * Switch 2 support
>>>>>> + * =============================================================================
>>>>>> + */
>>>>>> +#define NS2_BTNR_B BIT(0)
>>>>>> +#define NS2_BTNR_A BIT(1)
>>>>>> +#define NS2_BTNR_Y BIT(2)
>>>>>> +#define NS2_BTNR_X BIT(3)
>>>>>> +#define NS2_BTNR_R BIT(4)
>>>>>> +#define NS2_BTNR_ZR BIT(5)
>>>>>> +#define NS2_BTNR_PLUS BIT(6)
>>>>>> +#define NS2_BTNR_RS BIT(7)
>>>>>> +
>>>>>> +#define NS2_BTNL_DOWN BIT(0)
>>>>>> +#define NS2_BTNL_RIGHT BIT(1)
>>>>>> +#define NS2_BTNL_LEFT BIT(2)
>>>>>> +#define NS2_BTNL_UP BIT(3)
>>>>>> +#define NS2_BTNL_L BIT(4)
>>>>>> +#define NS2_BTNL_ZL BIT(5)
>>>>>> +#define NS2_BTNL_MINUS BIT(6)
>>>>>> +#define NS2_BTNL_LS BIT(7)
>>>>>> +
>>>>>> +#define NS2_BTN3_C BIT(4)
>>>>>> +#define NS2_BTN3_SR BIT(6)
>>>>>> +#define NS2_BTN3_SL BIT(7)
>>>>>> +
>>>>>> +#define NS2_BTN_JCR_HOME BIT(0)
>>>>>> +#define NS2_BTN_JCR_GR BIT(2)
>>>>>> +#define NS2_BTN_JCR_C NS2_BTN3_C
>>>>>> +#define NS2_BTN_JCR_SR NS2_BTN3_SR
>>>>>> +#define NS2_BTN_JCR_SL NS2_BTN3_SL
>>>>>> +
>>>>>> +#define NS2_BTN_JCL_CAPTURE BIT(0)
>>>>>> +#define NS2_BTN_JCL_GL BIT(2)
>>>>>> +#define NS2_BTN_JCL_SR NS2_BTN3_SR
>>>>>> +#define NS2_BTN_JCL_SL NS2_BTN3_SL
>>>>>> +
>>>>>> +#define NS2_BTN_PRO_HOME BIT(0)
>>>>>> +#define NS2_BTN_PRO_CAPTURE BIT(1)
>>>>>> +#define NS2_BTN_PRO_GR BIT(2)
>>>>>> +#define NS2_BTN_PRO_GL BIT(3)
>>>>>> +#define NS2_BTN_PRO_C NS2_BTN3_C
>>>>>> +
>>>>>> +#define NS2_BTN_GC_HOME BIT(0)
>>>>>> +#define NS2_BTN_GC_CAPTURE BIT(1)
>>>>>> +#define NS2_BTN_GC_C NS2_BTN3_C
>>>>>> +
>>>>>> +#define NS2_TRIGGER_RANGE 4095
>>>>>> +#define NS2_AXIS_MIN -32768
>>>>>> +#define NS2_AXIS_MAX 32767
>>>>>> +
>>>>>> +#define NS2_MAX_PLAYER_ID 8
>>>>>> +
>>>>>> +#define NS2_MAX_INIT_RETRIES 4
>>>>>> +
>>>>>> +#define NS2_FLASH_ADDR_SERIAL 0x13002
>>>>>> +#define NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB 0x130a8
>>>>>> +#define NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB 0x130e8
>>>>>> +#define NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB 0x13140
>>>>>> +#define NS2_FLASH_ADDR_USER_PRIMARY_CALIB 0x1fc040
>>>>>> +#define NS2_FLASH_ADDR_USER_SECONDARY_CALIB 0x1fc080
>>>>>> +
>>>>>> +#define NS2_FLASH_SIZE_SERIAL 0x10
>>>>>> +#define NS2_FLASH_SIZE_FACTORY_AXIS_CALIB 9
>>>>>> +#define NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB 2
>>>>>> +#define NS2_FLASH_SIZE_USER_AXIS_CALIB 11
>>>>>> +
>>>>>> +#define NS2_USER_CALIB_MAGIC 0xa1b2
>>>>>> +
>>>>>> +#define NS2_FEATURE_BUTTONS BIT(0)
>>>>>> +#define NS2_FEATURE_ANALOG BIT(1)
>>>>>> +#define NS2_FEATURE_IMU BIT(2)
>>>>>> +#define NS2_FEATURE_MOUSE BIT(4)
>>>>>> +#define NS2_FEATURE_RUMBLE BIT(5)
>>>>>> +#define NS2_FEATURE_MAGNETO BIT(7)
>>>>>> +
>>>>>> +enum switch2_subcmd_flash {
>>>>>> + NS2_SUBCMD_FLASH_READ_BLOCK = 0x01,
>>>>>> + NS2_SUBCMD_FLASH_WRITE_BLOCK = 0x02,
>>>>>> + NS2_SUBCMD_FLASH_ERASE_BLOCK = 0x03,
>>>>>> + NS2_SUBCMD_FLASH_READ = 0x04,
>>>>>> + NS2_SUBCMD_FLASH_WRITE = 0x05,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_subcmd_init {
>>>>>> + NS2_SUBCMD_INIT_SELECT_REPORT = 0xa,
>>>>>> + NS2_SUBCMD_INIT_USB = 0xd,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_subcmd_feature_select {
>>>>>> + NS2_SUBCMD_FEATSEL_GET_INFO = 0x1,
>>>>>> + NS2_SUBCMD_FEATSEL_SET_MASK = 0x2,
>>>>>> + NS2_SUBCMD_FEATSEL_CLEAR_MASK = 0x3,
>>>>>> + NS2_SUBCMD_FEATSEL_ENABLE = 0x4,
>>>>>> + NS2_SUBCMD_FEATSEL_DISABLE = 0x5,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_subcmd_grip {
>>>>>> + NS2_SUBCMD_GRIP_GET_INFO = 0x1,
>>>>>> + NS2_SUBCMD_GRIP_ENABLE_BUTTONS = 0x2,
>>>>>> + NS2_SUBCMD_GRIP_GET_INFO_EXT = 0x3,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_subcmd_led {
>>>>>> + NS2_SUBCMD_LED_P1 = 0x1,
>>>>>> + NS2_SUBCMD_LED_P2 = 0x2,
>>>>>> + NS2_SUBCMD_LED_P3 = 0x3,
>>>>>> + NS2_SUBCMD_LED_P4 = 0x4,
>>>>>> + NS2_SUBCMD_LED_ALL_ON = 0x5,
>>>>>> + NS2_SUBCMD_LED_ALL_OFF = 0x6,
>>>>>> + NS2_SUBCMD_LED_PATTERN = 0x7,
>>>>>> + NS2_SUBCMD_LED_BLINK = 0x8,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_subcmd_fw_info {
>>>>>> + NS2_SUBCMD_FW_INFO_GET = 0x1,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_ctlr_type {
>>>>>> + NS2_CTLR_TYPE_JCL = 0x00,
>>>>>> + NS2_CTLR_TYPE_JCR = 0x01,
>>>>>> + NS2_CTLR_TYPE_PRO = 0x02,
>>>>>> + NS2_CTLR_TYPE_GC = 0x03,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_report_id {
>>>>>> + NS2_REPORT_UNIFIED = 0x05,
>>>>>> + NS2_REPORT_JCL = 0x07,
>>>>>> + NS2_REPORT_JCR = 0x08,
>>>>>> + NS2_REPORT_PRO = 0x09,
>>>>>> + NS2_REPORT_GC = 0x0a,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_init_step {
>>>>>> + NS2_INIT_READ_SERIAL,
>>>>>> + NS2_INIT_GET_FIRMWARE_INFO,
>>>>>> + NS2_INIT_READ_FACTORY_PRIMARY_CALIB,
>>>>>> + NS2_INIT_READ_FACTORY_SECONDARY_CALIB,
>>>>>> + NS2_INIT_READ_FACTORY_TRIGGER_CALIB,
>>>>>> + NS2_INIT_READ_USER_PRIMARY_CALIB,
>>>>>> + NS2_INIT_READ_USER_SECONDARY_CALIB,
>>>>>> + NS2_INIT_SET_FEATURE_MASK,
>>>>>> + NS2_INIT_ENABLE_FEATURES,
>>>>>> + NS2_INIT_GRIP_BUTTONS,
>>>>>> + NS2_INIT_REPORT_FORMAT,
>>>>>> + NS2_INIT_SET_PLAYER_LEDS,
>>>>>> + NS2_INIT_INPUT,
>>>>>> + NS2_INIT_FINISH,
>>>>>> + NS2_INIT_DONE,
>>>>>> +};
>>>>>> +
>>>>>> +struct switch2_version_info {
>>>>>> + uint8_t major;
>>>>>> + uint8_t minor;
>>>>>> + uint8_t patch;
>>>>>> + uint8_t ctlr_type;
>>>>>> + __le32 unk;
>>>>>> + int8_t dsp_major;
>>>>>> + int8_t dsp_minor;
>>>>>> + int8_t dsp_patch;
>>>>>> + int8_t dsp_type;
>>>>>> +};
>>>>>> +
>>>>>> +struct switch2_axis_calibration {
>>>>>> + uint16_t neutral;
>>>>>> + uint16_t negative;
>>>>>> + uint16_t positive;
>>>>>> +};
>>>>>> +
>>>>>> +struct switch2_stick_calibration {
>>>>>> + struct switch2_axis_calibration x;
>>>>>> + struct switch2_axis_calibration y;
>>>>>> +};
>>>>>> +
>>>>>> +struct switch2_controller {
>>>>>> + struct hid_device *hdev;
>>>>>> + struct switch2_cfg_intf *cfg;
>>>>>> +
>>>>>> + char name[64];
>>>>>> + char phys[64];
>>>>>> + struct list_head entry;
>>>>>> + struct mutex lock;
>>>>>> +
>>>>>> + enum switch2_ctlr_type ctlr_type;
>>>>>> + enum switch2_init_step init_step;
>>>>>> + struct input_dev __rcu *input;
>>>>>> + char serial[NS2_FLASH_SIZE_SERIAL + 1];
>>>>>> + struct switch2_version_info version;
>>>>>> +
>>>>>> + struct switch2_stick_calibration stick_calib[2];
>>>>>> + uint8_t lt_zero;
>>>>>> + uint8_t rt_zero;
>>>>>> +
>>>>>> + uint32_t player_id;
>>>>>> + struct led_classdev leds[4];
>>>>>> +};
>>>>>> +
>>>>>> +static DEFINE_MUTEX(switch2_controllers_lock);
>>>>>> +static LIST_HEAD(switch2_controllers);
>>>>>> +
>>>>>> +struct switch2_ctlr_button_mapping {
>>>>>> + uint32_t code;
>>>>>> + int byte;
>>>>>> + uint32_t bit;
>>>>>> +};
>>>>>> +
>>>>>> +static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[] = {
>>>>>> + { BTN_DPAD_LEFT, 0, NS2_BTNL_LEFT, },
>>>>>> + { BTN_DPAD_UP, 0, NS2_BTNL_UP, },
>>>>>> + { BTN_DPAD_DOWN, 0, NS2_BTNL_DOWN, },
>>>>>> + { BTN_DPAD_RIGHT, 0, NS2_BTNL_RIGHT, },
>>>>>> + { BTN_TL, 0, NS2_BTNL_L, },
>>>>>> + { BTN_TL2, 0, NS2_BTNL_ZL, },
>>>>>> + { BTN_SELECT, 0, NS2_BTNL_MINUS, },
>>>>>> + { BTN_THUMBL, 0, NS2_BTNL_LS, },
>>>>>> + { KEY_RECORD, 1, NS2_BTN_JCL_CAPTURE, },
>>>>>> + { BTN_GRIPR, 1, NS2_BTN_JCL_SL, },
>>>>>> + { BTN_GRIPR2, 1, NS2_BTN_JCL_SR, },
>>>>>> + { BTN_GRIPL, 1, NS2_BTN_JCL_GL, },
>>>>>> + { /* sentinel */ },
>>>>>> +};
>>>>>> +
>>>>>> +static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings[] = {
>>>>>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
>>>>>> + { BTN_EAST, 0, NS2_BTNR_B, },
>>>>>> + { BTN_NORTH, 0, NS2_BTNR_X, },
>>>>>> + { BTN_WEST, 0, NS2_BTNR_Y, },
>>>>>> + { BTN_TR, 0, NS2_BTNR_R, },
>>>>>> + { BTN_TR2, 0, NS2_BTNR_ZR, },
>>>>>> + { BTN_START, 0, NS2_BTNR_PLUS, },
>>>>>> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
>>>>>> + { BTN_C, 1, NS2_BTN_JCR_C, },
>>>>>> + { BTN_MODE, 1, NS2_BTN_JCR_HOME, },
>>>>>> + { BTN_GRIPL2, 1, NS2_BTN_JCR_SL, },
>>>>>> + { BTN_GRIPL, 1, NS2_BTN_JCR_SR, },
>>>>>> + { BTN_GRIPR, 1, NS2_BTN_JCR_GR, },
>>>>>> + { /* sentinel */ },
>>>>>> +};
>>>>>> +
>>>>>> +static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
>>>>>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
>>>>>> + { BTN_EAST, 0, NS2_BTNR_B, },
>>>>>> + { BTN_NORTH, 0, NS2_BTNR_X, },
>>>>>> + { BTN_WEST, 0, NS2_BTNR_Y, },
>>>>>> + { BTN_TL, 1, NS2_BTNL_L, },
>>>>>> + { BTN_TR, 0, NS2_BTNR_R, },
>>>>>> + { BTN_TL2, 1, NS2_BTNL_ZL, },
>>>>>> + { BTN_TR2, 0, NS2_BTNR_ZR, },
>>>>>> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
>>>>>> + { BTN_START, 0, NS2_BTNR_PLUS, },
>>>>>> + { BTN_THUMBL, 1, NS2_BTNL_LS, },
>>>>>> + { BTN_THUMBR, 0, NS2_BTNR_RS, },
>>>>>> + { BTN_MODE, 2, NS2_BTN_PRO_HOME },
>>>>>> + { KEY_RECORD, 2, NS2_BTN_PRO_CAPTURE },
>>>>>> + { BTN_GRIPR, 2, NS2_BTN_PRO_GR },
>>>>>> + { BTN_GRIPL, 2, NS2_BTN_PRO_GL },
>>>>>> + { BTN_C, 2, NS2_BTN_PRO_C },
>>>>>> + { /* sentinel */ },
>>>>>> +};
>>>>>> +
>>>>>> +static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
>>>>>> + { BTN_SOUTH, 0, NS2_BTNR_A, },
>>>>>> + { BTN_EAST, 0, NS2_BTNR_B, },
>>>>>> + { BTN_NORTH, 0, NS2_BTNR_X, },
>>>>>> + { BTN_WEST, 0, NS2_BTNR_Y, },
>>>>>> + { BTN_TL2, 1, NS2_BTNL_L, },
>>>>>> + { BTN_TR2, 0, NS2_BTNR_R, },
>>>>>> + { BTN_TL, 1, NS2_BTNL_ZL, },
>>>>>> + { BTN_TR, 0, NS2_BTNR_ZR, },
>>>>>> + { BTN_SELECT, 1, NS2_BTNL_MINUS, },
>>>>>> + { BTN_START, 0, NS2_BTNR_PLUS, },
>>>>>> + { BTN_MODE, 2, NS2_BTN_GC_HOME },
>>>>>> + { KEY_RECORD, 2, NS2_BTN_GC_CAPTURE },
>>>>>> + { BTN_C, 2, NS2_BTN_GC_C },
>>>>>> + { /* sentinel */ },
>>>>>> +};
>>>>>> +
>>>>>> +static const uint8_t switch2_init_cmd_data[] = {
>>>>>> + /*
>>>>>> + * The last 6 bytes of this packet are the MAC address of
>>>>>> + * the console, but we don't need that for USB
>>>>>> + */
>>>>>> + 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
>>>>>> +};
>>>>>> +
>>>>>> +static const uint8_t switch2_one_data[] = { 0x01, 0x00, 0x00, 0x00 };
>>>>>> +
>>>>>> +static const uint8_t switch2_feature_mask[] = {
>>>>>> + NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU,
>>>>>> + 0x00, 0x00, 0x00
>>>>>> +};
>>>>>> +
>>>>>> +static void switch2_init_step_done(struct switch2_controller *ns2, enum switch2_init_step step)
>>>>>> +{
>>>>>> + if (ns2->init_step != step)
>>>>>> + return;
>>>>>> +
>>>>>> + ns2->init_step++;
>>>>>> +}
>>>>>> +
>>>>>> +static inline bool switch2_ctlr_is_joycon(enum switch2_ctlr_type type)
>>>>>> +{
>>>>>> + return type == NS2_CTLR_TYPE_JCL || type == NS2_CTLR_TYPE_JCR;
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_set_leds(struct switch2_controller *ns2)
>>>>>> +{
>>>>>> + int i;
>>>>>> + uint8_t message[8] = { 0 };
>>>>>> +
>>>>>> + for (i = 0; i < JC_NUM_LEDS; i++)
>>>>>> + message[0] |= (!!ns2->leds[i].brightness) << i;
>>>>>> +
>>>>>> + if (!ns2->cfg)
>>>>>> + return -ENOTCONN;
>>>>>> + return ns2->cfg->send_command(NS2_CMD_LED, NS2_SUBCMD_LED_PATTERN,
>>>>>> + &message, sizeof(message),
>>>>>> + ns2->cfg);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_player_led_brightness_set(struct led_classdev *led,
>>>>>> + enum led_brightness brightness)
>>>>>> +{
>>>>>> + struct device *dev = led->dev->parent;
>>>>>> + struct hid_device *hdev = to_hid_device(dev);
>>>>>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
>>>>>> +
>>>>>> + if (!ns2)
>>>>>> + return -ENODEV;
>>>>>> +
>>>>>> + guard(mutex)(&ns2->lock);
>>>>>> + return switch2_set_leds(ns2);
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_leds_create(struct switch2_controller *ns2)
>>>>>> +{
>>>>>> + struct hid_device *hdev = ns2->hdev;
>>>>>> + struct led_classdev *led;
>>>>>> + int i;
>>>>>> + int player_led_pattern;
>>>>>> +
>>>>>> + player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
>>>>>> + hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
>>>>>> +
>>>>>> + for (i = 0; i < JC_NUM_LEDS; i++) {
>>>>>> + led = &ns2->leds[i];
>>>>>> + led->brightness = joycon_player_led_patterns[player_led_pattern][i];
>>>>>> + led->max_brightness = 1;
>>>>>> + led->brightness_set_blocking = switch2_player_led_brightness_set;
>>>>>> + led->flags = LED_CORE_SUSPENDRESUME | LED_HW_PLUGGABLE;
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_config_buttons(struct input_dev *idev,
>>>>>> + const struct switch2_ctlr_button_mapping button_mappings[])
>>>>>> +{
>>>>>> + const struct switch2_ctlr_button_mapping *button;
>>>>>> +
>>>>>> + for (button = button_mappings; button->code; button++)
>>>>>> + input_set_capability(idev, EV_KEY, button->code);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_init_input(struct switch2_controller *ns2)
>>>>>> +{
>>>>>> + struct input_dev *input;
>>>>>> + struct hid_device *hdev = ns2->hdev;
>>>>>> + int i;
>>>>>> + int ret;
>>>>>> +
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_FINISH);
>>>>>> +
>>>>>> + rcu_read_lock();
>>>>>> + input = rcu_dereference(ns2->input);
>>>>>> + rcu_read_unlock();
>>>>>> +
>>>>>> + if (input)
>>>>>> + return 0;
>>>>>> +
>>>>>> + input = devm_input_allocate_device(&hdev->dev);
>>>>>> + if (!input)
>>>>>> + return -ENOMEM;
>>>>>> +
>>>>>> + input_set_drvdata(input, ns2);
>>>>>> + input->dev.parent = &hdev->dev;
>>>>>> + input->id.bustype = hdev->bus;
>>>>>> + input->id.vendor = hdev->vendor;
>>>>>> + input->id.product = hdev->product;
>>>>>> + input->id.version = hdev->version;
>>>>>> + input->uniq = ns2->serial;
>>>>>> + input->name = ns2->name;
>>>>>> + input->phys = hdev->phys;
>>>>>> +
>>>>>> + switch (ns2->ctlr_type) {
>>>>>> + case NS2_CTLR_TYPE_JCL:
>>>>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + switch2_config_buttons(input, ns2_left_joycon_button_mappings);
>>>>>> + break;
>>>>>> + case NS2_CTLR_TYPE_JCR:
>>>>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + switch2_config_buttons(input, ns2_right_joycon_button_mappings);
>>>>>> + break;
>>>>>> + case NS2_CTLR_TYPE_GC:
>>>>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_Z, 0, NS2_TRIGGER_RANGE, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_RZ, 0, NS2_TRIGGER_RANGE, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
>>>>>> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
>>>>>> + switch2_config_buttons(input, ns2_gccon_mappings);
>>>>>> + break;
>>>>>> + case NS2_CTLR_TYPE_PRO:
>>>>>> + input_set_abs_params(input, ABS_X, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_Y, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_RX, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_RY, NS2_AXIS_MIN, NS2_AXIS_MAX, 32, 128);
>>>>>> + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
>>>>>> + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
>>>>>> + switch2_config_buttons(input, ns2_procon_mappings);
>>>>>> + break;
>>>>>> + default:
>>>>>> + input_free_device(input);
>>>>>> + return -EINVAL;
>>>>>> + }
>>>>>> +
>>>>>> + hid_info(ns2->hdev, "Firmware version %u.%u.%u (type %i)\n", ns2->version.major,
>>>>>> + ns2->version.minor, ns2->version.patch, ns2->version.ctlr_type);
>>>>>> + if (ns2->version.dsp_type >= 0)
>>>>>> + hid_info(ns2->hdev, "DSP version %u.%u.%u\n", ns2->version.dsp_major,
>>>>>> + ns2->version.dsp_minor, ns2->version.dsp_patch);
>>>>>> +
>>>>>> + ret = input_register_device(input);
>>>>>> + if (ret < 0) {
>>>>>> + hid_err(ns2->hdev, "Failed to register input; ret=%d\n", ret);
>>>>>> + return ret;
>>>>>> + }
>>>>>> +
>>>>>> + for (i = 0; i < JC_NUM_LEDS; i++) {
>>>>>> + struct led_classdev *led = &ns2->leds[i];
>>>>>> + char *name = devm_kasprintf(&input->dev, GFP_KERNEL, "%s:%s:%s",
>>>>>> + dev_name(&input->dev),
>>>>>> + "green",
>>>>>> + joycon_player_led_names[i]);
>>>>>> +
>>>>>> + if (!name)
>>>>>> + return -ENOMEM;
>>>>>> +
>>>>>> + led->name = name;
>>>>>> + ret = devm_led_classdev_register(&input->dev, led);
>>>>>> + if (ret < 0) {
>>>>>> + dev_err(&input->dev, "Failed to register player %d LED; ret=%d\n",
>>>>>> + i + 1, ret);
>>>>>> + input_unregister_device(input);
>>>>>> + return ret;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + rcu_assign_pointer(ns2->input, input);
>>>>>> + synchronize_rcu();
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static struct switch2_controller *switch2_get_controller(const char *phys)
>>>>>> +{
>>>>>> + struct switch2_controller *ns2;
>>>>>> +
>>>>>> + guard(mutex)(&switch2_controllers_lock);
>>>>>> + list_for_each_entry(ns2, &switch2_controllers, entry) {
>>>>>> + if (strncmp(ns2->phys, phys, sizeof(ns2->phys)) == 0)
>>>>>> + return ns2;
>>>>>> + }
>>>>>> + ns2 = kzalloc(sizeof(*ns2), GFP_KERNEL);
>>>>>> + if (!ns2)
>>>>>> + return ERR_PTR(-ENOMEM);
>>>>>> +
>>>>>> + mutex_init(&ns2->lock);
>>>>>> + INIT_LIST_HEAD(&ns2->entry);
>>>>>> + list_add(&ns2->entry, &switch2_controllers);
>>>>>> + strscpy(ns2->phys, phys, sizeof(ns2->phys));
>>>>>> + return ns2;
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_controller_put(struct switch2_controller *ns2)
>>>>>> +{
>>>>>> + struct input_dev *input;
>>>>>> + bool do_free;
>>>>>> +
>>>>>> + guard(mutex)(&switch2_controllers_lock);
>>>>>> + mutex_lock(&ns2->lock);
>>>>>> +
>>>>>> + rcu_read_lock();
>>>>>> + input = rcu_dereference(ns2->input);
>>>>>> + rcu_read_unlock();
>>>>>> +
>>>>>> + rcu_assign_pointer(ns2->input, NULL);
>>>>>> + synchronize_rcu();
>>>>>> +
>>>>>> + ns2->init_step = 0;
>>>>>> + do_free = !ns2->hdev && !ns2->cfg;
>>>>>> + mutex_unlock(&ns2->lock);
>>>>>> +
>>>>>> + if (input)
>>>>>> + input_unregister_device(input);
>>>>>> +
>>>>>> + if (do_free) {
>>>>>> + list_del_init(&ns2->entry);
>>>>>> + mutex_destroy(&ns2->lock);
>>>>>> + kfree(ns2);
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static bool switch2_parse_stick_calibration(struct switch2_stick_calibration *calib,
>>>>>> + const uint8_t *data)
>>>>>> +{
>>>>>> + static const uint8_t UNCALIBRATED[9] = {
>>>>>> + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
>>>>>> + };
>>>>>> + if (memcmp(UNCALIBRATED, data, sizeof(UNCALIBRATED)) == 0)
>>>>>> + return false;
>>>>>> +
>>>>>> + calib->x.neutral = data[0];
>>>>>> + calib->x.neutral |= (data[1] & 0x0F) << 8;
>>>>>> +
>>>>>> + calib->y.neutral = data[1] >> 4;
>>>>>> + calib->y.neutral |= data[2] << 4;
>>>>>> +
>>>>>> + calib->x.positive = data[3];
>>>>>> + calib->x.positive |= (data[4] & 0x0F) << 8;
>>>>>> +
>>>>>> + calib->y.positive = data[4] >> 4;
>>>>>> + calib->y.positive |= data[5] << 4;
>>>>>> +
>>>>>> + calib->x.negative = data[6];
>>>>>> + calib->x.negative |= (data[7] & 0x0F) << 8;
>>>>>> +
>>>>>> + calib->y.negative = data[7] >> 4;
>>>>>> + calib->y.negative |= data[8] << 4;
>>>>>> +
>>>>>> + return true;
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_handle_flash_read(struct switch2_controller *ns2, uint8_t size,
>>>>>> + uint32_t address, const uint8_t *data)
>>>>>> +{
>>>>>> + bool ok;
>>>>>> +
>>>>>> + switch (address) {
>>>>>> + case NS2_FLASH_ADDR_SERIAL:
>>>>>> + if (size != NS2_FLASH_SIZE_SERIAL)
>>>>>> + return;
>>>>>> + memcpy(ns2->serial, data, size);
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_SERIAL);
>>>>>> + break;
>>>>>> + case NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB:
>>>>>> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
>>>>>> + return;
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_PRIMARY_CALIB);
>>>>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], data);
>>>>>> + if (ok) {
>>>>>> + hid_dbg(ns2->hdev, "Got factory primary stick calibration:\n");
>>>>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>>>>>> + ns2->stick_calib[0].x.negative,
>>>>>> + ns2->stick_calib[0].x.neutral,
>>>>>> + ns2->stick_calib[0].x.positive);
>>>>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>>>>>> + ns2->stick_calib[0].y.negative,
>>>>>> + ns2->stick_calib[0].y.neutral,
>>>>>> + ns2->stick_calib[0].y.positive);
>>>>>> + } else {
>>>>>> + hid_dbg(ns2->hdev, "Factory primary stick calibration not present\n");
>>>>>> + }
>>>>>> + break;
>>>>>> + case NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB:
>>>>>> + if (size != NS2_FLASH_SIZE_FACTORY_AXIS_CALIB)
>>>>>> + return;
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
>>>>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], data);
>>>>>> + if (ok) {
>>>>>> + hid_dbg(ns2->hdev, "Got factory secondary stick calibration:\n");
>>>>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>>>>>> + ns2->stick_calib[1].x.negative,
>>>>>> + ns2->stick_calib[1].x.neutral,
>>>>>> + ns2->stick_calib[1].x.positive);
>>>>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>>>>>> + ns2->stick_calib[1].y.negative,
>>>>>> + ns2->stick_calib[1].y.neutral,
>>>>>> + ns2->stick_calib[1].y.positive);
>>>>>> + } else {
>>>>>> + hid_dbg(ns2->hdev, "Factory secondary stick calibration not present\n");
>>>>>> + }
>>>>>> + break;
>>>>>> + case NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB:
>>>>>> + if (size != NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB)
>>>>>> + return;
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
>>>>>> + if (data[0] != 0xFF && data[1] != 0xFF) {
>>>>>> + ns2->lt_zero = data[0];
>>>>>> + ns2->rt_zero = data[1];
>>>>>> +
>>>>>> + hid_dbg(ns2->hdev, "Got factory trigger calibration:\n");
>>>>>> + hid_dbg(ns2->hdev, "Left zero point: %i\n", ns2->lt_zero);
>>>>>> + hid_dbg(ns2->hdev, "Right zero point: %i\n", ns2->rt_zero);
>>>>>> + } else {
>>>>>> + hid_dbg(ns2->hdev, "Factory trigger calibration not present\n");
>>>>>> + }
>>>>>> + break;
>>>>>> + case NS2_FLASH_ADDR_USER_PRIMARY_CALIB:
>>>>>> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
>>>>>> + return;
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_PRIMARY_CALIB);
>>>>>> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
>>>>>> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], &data[2]);
>>>>>> + if (ok) {
>>>>>> + hid_dbg(ns2->hdev, "Got user primary stick calibration:\n");
>>>>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>>>>>> + ns2->stick_calib[0].x.negative,
>>>>>> + ns2->stick_calib[0].x.neutral,
>>>>>> + ns2->stick_calib[0].x.positive);
>>>>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>>>>>> + ns2->stick_calib[0].y.negative,
>>>>>> + ns2->stick_calib[0].y.neutral,
>>>>>> + ns2->stick_calib[0].y.positive);
>>>>>> + } else {
>>>>>> + hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
>>>>>> + }
>>>>>> + break;
>>>>>> + case NS2_FLASH_ADDR_USER_SECONDARY_CALIB:
>>>>>> + if (size != NS2_FLASH_SIZE_USER_AXIS_CALIB)
>>>>>> + return;
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
>>>>>> + if (__le16_to_cpu(*(__le16 *)data) != NS2_USER_CALIB_MAGIC) {
>>>>>> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], &data[2]);
>>>>>> + if (ok) {
>>>>>> + hid_dbg(ns2->hdev, "Got user secondary stick calibration:\n");
>>>>>> + hid_dbg(ns2->hdev, "Left max: %i, neutral: %i, right max: %i\n",
>>>>>> + ns2->stick_calib[1].x.negative,
>>>>>> + ns2->stick_calib[1].x.neutral,
>>>>>> + ns2->stick_calib[1].x.positive);
>>>>>> + hid_dbg(ns2->hdev, "Down max: %i, neutral: %i, up max: %i\n",
>>>>>> + ns2->stick_calib[1].y.negative,
>>>>>> + ns2->stick_calib[1].y.neutral,
>>>>>> + ns2->stick_calib[1].y.positive);
>>>>>> + } else {
>>>>>> + hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
>>>>>> + }
>>>>>> + break;
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_report_buttons(struct input_dev *input, const uint8_t *bytes,
>>>>>> + const struct switch2_ctlr_button_mapping button_mappings[])
>>>>>> +{
>>>>>> + const struct switch2_ctlr_button_mapping *button;
>>>>>> +
>>>>>> + for (button = button_mappings; button->code; button++)
>>>>>> + input_report_key(input, button->code, bytes[button->byte] & button->bit);
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_report_axis(struct input_dev *input, struct switch2_axis_calibration *calib,
>>>>>> + int axis, bool invert, int value)
>>>>>> +{
>>>>>> + if (calib && calib->neutral && calib->negative && calib->positive) {
>>>>>> + value -= calib->neutral;
>>>>>> + value *= NS2_AXIS_MAX + 1;
>>>>>> + if (value < 0)
>>>>>> + value /= calib->negative;
>>>>>> + else
>>>>>> + value /= calib->positive;
>>>>>> + } else {
>>>>>> + value = (value - 2048) * 16;
>>>>>> + }
>>>>>> +
>>>>>> + if (invert)
>>>>>> + value = -value;
>>>>>> + input_report_abs(input, axis,
>>>>>> + clamp(value, NS2_AXIS_MIN, NS2_AXIS_MAX));
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_report_stick(struct input_dev *input, struct switch2_stick_calibration *calib,
>>>>>> + int x, bool invert_x, int y, bool invert_y, const uint8_t *data)
>>>>>> +{
>>>>>> + switch2_report_axis(input, &calib->x, x, invert_x, data[0] | ((data[1] & 0x0F) << 8));
>>>>>> + switch2_report_axis(input, &calib->y, y, invert_y, (data[1] >> 4) | (data[2] << 4));
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_report_trigger(struct input_dev *input, uint8_t zero, int abs, uint8_t data)
>>>>>> +{
>>>>>> + int value = (NS2_TRIGGER_RANGE + 1) * (data - zero) / (232 - zero);
>>>>>> +
>>>>>> + input_report_abs(input, abs, clamp(value, 0, NS2_TRIGGER_RANGE));
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_event(struct hid_device *hdev, struct hid_report *report, uint8_t *raw_data,
>>>>>> + int size)
>>>>>> +{
>>>>>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
>>>>>> + struct input_dev *input;
>>>>>> +
>>>>>> + if (report->type != HID_INPUT_REPORT)
>>>>>> + return 0;
>>>>>> +
>>>>>> + if (size < 15)
>>>>>> + return -EINVAL;
>>>>>> +
>>>>>> + guard(rcu)();
>>>>>> + input = rcu_dereference(ns2->input);
>>>>>> +
>>>>>> + if (!input)
>>>>>> + return 0;
>>>>>> +
>>>>>> + switch (report->id) {
>>>>>> + case NS2_REPORT_UNIFIED:
>>>>>> + /*
>>>>>> + * TODO
>>>>>> + * This won't be sent unless the report type gets changed via command
>>>>>> + * 03-0A, but we should support it at some point regardless.
>>>>>> + */
>>>>>> + break;
>>>>>> + case NS2_REPORT_JCL:
>>>>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>>>>>> + ABS_Y, true, &raw_data[6]);
>>>>>> + switch2_report_buttons(input, &raw_data[3], ns2_left_joycon_button_mappings);
>>>>>> + break;
>>>>>> + case NS2_REPORT_JCR:
>>>>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>>>>>> + ABS_Y, true, &raw_data[6]);
>>>>>> + switch2_report_buttons(input, &raw_data[3], ns2_right_joycon_button_mappings);
>>>>>> + break;
>>>>>> + case NS2_REPORT_GC:
>>>>>> + input_report_abs(input, ABS_HAT0X,
>>>>>> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
>>>>>> + !!(raw_data[4] & NS2_BTNL_LEFT));
>>>>>> + input_report_abs(input, ABS_HAT0Y,
>>>>>> + !!(raw_data[4] & NS2_BTNL_DOWN) -
>>>>>> + !!(raw_data[4] & NS2_BTNL_UP));
>>>>>> + switch2_report_buttons(input, &raw_data[3], ns2_gccon_mappings);
>>>>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>>>>>> + ABS_Y, true, &raw_data[6]);
>>>>>> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
>>>>>> + ABS_RY, true, &raw_data[9]);
>>>>>> + switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[13]);
>>>>>> + switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[14]);
>>>>>> + break;
>>>>>> + case NS2_REPORT_PRO:
>>>>>> + input_report_abs(input, ABS_HAT0X,
>>>>>> + !!(raw_data[4] & NS2_BTNL_RIGHT) -
>>>>>> + !!(raw_data[4] & NS2_BTNL_LEFT));
>>>>>> + input_report_abs(input, ABS_HAT0Y,
>>>>>> + !!(raw_data[4] & NS2_BTNL_DOWN) -
>>>>>> + !!(raw_data[4] & NS2_BTNL_UP));
>>>>>> + switch2_report_buttons(input, &raw_data[3], ns2_procon_mappings);
>>>>>> + switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,
>>>>>> + ABS_Y, true, &raw_data[6]);
>>>>>> + switch2_report_stick(input, &ns2->stick_calib[1], ABS_RX, false,
>>>>>> + ABS_RY, true, &raw_data[9]);
>>>>>> + break;
>>>>>> + default:
>>>>>> + return -EINVAL;
>>>>>> + }
>>>>>> +
>>>>>> + input_sync(input);
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_features_enable(struct switch2_controller *ns2, int features)
>>>>>> +{
>>>>>> + __le32 feature_bits = __cpu_to_le32(features);
>>>>>> +
>>>>>> + if (!ns2->cfg)
>>>>>> + return -ENOTCONN;
>>>>>> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_ENABLE,
>>>>>> + &feature_bits, sizeof(feature_bits),
>>>>>> + ns2->cfg);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_read_flash(struct switch2_controller *ns2, uint32_t address,
>>>>>> + uint8_t size)
>>>>>> +{
>>>>>> + uint8_t message[8] = { size, 0x7e };
>>>>>> +
>>>>>> + if (!ns2->cfg)
>>>>>> + return -ENOTCONN;
>>>>>> + *(__le32 *)&message[4] = __cpu_to_le32(address);
>>>>>> + return ns2->cfg->send_command(NS2_CMD_FLASH, NS2_SUBCMD_FLASH_READ, message,
>>>>>> + sizeof(message), ns2->cfg);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_set_player_id(struct switch2_controller *ns2, uint32_t player_id)
>>>>>> +{
>>>>>> + int i;
>>>>>> + int player_led_pattern = player_id % JC_NUM_LED_PATTERNS;
>>>>>> +
>>>>>> + for (i = 0; i < JC_NUM_LEDS; i++)
>>>>>> + ns2->leds[i].brightness = joycon_player_led_patterns[player_led_pattern][i];
>>>>>> +
>>>>>> + return switch2_set_leds(ns2);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
>>>>>> +{
>>>>>> + __le32 format_id = __cpu_to_le32(fmt);
>>>>>> +
>>>>>> + if (!ns2->cfg)
>>>>>> + return -ENOTCONN;
>>>>>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
>>>>>> + &format_id, sizeof(format_id),
>>>>>> + ns2->cfg);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_init_controller(struct switch2_controller *ns2)
>>>>>
>>>>> This is now a recursive call while in v1 it wasn't. I think I preferred
>>>>> the non-recursive version as there was one place where init_step
>>>>> state was changed while now I am not sure where it happens (and whether
>>>>> there is a code path where we end up in an infinite recursion)
>>>>>
>>>>> What is the advantage of the recursive version compared to the
>>>>> non-recursive one?
>>> >
>>>>
>>>> The old version incremented the step regardless of whether or not it
>>>> could confirm it had happened. Since the confirmation is now handled
>>>> with an external step, calling into switch2_init_step_done, the loop
>>>> condition would become somewhat complicated.
>>>> I replaced it with explicit tail calls since that make the
>>>> control flow simplier, and it is always matched with a call to
>>>> switch2_init_step_done to ensure that the state is always advanced. As
>>>
>>> From what I can tell switch2_init_step_done currently only advances
>>> the state if the current state is the expected one. This seems fine,
>>> but it also means that if the state is not the expected one, the
>>> state is not advanced and the recursive call continues anyway (in the
>>> NS2_INIT_READ_USER_SECONDARY_CALIB case, for example). I assume this
>>> should never happen but if we end up in this case for some reason we
>>> will recurse forever.
>>
>> That's correct, and the only way it would happen forever is if there's a
>> bug. The same would be true in a loop version if it doesn't advance the
>> state properly either, fwiw, which happened during development of this
>> version. Regardless, I can reduce the chance of introducing such a bug
>> by passing ns2->init_step instead of a constant, so I'll make that
>> change in v4.
>>
>>>
>>> The same case also potentially calls switch2_read_flash and it isn't
>>> clear to me if this means that the initialisation is done (as there
>>> is no switch2_init_step_done call and we are not in the FINISH state
>>> either). There is also the possibility of switch2_read_flash calling
>>> switch2_init_controller again, which one then has to check ... (note
>>> that this is not the case here though)
>>
>> switch2_handle_flash_read will advance the state once it's verified that
>> the read actually happened. If the step failed for whatever reason, this
>> same codepath will retry the specific read, as the caller
>> (switch2_receive_command) will always call into switch2_init_controller
>> if setup isn't done. This is how the retry logic works.
>
> Ah, so the call chain looks something like the below?
>
> switch2_read_flash->
> switch2_usb_send_cmd->
> switch2_usb_message_in_work (?)->
> switch2_receive_command->
> switch2_handle_flash_read->
> switch2_init_step_done
Yes, and then switch2_init_controller is called again at the end of
switch2_receive_command
>
>>
>>>
>>> To me it seems like it would be clearer to do a `ns2->init_step++` and
>>> then `continue` to make the progress of the state more visible and to
>>> do an explicit `break` when we are supposed to stop the initialisation.
>>>
>>
>> The problem with the loop approach, in my opinion is due to the fact
>> that the loop is the *exception*, not the rule. The loop idiom makes it
>> look like a loop is expected. Further the ns2->init_step++ in the
>> previous version means that the verification does not occur, so in the
>> case of any sort of failure it'll plow ahead anyway instead of retrying.
>> The point of this approach is to avoid that.
>
> In my mind a while-loop like you mentioned it above would make the state
> changes more obvious (since they could all be done in the loop body), while
> still allowing for retries. Something like the below, perhaps (untested).
>
> while (ns2->init_step < NS2_INIT_DONE) {
> switch (ns2->init_step) {
> ...
> case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
> if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
> ns->init_step++
> continue;
> }
>
> ret = switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
> NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
> if (ret) {
> // if it makes sense to retry here
> continue;
> }
>
> ns->init_step++
> break;
>
> case ...
> }
> }
>
This can't be done because the process is fundamentally asynchronous.
We'd have to block on waiting for a reply to the USB packet, which is
not a good idea. This is why switch2_receive_command calls into
switch2_init_controller at the end: it's resuming where it left off.
Each of those returns is a step that interacts with the hardware, and we
need to wait for the hardware to reply.
There is a potential weird interaction here whereby if we get an
unprompted command and/or reply from the controller it will retry a step
before it gets a reply for it, but in practice this doesn't happen. The
controllers, as far as we know, only reply and never initiate any
commands. Furthermore, all of these steps are also idempotent, so it's
not a big deal of they get repeated erroneously.
>>
>>>
>>>> such, the number recursive calls is explicitly limited, and the fact
>>>> that they're tail calls should mean that it doesn't increase the stack
>>>> depth (unless there's something I don't know about how the kernel
>>>> is compiled, which is possible in the wake of things like retpoline
>>>> protections).
>>>
>>> I actually wasn't aware that the tail call optimisation means that no
>>> new stack frame will be added, neat!
>>>
>>> I don't know how or if retpoline has an impact on this optimisation
>>> either, I am afraid.
>>>
>>>
>>>> I suppose I could replace it with a loop, but the condition would be
>>>> the same so the only real difference would be a `while (ns2->init_step
>>>> < NS2_INIT_DONE)` instead of the tail calls; the tail calls themselves
>>>> would just become break statements. There would be no functional
>>>> difference.
>>>
>>> hm, wouldn't the tail calls become "continue" statements (as the loop
>>> version would just start a new iteration there)?
>>
>> Yes, it'd be (roughly) equivalent. It'd just read differently.
>
> Yes, I agree.
>
>>
>>>
>>> I agree that both versions can definitely be implemented in an equivalent
>>> manner with no functional differences, but I do have my preference :)
>>>
>>> This is not my call to make anyways though, so let's wait for others to
>>> chime in.
>>>
>>>
>>>>
>>>>>> +{
>>>>>> + if (ns2->init_step == NS2_INIT_DONE)
>>>>>> + return 0;
>>>>>> +
>>>>>> + if (!ns2->cfg)
>>>>>> + return -ENOTCONN;
>>>>>> +
>>>>>> + switch (ns2->init_step) {
>>>>>> + case NS2_INIT_READ_SERIAL:
>>>>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_SERIAL,
>>>>>> + NS2_FLASH_SIZE_SERIAL);
>>>>>> + case NS2_INIT_GET_FIRMWARE_INFO:
>>>>>> + return ns2->cfg->send_command(NS2_CMD_FW_INFO, NS2_SUBCMD_FW_INFO_GET,
>>>>>> + NULL, 0, ns2->cfg);
>>>>>> + case NS2_INIT_READ_FACTORY_PRIMARY_CALIB:
>>>>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_PRIMARY_CALIB,
>>>>>> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
>>>>>> + case NS2_INIT_READ_FACTORY_SECONDARY_CALIB:
>>>>>> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_SECONDARY_CALIB);
>>>>>> + return switch2_init_controller(ns2);
>>>>>> + }
>>>>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_SECONDARY_CALIB,
>>>>>> + NS2_FLASH_SIZE_FACTORY_AXIS_CALIB);
>>>>>> + case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
>>>>>> + if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_FACTORY_TRIGGER_CALIB);
>>>>>> + return switch2_init_controller(ns2);
>>>>>> + }
>>>>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
>>>>>> + NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
>>>>>> + case NS2_INIT_READ_USER_PRIMARY_CALIB:
>>>>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_PRIMARY_CALIB,
>>>>>> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
>>>>>> + case NS2_INIT_READ_USER_SECONDARY_CALIB:
>>>>>> + if (switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_READ_USER_SECONDARY_CALIB);
>>>>>> + return switch2_init_controller(ns2);
>>>>>> + }
>>>>>> + return switch2_read_flash(ns2, NS2_FLASH_ADDR_USER_SECONDARY_CALIB,
>>>>>> + NS2_FLASH_SIZE_USER_AXIS_CALIB);
>>>>>> + case NS2_INIT_SET_FEATURE_MASK:
>>>>>> + return ns2->cfg->send_command(NS2_CMD_FEATSEL, NS2_SUBCMD_FEATSEL_SET_MASK,
>>>>>> + switch2_feature_mask, sizeof(switch2_feature_mask), ns2->cfg);
>>>>>> + case NS2_INIT_ENABLE_FEATURES:
>>>>>> + return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG);
>>>>>> + case NS2_INIT_GRIP_BUTTONS:
>>>>>> + if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
>>>>>> + return switch2_init_controller(ns2);
>>>>>> + }
>>>>>> + return ns2->cfg->send_command(NS2_CMD_GRIP, NS2_SUBCMD_GRIP_ENABLE_BUTTONS,
>>>>>> + switch2_one_data, sizeof(switch2_one_data),
>>>>>> + ns2->cfg);
>>>>>> + case NS2_INIT_REPORT_FORMAT:
>>>>>> + switch (ns2->ctlr_type) {
>>>>>> + case NS2_CTLR_TYPE_JCL:
>>>>>> + return switch2_set_report_format(ns2, NS2_REPORT_JCL);
>>>>>> + case NS2_CTLR_TYPE_JCR:
>>>>>> + return switch2_set_report_format(ns2, NS2_REPORT_JCR);
>>>>>> + case NS2_CTLR_TYPE_PRO:
>>>>>> + return switch2_set_report_format(ns2, NS2_REPORT_PRO);
>>>>>> + case NS2_CTLR_TYPE_GC:
>>>>>> + return switch2_set_report_format(ns2, NS2_REPORT_GC);
>>>>>> + default:
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
>>>>>> + return switch2_init_controller(ns2);
>>>>>> + }
>>>>>> + case NS2_INIT_SET_PLAYER_LEDS:
>>>>>> + return switch2_set_player_id(ns2, ns2->player_id);
>>>>>> + case NS2_INIT_INPUT:
>>>>>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_USB,
>>>>>> + switch2_init_cmd_data, sizeof(switch2_init_cmd_data), ns2->cfg);
>>>>>> + case NS2_INIT_FINISH:
>>>>>> + if (ns2->hdev)
>>>>>
>>>>> If this is not set we skip the switch2_init_input call but don't error
>>>>> out. Is this intentional (are we expecting ns2->hdev to be populated at
>>>>> a later time and this step retried, perhaps)?
>>>>
>>>> This is intentional, yes. There are a handful of places this can
>>>> get called, including the function that sets ns2->hdev in the first
>>>> place (switch2_probe). It's to ensure the steps start as soon as
>>>> possible (when the cfg pointer gets set) but it can't finish until the
>>>> hdev gets set, which can be either before or after, depending on the
>>>> ordering the interfaces get enumerated.
>>>
>>> I'm probably not correctly following the logic in this case, but if the
>>> steps can only finish once ns2->hdev is set and we are `break;`-ing here
>>> in case it's not, doesn't that mean we never retry (since there is no
>>> recursive call after)?
>>
>> We retry in switch2_probe. It calls switch2_init_controller at the end
>> of it, so we hit that step again, and now since ns2->hdev is necessarily
>> filled it continues.
>
> That means the retry is kicked-off from a different part of the code in
> this case, got it!
>
>>
>>>
>>>>
>>>>>
>>>>>> + return switch2_init_input(ns2);
>>>>>> + break;
>>>>>> + default:
>>>>>> + WARN_ON_ONCE(1);
>>>>>> + break;
>>>>>> + }
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +int switch2_receive_command(struct switch2_controller *ns2,
>>>>>> + const uint8_t *message, size_t length)
>>>>>> +{
>>>>>> + const struct switch2_cmd_header *header;
>>>>>> + int ret = 0;
>>>>>> +
>>>>>> + if (length < 8)
>>>>>> + return -EINVAL;
>>>>>> +
>>>>>> + print_hex_dump_debug("got cmd: ", DUMP_PREFIX_OFFSET, 16, 1, message, length, false);
>>>>>> +
>>>>>> + guard(mutex)(&ns2->lock);
>>>>>> +
>>>>>> + header = (const struct switch2_cmd_header *)message;
>>>>>> + if (!(header->flags & NS2_FLAG_OK)) {
>>>>>> + ret = -EIO;
>>>>>> + goto exit;
>>>>>> + }
>>>>>> + message = &message[8];
>>>>>> + switch (header->command) {
>>>>>> + case NS2_CMD_FLASH:
>>>>>> + if (header->subcommand == NS2_SUBCMD_FLASH_READ) {
>>>>>> + uint8_t read_size;
>>>>>> + uint32_t read_address;
>>>>>> +
>>>>>> + if (length < 16) {
>>>>>> + ret = -EINVAL;
>>>>>> + goto exit;
>>>>>> + }
>>>>>> + read_size = message[0];
>>>>>> + read_address = __le32_to_cpu(*(__le32 *)&message[4]);
>>>>>> + if (length < read_size + 16) {
>>>>>> + ret = -EINVAL;
>>>>>> + goto exit;
>>>>>> + }
>>>>>> + switch2_handle_flash_read(ns2, read_size, read_address, &message[8]);
>>>>>> + }
>>>>>> + break;
>>>>>> + case NS2_CMD_INIT:
>>>>>> + if (header->subcommand == NS2_SUBCMD_INIT_USB)
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_INPUT);
>>>>>> + else if (header->subcommand == NS2_SUBCMD_INIT_SELECT_REPORT)
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_REPORT_FORMAT);
>>>>>> + break;
>>>>>> + case NS2_CMD_GRIP:
>>>>>> + if (header->subcommand == NS2_SUBCMD_GRIP_ENABLE_BUTTONS)
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_GRIP_BUTTONS);
>>>>>> + break;
>>>>>> + case NS2_CMD_LED:
>>>>>> + if (header->subcommand == NS2_SUBCMD_LED_PATTERN)
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_SET_PLAYER_LEDS);
>>>>>> + break;
>>>>>> + case NS2_CMD_FEATSEL:
>>>>>> + if (header->subcommand == NS2_SUBCMD_FEATSEL_SET_MASK)
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_SET_FEATURE_MASK);
>>>>>> + else if (header->subcommand == NS2_SUBCMD_FEATSEL_ENABLE)
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_ENABLE_FEATURES);
>>>>>> + break;
>>>>>> + case NS2_CMD_FW_INFO:
>>>>>> + if (header->subcommand == NS2_SUBCMD_FW_INFO_GET) {
>>>>>> + if (length < sizeof(ns2->version)) {
>>>>>> + ret = -EINVAL;
>>>>>> + goto exit;
>>>>>> + }
>>>>>> + memcpy(&ns2->version, message, sizeof(ns2->version));
>>>>>> + ns2->ctlr_type = ns2->version.ctlr_type;
>>>>>> + switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
>>>>>> + }
>>>>>> + break;
>>>>>> + default:
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> +exit:
>>>>>> + if (ns2->init_step < NS2_INIT_DONE)
>>>>>> + switch2_init_controller(ns2);
>>>>>> +
>>>>>> + return ret;
>>>>>> +}
>>>>>> +EXPORT_SYMBOL_GPL(switch2_receive_command);
>>>>>> +
>>>>>> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg)
>>>>>> +{
>>>>>> + struct switch2_controller *ns2 = switch2_get_controller(phys);
>>>>>> +
>>>>>> + if (IS_ERR(ns2))
>>>>>> + return PTR_ERR(ns2);
>>>>>> +
>>>>>> + cfg->parent = ns2;
>>>>>> +
>>>>>> + guard(mutex)(&ns2->lock);
>>>>>> + WARN_ON(ns2->cfg);
>>>>>> + ns2->cfg = cfg;
>>>>>> +
>>>>>> + if (ns2->hdev)
>>>>>> + return switch2_init_controller(ns2);
>>>>>> + return 0;
>>>>>> +}
>>>>>> +EXPORT_SYMBOL_GPL(switch2_controller_attach_cfg);
>>>>>> +
>>>>>> +void switch2_controller_detach_cfg(struct switch2_controller *ns2)
>>>>>> +{
>>>>>> + mutex_lock(&ns2->lock);
>>>>>> + WARN_ON(ns2 != ns2->cfg->parent);
>>>>>> + ns2->cfg = NULL;
>>>>>> + mutex_unlock(&ns2->lock);
>>>>>> + switch2_controller_put(ns2);
>>>>>> +}
>>>>>> +EXPORT_SYMBOL_GPL(switch2_controller_detach_cfg);
>>>>>> +
>>>>>> +static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id)
>>>>>> +{
>>>>>> + struct switch2_controller *ns2;
>>>>>> + struct usb_device *udev;
>>>>>> + char phys[64];
>>>>>> + int ret;
>>>>>> +
>>>>>> + if (!hid_is_usb(hdev))
>>>>>> + return -ENODEV;
>>>>>> +
>>>>>> + udev = hid_to_usb_dev(hdev);
>>>>>> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
>>>>>> + return -EINVAL;
>>>>>> +
>>>>>> + ret = hid_parse(hdev);
>>>>>> + if (ret) {
>>>>>> + hid_err(hdev, "parse failed %d\n", ret);
>>>>>> + return ret;
>>>>>> + }
>>>>>> +
>>>>>> + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
>>>>>> + if (ret) {
>>>>>> + hid_err(hdev, "hw_start failed %d\n", ret);
>>>>>> + return ret;
>>>>>> + }
>>>>>> +
>>>>>> + ret = hid_hw_open(hdev);
>>>>>> + if (ret) {
>>>>>> + hid_err(hdev, "hw_open failed %d\n", ret);
>>>>>> + goto err_stop;
>>>>>> + }
>>>>>
>>>>> For the Switch 1 controllers we are calling hid_device_io_start after
>>>>> hid_hw_open. Is this not necessary in this case?
>>>>
>>>> Since we don't do any HID I/O during probe it's not needed; the
>>>> startup configuration is on the cfg interface instead.
>>>
>>> Ah, I can see that for the Switch 1 controllers we are sending USB
>>> HID packets during probe, while we don't do this for the Switch 2
>>> controllers. That explains why this call is not needed here.
>>>
>>>>
>>>>>
>>>>>> +
>>>>>> + ns2 = switch2_get_controller(phys);
>>>>>> + if (!ns2) {
>>>>>
>>>>> switch2_get_controller returns an err pointer in case of ENOMEM, not
>>>>> NULL so I think this check has to be changed.
>>>>
>>>> Fixed locally, thanks. I'll send that out with v4 (this was actually
>>>> v3 but I thought it was v2, oops).
>>>>
>>>>>
>>>>>> + ret = -ENOMEM;
>>>>>> + goto err_close;
>>>>>> + }
>>>>>> +
>>>>>> + guard(mutex)(&ns2->lock);
>>>>>> + WARN_ON(ns2->hdev);
>>>>>> + ns2->hdev = hdev;
>>>>>> + switch (hdev->product | (hdev->vendor << 16)) {
>>>>>> + default:
>>>>>> + strscpy(ns2->name, hdev->name, sizeof(ns2->name));
>>>>>> + break;
>>>>>> + /* Some controllers have slightly wrong names so we override them */
>>>>>> + case USB_DEVICE_ID_NINTENDO_NS2_JOYCONR | (USB_VENDOR_ID_NINTENDO << 16):
>>>>>> + /* Missing the "2" in the name */
>>>>>> + strscpy(ns2->name, "Nintendo Joy-Con 2 (R)", sizeof(ns2->name));
>>>>>> + break;
>>>>>> + case USB_DEVICE_ID_NINTENDO_NS2_GCCON | (USB_VENDOR_ID_NINTENDO << 16):
>>>>>> + /* Has "Nintendo" in the name twice */
>>>>>> + strscpy(ns2->name, "Nintendo GameCube Controller", sizeof(ns2->name));
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + ns2->player_id = U32_MAX;
>>>>>> + ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
>>>>>> + if (ret < 0)
>>>>>> + hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
>>>>>> + else
>>>>>> + ns2->player_id = ret;
>>>>>> +
>>>>>> + switch2_leds_create(ns2);
>>>>>> +
>>>>>> + hid_set_drvdata(hdev, ns2);
>>>>>> +
>>>>>> + if (ns2->cfg)
>>>>>> + return switch2_init_controller(ns2);
>>>>>> +
>>>>>> + return 0;
>>>>>> +
>>>>>> +err_close:
>>>>>> + hid_hw_close(hdev);
>>>>>> +err_stop:
>>>>>> + hid_hw_stop(hdev);
>>>>>> +
>>>>>> + return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_remove(struct hid_device *hdev)
>>>>>> +{
>>>>>> + struct switch2_controller *ns2 = hid_get_drvdata(hdev);
>>>>>> +
>>>>>> + hid_hw_close(hdev);
>>>>>> + mutex_lock(&ns2->lock);
>>>>>> + WARN_ON(ns2->hdev != hdev);
>>>>>> + ns2->hdev = NULL;
>>>>>> + mutex_unlock(&ns2->lock);
>>>>>> + ida_free(&nintendo_player_id_allocator, ns2->player_id);
>>>>>> + switch2_controller_put(ns2);
>>>>>> + hid_hw_stop(hdev);
>>>>>> +}
>>>>>> +
>>>>>> static const struct hid_device_id nintendo_hid_devices[] = {
>>>>>> + /* Switch devices */
>>>>>> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> USB_DEVICE_ID_NINTENDO_PROCON) },
>>>>>> { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> @@ -2813,10 +3935,69 @@ static const struct hid_device_id nintendo_hid_devices[] = {
>>>>>> USB_DEVICE_ID_NINTENDO_GENCON) },
>>>>>> { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> USB_DEVICE_ID_NINTENDO_N64CON) },
>>>>>> + /* Switch 2 devices */
>>>>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
>>>>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> + USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
>>>>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> + USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
>>>>>> + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
>>>>>> + USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
>>>>>> { }
>>>>>> };
>>>>>> MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
>>>>>>
>>>>>> +static bool nintendo_is_switch2(struct hid_device *hdev)
>>>>>> +{
>>>>>> + return hdev->vendor == USB_VENDOR_ID_NINTENDO &&
>>>>>> + hdev->product >= USB_DEVICE_ID_NINTENDO_NS2_JOYCONR;
>>>>>> +}
>>>>>> +
>>>>>> +static void nintendo_hid_remove(struct hid_device *hdev)
>>>>>> +{
>>>>>> + if (nintendo_is_switch2(hdev))
>>>>>> + switch2_remove(hdev);
>>>>>> + else
>>>>>> + joycon_remove(hdev);
>>>>>> +}
>>>>>> +
>>>>>> +static int nintendo_hid_event(struct hid_device *hdev,
>>>>>> + struct hid_report *report, u8 *raw_data, int size)
>>>>>> +{
>>>>>> + if (nintendo_is_switch2(hdev))
>>>>>> + return switch2_event(hdev, report, raw_data, size);
>>>>>> + else
>>>>>> + return joycon_event(hdev, report, raw_data, size);
>>>>>> +}
>>>>>> +
>>>>>> +static int nintendo_hid_probe(struct hid_device *hdev,
>>>>>> + const struct hid_device_id *id)
>>>>>> +{
>>>>>> + if (nintendo_is_switch2(hdev))
>>>>>> + return switch2_probe(hdev, id);
>>>>>> + else
>>>>>> + return joycon_probe(hdev, id);
>>>>>> +}
>>>>>> +
>>>>>> +#ifdef CONFIG_PM
>>>>>> +static int nintendo_hid_resume(struct hid_device *hdev)
>>>>>> +{
>>>>>> + if (nintendo_is_switch2(hdev))
>>>>>> + return 0;
>>>>>> + else
>>>>>> + return joycon_resume(hdev);
>>>>>> +}
>>>>>> +
>>>>>> +static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>>>>>> +{
>>>>>> + if (nintendo_is_switch2(hdev))
>>>>>> + return 0;
>>>>>> + else
>>>>>> + return joycon_suspend(hdev, message);
>>>>>> +}
>>>>>> +#endif
>>>>>> +
>>>>>> static struct hid_driver nintendo_hid_driver = {
>>>>>> .name = "nintendo",
>>>>>> .id_table = nintendo_hid_devices,
>>>>>> @@ -2844,4 +4025,5 @@ MODULE_LICENSE("GPL");
>>>>>> MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
>>>>>> MODULE_AUTHOR("Emily Strickland <linux@emily.st>");
>>>>>> MODULE_AUTHOR("Daniel J. Ogorchock <djogorchock@gmail.com>");
>>>>>> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
>>>>>> MODULE_DESCRIPTION("Driver for Nintendo Switch Controllers");
>>>>>> diff --git a/drivers/hid/hid-nintendo.h b/drivers/hid/hid-nintendo.h
>>>>>> new file mode 100644
>>>>>> index 0000000000000..7aff22f302661
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/hid/hid-nintendo.h
>>>>>> @@ -0,0 +1,72 @@
>>>>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>>>>> +/*
>>>>>> + * HID driver for Nintendo Switch 2 controllers
>>>>>> + *
>>>>>> + * Copyright (c) 2025 Valve Software
>>>>>> + *
>>>>>> + * This driver is based on the following work:
>>>>>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
>>>>>> + * https://github.com/ndeadly/switch2_controller_research
>>>>>> + */
>>>>>> +
>>>>>> +#ifndef __HID_NINTENDO_H
>>>>>> +#define __HID_NINTENDO_H
>>>>>> +
>>>>>> +#include <linux/bits.h>
>>>>>> +
>>>>>> +#define NS2_FLAG_OK BIT(0)
>>>>>> +#define NS2_FLAG_NACK BIT(2)
>>>>>> +
>>>>>> +enum switch2_cmd {
>>>>>> + NS2_CMD_NFC = 0x01,
>>>>>> + NS2_CMD_FLASH = 0x02,
>>>>>> + NS2_CMD_INIT = 0x03,
>>>>>> + NS2_CMD_GRIP = 0x08,
>>>>>> + NS2_CMD_LED = 0x09,
>>>>>> + NS2_CMD_VIBRATE = 0x0a,
>>>>>> + NS2_CMD_BATTERY = 0x0b,
>>>>>> + NS2_CMD_FEATSEL = 0x0c,
>>>>>> + NS2_CMD_FW_UPD = 0x0d,
>>>>>> + NS2_CMD_FW_INFO = 0x10,
>>>>>> + NS2_CMD_BT_PAIR = 0x15,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_direction {
>>>>>> + NS2_DIR_IN = 0x00,
>>>>>> + NS2_DIR_OUT = 0x90,
>>>>>> +};
>>>>>> +
>>>>>> +enum switch2_transport {
>>>>>> + NS2_TRANS_USB = 0x00,
>>>>>> + NS2_TRANS_BT = 0x01,
>>>>>> +};
>>>>>> +
>>>>>> +struct switch2_cmd_header {
>>>>>> + uint8_t command;
>>>>>> + uint8_t flags;
>>>>>> + uint8_t transport;
>>>>>> + uint8_t subcommand;
>>>>>> + uint8_t unk1;
>>>>>> + uint8_t length;
>>>>>> + uint16_t unk2;
>>>>>> +};
>>>>>> +static_assert(sizeof(struct switch2_cmd_header) == 8);
>>>>>> +
>>>>>> +struct device;
>>>>>> +struct switch2_controller;
>>>>>> +struct switch2_cfg_intf {
>>>>>> + struct switch2_controller *parent;
>>>>>> + struct device *dev;
>>>>>> +
>>>>>> + int (*send_command)(enum switch2_cmd command, uint8_t subcommand,
>>>>>> + const void *message, size_t length,
>>>>>> + struct switch2_cfg_intf *intf);
>>>>>> +};
>>>>>> +
>>>>>> +int switch2_controller_attach_cfg(const char *phys, struct switch2_cfg_intf *cfg);
>>>>>> +void switch2_controller_detach_cfg(struct switch2_controller *controller);
>>>>>> +
>>>>>> +int switch2_receive_command(struct switch2_controller *controller,
>>>>>> + const uint8_t *message, size_t length);
>>>>>> +
>>>>>> +#endif
>>>>>> diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
>>>>>> index 7755e5b454d2c..868262c6ccd9a 100644
>>>>>> --- a/drivers/input/joystick/Kconfig
>>>>>> +++ b/drivers/input/joystick/Kconfig
>>>>>> @@ -422,4 +422,15 @@ config JOYSTICK_SEESAW
>>>>>> To compile this driver as a module, choose M here: the module will be
>>>>>> called adafruit-seesaw.
>>>>>>
>>>>>> +config JOYSTICK_NINTENDO_SWITCH2_USB
>>>>>> + tristate "Wired Nintendo Switch 2 controller support"
>>>>>> + depends on HID_NINTENDO
>>>>>> + depends on USB
>>>>>> + help
>>>>>> + Say Y here if you want to enable support for wired Nintendo Switch 2
>>>>>> + controllers.
>>>>>> +
>>>>>> + To compile this driver as a module, choose M here: the
>>>>>> + module will be called nintendo-switch2-usb.
>>>>>> +
>>>>>> endif
>>>>>> diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
>>>>>> index 9976f596a9208..8f92900ae8856 100644
>>>>>> --- a/drivers/input/joystick/Makefile
>>>>>> +++ b/drivers/input/joystick/Makefile
>>>>>> @@ -34,6 +34,7 @@ obj-$(CONFIG_JOYSTICK_SIDEWINDER) += sidewinder.o
>>>>>> obj-$(CONFIG_JOYSTICK_SPACEBALL) += spaceball.o
>>>>>> obj-$(CONFIG_JOYSTICK_SPACEORB) += spaceorb.o
>>>>>> obj-$(CONFIG_JOYSTICK_STINGER) += stinger.o
>>>>>> +obj-$(CONFIG_JOYSTICK_NINTENDO_SWITCH2_USB) += nintendo-switch2-usb.o
>>>>>> obj-$(CONFIG_JOYSTICK_TMDC) += tmdc.o
>>>>>> obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
>>>>>> obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
>>>>>> diff --git a/drivers/input/joystick/nintendo-switch2-usb.c b/drivers/input/joystick/nintendo-switch2-usb.c
>>>>>> new file mode 100644
>>>>>> index 0000000000000..ebd89d852e21a
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/input/joystick/nintendo-switch2-usb.c
>>>>>> @@ -0,0 +1,353 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>>>> +/*
>>>>>> + * USB driver for Nintendo Switch 2 controllers configuration interface
>>>>>> + *
>>>>>> + * Copyright (c) 2025 Valve Software
>>>>>> + *
>>>>>> + * This driver is based on the following work:
>>>>>> + * https://gist.github.com/shinyquagsire23/66f006b46c56216acbaac6c1e2279b64
>>>>>> + * https://github.com/ndeadly/switch2_controller_research
>>>>>> + */
>>>>>> +
>>>>>> +#include "../../hid/hid-ids.h"
>>>>>> +#include "../../hid/hid-nintendo.h"
>>>>>> +#include <linux/module.h>
>>>>>> +#include <linux/usb/input.h>
>>>>>> +
>>>>>> +#define NS2_BULK_SIZE 64
>>>>>> +#define NS2_IN_URBS 2
>>>>>> +#define NS2_OUT_URBS 4
>>>>>> +
>>>>>> +static struct usb_driver switch2_usb;
>>>>>> +
>>>>>> +struct switch2_urb {
>>>>>> + struct urb *urb;
>>>>>> + uint8_t *data;
>>>>>> + bool active;
>>>>>> +};
>>>>>> +
>>>>>> +struct switch2_usb {
>>>>>> + struct switch2_cfg_intf cfg;
>>>>>> + struct usb_device *udev;
>>>>>> +
>>>>>> + struct switch2_urb bulk_in[NS2_IN_URBS];
>>>>>> + struct usb_anchor bulk_in_anchor;
>>>>>> + spinlock_t bulk_in_lock;
>>>>>> +
>>>>>> + struct switch2_urb bulk_out[NS2_OUT_URBS];
>>>>>> + struct usb_anchor bulk_out_anchor;
>>>>>> + spinlock_t bulk_out_lock;
>>>>>> +
>>>>>> + int message_in;
>>>>>> + struct work_struct message_in_work;
>>>>>> +};
>>>>>> +
>>>>>> +static void switch2_bulk_in(struct urb *urb)
>>>>>> +{
>>>>>> + struct switch2_usb *ns2_usb = urb->context;
>>>>>> + int i;
>>>>>> + bool schedule = false;
>>>>>> + unsigned long flags;
>>>>>> +
>>>>>> + switch (urb->status) {
>>>>>> + case 0:
>>>>>> + schedule = true;
>>>>>> + break;
>>>>>> + case -ECONNRESET:
>>>>>> + case -ENOENT:
>>>>>> + case -ESHUTDOWN:
>>>>>> + dev_dbg(&ns2_usb->udev->dev, "shutting down input urb: %d\n", urb->status);
>>>>>> + return;
>>>>>> + default:
>>>>>> + dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
>>>>>> + break;
>>>>>> + }
>>>>>> +
>>>>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>>>>>> + for (i = 0; i < NS2_IN_URBS; i++) {
>>>>>> + int err;
>>>>>> + struct switch2_urb *ns2_urb;
>>>>>> +
>>>>>> + if (ns2_usb->bulk_in[i].urb == urb) {
>>>>>> + ns2_usb->message_in = i;
>>>>>> + continue;
>>>>>> + }
>>>>>> +
>>>>>> + if (ns2_usb->bulk_in[i].active)
>>>>>> + continue;
>>>>>> +
>>>>>> + ns2_urb = &ns2_usb->bulk_in[i];
>>>>>> + usb_anchor_urb(ns2_urb->urb, &ns2_usb->bulk_in_anchor);
>>>>>> + err = usb_submit_urb(ns2_urb->urb, GFP_ATOMIC);
>>>>>> + if (err) {
>>>>>> + usb_unanchor_urb(ns2_urb->urb);
>>>>>> + dev_dbg(&ns2_usb->udev->dev, "failed to queue input urb: %d\n", err);
>>>>>> + } else {
>>>>>> + ns2_urb->active = true;
>>>>>> + }
>>>>>> + }
>>>>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>>>>>> +
>>>>>> + if (schedule)
>>>>>> + schedule_work(&ns2_usb->message_in_work);
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_bulk_out(struct urb *urb)
>>>>>> +{
>>>>>> + struct switch2_usb *ns2_usb = urb->context;
>>>>>> + int i;
>>>>>> +
>>>>>> + guard(spinlock_irqsave)(&ns2_usb->bulk_out_lock);
>>>>>> +
>>>>>> + switch (urb->status) {
>>>>>> + case 0:
>>>>>> + break;
>>>>>> + case -ECONNRESET:
>>>>>> + case -ENOENT:
>>>>>> + case -ESHUTDOWN:
>>>>>> + dev_dbg(&ns2_usb->udev->dev, "shutting down output urb: %d\n", urb->status);
>>>>>> + return;
>>>>>> + default:
>>>>>> + dev_dbg(&ns2_usb->udev->dev, "unknown output urb status: %d\n", urb->status);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>>>>>> + if (ns2_usb->bulk_out[i].urb != urb)
>>>>>> + continue;
>>>>>> +
>>>>>> + ns2_usb->bulk_out[i].active = false;
>>>>>> + break;
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_usb_send_cmd(enum switch2_cmd command, uint8_t subcommand,
>>>>>> + const void *message, size_t size, struct switch2_cfg_intf *cfg)
>>>>>> +{
>>>>>> + struct switch2_usb *ns2_usb = (struct switch2_usb *)cfg;
>>>>>> + struct switch2_urb *urb = NULL;
>>>>>> + int i;
>>>>>> + int ret;
>>>>>> + unsigned long flags;
>>>>>> +
>>>>>> + struct switch2_cmd_header header = {
>>>>>> + command, NS2_DIR_OUT | NS2_FLAG_OK, NS2_TRANS_USB, subcommand, 0, size
>>>>>> + };
>>>>>> +
>>>>>> + if (WARN_ON(size > 56))
>>>>>> + return -EINVAL;
>>>>>> +
>>>>>> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
>>>>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>>>>>> + if (ns2_usb->bulk_out[i].active)
>>>>>> + continue;
>>>>>> +
>>>>>> + urb = &ns2_usb->bulk_out[i];
>>>>>> + urb->active = true;
>>>>>> + break;
>>>>>> + }
>>>>>> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
>>>>>> +
>>>>>> + if (!urb) {
>>>>>> + dev_warn(&ns2_usb->udev->dev, "output queue full, dropping message\n");
>>>>>> + return -ENOBUFS;
>>>>>> + }
>>>>>> +
>>>>>> + memcpy(urb->data, &header, sizeof(header));
>>>>>> + if (message && size)
>>>>>> + memcpy(&urb->data[8], message, size);
>>>>>> + urb->urb->transfer_buffer_length = size + sizeof(header);
>>>>>> +
>>>>>> + print_hex_dump_debug("sending cmd: ", DUMP_PREFIX_OFFSET, 16, 1, urb->data,
>>>>>> + size + sizeof(header), false);
>>>>>> +
>>>>>> + usb_anchor_urb(urb->urb, &ns2_usb->bulk_out_anchor);
>>>>>> + ret = usb_submit_urb(urb->urb, GFP_ATOMIC);
>>>>>> + if (ret) {
>>>>>> + if (ret != -ENODEV)
>>>>>> + dev_warn(&ns2_usb->udev->dev, "failed to submit output urb: %i", ret);
>>>>>> + urb->active = false;
>>>>>> + usb_unanchor_urb(urb->urb);
>>>>>> + return ret;
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_usb_message_in_work(struct work_struct *work)
>>>>>> +{
>>>>>> + struct switch2_usb *ns2_usb = container_of(work, struct switch2_usb, message_in_work);
>>>>>> + struct switch2_urb *urb;
>>>>>> + int err;
>>>>>> + unsigned long flags;
>>>>>> +
>>>>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>>>>>> + urb = &ns2_usb->bulk_in[ns2_usb->message_in];
>>>>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>>>>>> +
>>>>>> + err = switch2_receive_command(ns2_usb->cfg.parent, urb->urb->transfer_buffer,
>>>>>> + urb->urb->actual_length);
>>>>>> + if (err)
>>>>>> + dev_dbg(&ns2_usb->udev->dev, "receive command failed: %d\n", err);
>>>>>> +
>>>>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>>>>>> + urb->active = false;
>>>>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>>>>>> +}
>>>>>> +
>>>>>> +static int switch2_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
>>>>>> +{
>>>>>> + struct switch2_usb *ns2_usb;
>>>>>> + struct usb_device *udev;
>>>>>> + struct usb_endpoint_descriptor *bulk_in, *bulk_out;
>>>>>> + char phys[64];
>>>>>> + int ret;
>>>>>> + int i;
>>>>>> +
>>>>>> + udev = interface_to_usbdev(intf);
>>>>>> + if (usb_make_path(udev, phys, sizeof(phys)) < 0)
>>>>>> + return -EINVAL;
>>>>>> +
>>>>>> + ret = usb_find_common_endpoints(intf->cur_altsetting, &bulk_in, &bulk_out, NULL, NULL);
>>>>>> + if (ret) {
>>>>>> + dev_err(&intf->dev, "failed to find bulk EPs\n");
>>>>>> + return ret;
>>>>>> + }
>>>>>> +
>>>>>> + ns2_usb = devm_kzalloc(&intf->dev, sizeof(*ns2_usb), GFP_KERNEL);
>>>>>> + if (!ns2_usb)
>>>>>> + return -ENOMEM;
>>>>>> +
>>>>>> + ns2_usb->udev = udev;
>>>>>> + for (i = 0; i < NS2_IN_URBS; i++) {
>>>>>> + ns2_usb->bulk_in[i].urb = usb_alloc_urb(0, GFP_KERNEL);
>>>>>> + if (!ns2_usb->bulk_in[i].urb) {
>>>>>> + ret = -ENOMEM;
>>>>>> + goto err_free_in;
>>>>>> + }
>>>>>> +
>>>>>> + ns2_usb->bulk_in[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
>>>>>> + &ns2_usb->bulk_in[i].urb->transfer_dma);
>>>>>> + if (!ns2_usb->bulk_in[i].data) {
>>>>>> + ret = -ENOMEM;
>>>>>> + goto err_free_in;
>>>>>> + }
>>>>>> +
>>>>>> + usb_fill_bulk_urb(ns2_usb->bulk_in[i].urb, udev,
>>>>>> + usb_rcvbulkpipe(udev, bulk_in->bEndpointAddress),
>>>>>> + ns2_usb->bulk_in[i].data, NS2_BULK_SIZE, switch2_bulk_in, ns2_usb);
>>>>>> + ns2_usb->bulk_in[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
>>>>>> + }
>>>>>> +
>>>>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>>>>>> + ns2_usb->bulk_out[i].urb = usb_alloc_urb(0, GFP_KERNEL);
>>>>>> + if (!ns2_usb->bulk_out[i].urb) {
>>>>>> + ret = -ENOMEM;
>>>>>> + goto err_free_out;
>>>>>> + }
>>>>>> +
>>>>>> + ns2_usb->bulk_out[i].data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
>>>>>> + &ns2_usb->bulk_out[i].urb->transfer_dma);
>>>>>> + if (!ns2_usb->bulk_out[i].data) {
>>>>>> + ret = -ENOMEM;
>>>>>> + goto err_free_out;
>>>>>> + }
>>>>>> +
>>>>>> + usb_fill_bulk_urb(ns2_usb->bulk_out[i].urb, udev,
>>>>>> + usb_sndbulkpipe(udev, bulk_out->bEndpointAddress),
>>>>>> + ns2_usb->bulk_out[i].data, NS2_BULK_SIZE, switch2_bulk_out, ns2_usb);
>>>>>> + ns2_usb->bulk_out[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
>>>>>> + }
>>>>>> +
>>>>>> + ns2_usb->bulk_in[0].active = true;
>>>>>> + ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
>>>>>> + if (ret < 0)
>>>>>> + goto err_free_out;
>>>>>> +
>>>>>> + init_usb_anchor(&ns2_usb->bulk_out_anchor);
>>>>>> + spin_lock_init(&ns2_usb->bulk_out_lock);
>>>>>> + init_usb_anchor(&ns2_usb->bulk_in_anchor);
>>>>>> + spin_lock_init(&ns2_usb->bulk_in_lock);
>>>>>> + INIT_WORK(&ns2_usb->message_in_work, switch2_usb_message_in_work);
>>>>>> +
>>>>>> + usb_set_intfdata(intf, ns2_usb);
>>>>>> +
>>>>>> + ns2_usb->cfg.dev = &ns2_usb->udev->dev;
>>>>>> + ns2_usb->cfg.send_command = switch2_usb_send_cmd;
>>>>>> +
>>>>>> + ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
>>>>>> + if (ret < 0)
>>>>>> + goto err_kill_urb;
>>>>>> +
>>>>>> + return 0;
>>>>>> +
>>>>>> +err_kill_urb:
>>>>>> + usb_kill_urb(ns2_usb->bulk_in[0].urb);
>>>>>> +err_free_out:
>>>>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>>>>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
>>>>>> + ns2_usb->bulk_out[i].urb->transfer_dma);
>>>>>> + usb_free_urb(ns2_usb->bulk_out[i].urb);
>>>>>> + }
>>>>>> +err_free_in:
>>>>>> + for (i = 0; i < NS2_IN_URBS; i++) {
>>>>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
>>>>>> + ns2_usb->bulk_in[i].urb->transfer_dma);
>>>>>> + usb_free_urb(ns2_usb->bulk_in[i].urb);
>>>>>> + }
>>>>>> + devm_kfree(&intf->dev, ns2_usb);
>>>>>> +
>>>>>> + return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void switch2_usb_disconnect(struct usb_interface *intf)
>>>>>> +{
>>>>>> + struct switch2_usb *ns2_usb = usb_get_intfdata(intf);
>>>>>> + unsigned long flags;
>>>>>> + int i;
>>>>>> +
>>>>>> + spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
>>>>>> + usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
>>>>>> + for (i = 0; i < NS2_OUT_URBS; i++) {
>>>>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_out[i].data,
>>>>>> + ns2_usb->bulk_out[i].urb->transfer_dma);
>>>>>> + usb_free_urb(ns2_usb->bulk_out[i].urb);
>>>>>> + }
>>>>>> + spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
>>>>>> +
>>>>>> + spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
>>>>>> + usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
>>>>>> + cancel_work_sync(&ns2_usb->message_in_work);
>>>>>> + for (i = 0; i < NS2_IN_URBS; i++) {
>>>>>> + usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, ns2_usb->bulk_in[i].data,
>>>>>> + ns2_usb->bulk_in[i].urb->transfer_dma);
>>>>>> + usb_free_urb(ns2_usb->bulk_in[i].urb);
>>>>>> + }
>>>>>> + spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
>>>>>> +
>>>>>> + switch2_controller_detach_cfg(ns2_usb->cfg.parent);
>>>>>
>>>>> As we have allocated ns2_usb with devm_kzalloc, don't we need to free it
>>>>> with devm_kfree again?
>>>>
>>>> Devres will automatically free it when the device is freed. As this is
>>>> the callback for the device going away, it's freed directly after this
>>>> function exits. That's why I'm using devm_kzalloc instead of kzalloc.
>>>
>>> Makes sense!
>>>
>>>
>>> Thanks for helping me to understand the code better!
>>>
>>> Cheers,
>>> Silvan
>>>
>>>>
>>>>>
>>>>> Cheers,
>>>>> Silvan
>>>>>
>>>>>> +}
>>>>>> +
>>>>>> +#define SWITCH2_CONTROLLER(vend, prod) \
>>>>>> + USB_DEVICE_AND_INTERFACE_INFO(vend, prod, USB_CLASS_VENDOR_SPEC, 0, 0)
>>>>>> +
>>>>>> +static const struct usb_device_id switch2_usb_devices[] = {
>>>>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONL) },
>>>>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_JOYCONR) },
>>>>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_PROCON) },
>>>>>> + { SWITCH2_CONTROLLER(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_NS2_GCCON) },
>>>>>> + { }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(usb, switch2_usb_devices);
>>>>>> +
>>>>>> +static struct usb_driver switch2_usb = {
>>>>>> + .name = "switch2",
>>>>>> + .id_table = switch2_usb_devices,
>>>>>> + .probe = switch2_usb_probe,
>>>>>> + .disconnect = switch2_usb_disconnect,
>>>>>> +};
>>>>>> +module_usb_driver(switch2_usb);
>>>>>> +
>>>>>> +MODULE_LICENSE("GPL");
>>>>>> +MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
>>>>>> +MODULE_DESCRIPTION("Driver for Nintendo Switch 2 Controllers");
>>>>>
>>>>>
>>>>
>>>> Vicki
>>>
>>>
>>
>> Unless anyone else chimes in with more suggestions I'll probably submit
>> v4 sometime next week with these two changes.
>
> Sounds good to me, thanks!
>
> Cheers,
> Silvan
>
>>
>> Vicki
Vicki
^ permalink raw reply
* [dtor-input:for-linus] BUILD SUCCESS ff14dafde15c11403fac61367a34fea08926e9ee
From: kernel test robot @ 2026-04-10 22:35 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: ff14dafde15c11403fac61367a34fea08926e9ee Input: uinput - take event lock when submitting FF request "event"
elapsed time: 997m
configs tested: 198
configs skipped: 3
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001-20260410 gcc-8.5.0
arc randconfig-001-20260411 gcc-12.5.0
arc randconfig-002-20260410 gcc-8.5.0
arc randconfig-002-20260411 gcc-12.5.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm randconfig-001-20260410 gcc-8.5.0
arm randconfig-001-20260411 gcc-12.5.0
arm randconfig-002-20260410 gcc-8.5.0
arm randconfig-002-20260411 gcc-12.5.0
arm randconfig-003-20260410 gcc-8.5.0
arm randconfig-003-20260411 gcc-12.5.0
arm randconfig-004-20260410 gcc-8.5.0
arm randconfig-004-20260411 gcc-12.5.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260410 gcc-10.5.0
arm64 randconfig-001-20260411 clang-23
arm64 randconfig-002-20260410 gcc-10.5.0
arm64 randconfig-002-20260411 clang-23
arm64 randconfig-003-20260410 gcc-10.5.0
arm64 randconfig-003-20260411 clang-23
arm64 randconfig-004-20260410 gcc-10.5.0
arm64 randconfig-004-20260411 clang-23
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260410 gcc-10.5.0
csky randconfig-001-20260411 clang-23
csky randconfig-002-20260410 gcc-10.5.0
csky randconfig-002-20260411 clang-23
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260410 gcc-15.2.0
hexagon randconfig-002-20260410 gcc-15.2.0
i386 allmodconfig clang-20
i386 allmodconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260410 clang-20
i386 buildonly-randconfig-002-20260410 clang-20
i386 buildonly-randconfig-003-20260410 clang-20
i386 buildonly-randconfig-004-20260410 clang-20
i386 buildonly-randconfig-005-20260410 clang-20
i386 buildonly-randconfig-006-20260410 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260410 gcc-14
i386 randconfig-002-20260410 gcc-14
i386 randconfig-003-20260410 gcc-14
i386 randconfig-004-20260410 gcc-14
i386 randconfig-005-20260410 gcc-14
i386 randconfig-006-20260410 gcc-14
i386 randconfig-007-20260410 gcc-14
i386 randconfig-011-20260410 clang-20
i386 randconfig-012-20260410 clang-20
i386 randconfig-013-20260410 clang-20
i386 randconfig-014-20260410 clang-20
i386 randconfig-015-20260410 clang-20
i386 randconfig-016-20260410 clang-20
i386 randconfig-017-20260410 clang-20
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260410 gcc-15.2.0
loongarch randconfig-002-20260410 gcc-15.2.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips vocore2_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260410 gcc-15.2.0
nios2 randconfig-002-20260410 gcc-15.2.0
openrisc allmodconfig clang-23
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260410 gcc-14.3.0
parisc randconfig-002-20260410 gcc-14.3.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc randconfig-001-20260410 gcc-14.3.0
powerpc randconfig-002-20260410 gcc-14.3.0
powerpc64 randconfig-001-20260410 gcc-14.3.0
powerpc64 randconfig-002-20260410 gcc-14.3.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260410 clang-23
sparc randconfig-001-20260411 gcc-14
sparc randconfig-002-20260410 clang-23
sparc randconfig-002-20260411 gcc-14
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260410 clang-23
sparc64 randconfig-001-20260411 gcc-14
sparc64 randconfig-002-20260410 clang-23
sparc64 randconfig-002-20260411 gcc-14
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260410 clang-23
um randconfig-001-20260411 gcc-14
um randconfig-002-20260410 clang-23
um randconfig-002-20260411 gcc-14
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260410 clang-20
x86_64 buildonly-randconfig-002-20260410 clang-20
x86_64 buildonly-randconfig-003-20260410 clang-20
x86_64 buildonly-randconfig-004-20260410 clang-20
x86_64 buildonly-randconfig-005-20260410 clang-20
x86_64 buildonly-randconfig-006-20260410 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260410 clang-20
x86_64 randconfig-002-20260410 clang-20
x86_64 randconfig-003-20260410 clang-20
x86_64 randconfig-004-20260410 clang-20
x86_64 randconfig-005-20260410 clang-20
x86_64 randconfig-006-20260410 clang-20
x86_64 randconfig-011-20260410 gcc-14
x86_64 randconfig-012-20260410 gcc-14
x86_64 randconfig-013-20260410 gcc-14
x86_64 randconfig-014-20260410 gcc-14
x86_64 randconfig-015-20260410 gcc-14
x86_64 randconfig-016-20260410 gcc-14
x86_64 randconfig-071-20260410 clang-20
x86_64 randconfig-072-20260410 clang-20
x86_64 randconfig-073-20260410 clang-20
x86_64 randconfig-074-20260410 clang-20
x86_64 randconfig-075-20260410 clang-20
x86_64 randconfig-076-20260410 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa allyesconfig gcc-15.2.0
xtensa randconfig-001-20260410 clang-23
xtensa randconfig-001-20260411 gcc-14
xtensa randconfig-002-20260410 clang-23
xtensa randconfig-002-20260411 gcc-14
xtensa smp_lx200_defconfig gcc-15.2.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] Input: edt-ft5x06 - fix use-after-free in debugfs teardown
From: Dmitry Torokhov @ 2026-04-11 4:13 UTC (permalink / raw)
To: linux-input; +Cc: Wolfram Sang, linux-kernel
The commit 68743c500c6e ("Input: edt-ft5x06 - use per-client debugfs
directory") removed the manual debugfs teardown, relying on the I2C core
to handle it. However, this creates a window where debugfs files are
still accessible after edt_ft5x06_ts_teardown_debugfs() frees
tsdata->raw_buffer.
To prevent a use-after-free, protect the freeing of raw_buffer with the
device mutex and set raw_buffer to NULL. The debugfs read function
already checks if raw_buffer is NULL under the same mutex, so this
safely avoids the use-after-free.
Fixes: 68743c500c6e ("Input: edt-ft5x06 - use per-client debugfs directory")
Cc: stable@vger.kernel.org
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/touchscreen/edt-ft5x06.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index ba8ff65f7ea6..d3b1177185a3 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -804,7 +804,10 @@ static void edt_ft5x06_ts_prepare_debugfs(struct edt_ft5x06_ts_data *tsdata)
static void edt_ft5x06_ts_teardown_debugfs(struct edt_ft5x06_ts_data *tsdata)
{
+ guard(mutex)(&tsdata->mutex);
+
kfree(tsdata->raw_buffer);
+ tsdata->raw_buffer = NULL;
}
#else
--
2.54.0.rc0.605.g598a273b03-goog
--
Dmitry
^ permalink raw reply related
* [git pull] Input updates for v7.0-rc7
From: Dmitry Torokhov @ 2026-04-11 4:23 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, linux-input
Hi Linus,
Please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.0-rc7
to receive updates for the input subsystem. You will get 2 fixes for
force feedback handling in uinput driver:
- a fix for circular locking dependency in uinput
- a fix for potential corruption of uinput event queue
Changelog:
---------
Dmitry Torokhov (1):
Input: uinput - take event lock when submitting FF request "event"
Mikhail Gavrilov (1):
Input: uinput - fix circular locking dependency with ff-core
Diffstat:
--------
drivers/input/misc/uinput.c | 35 ++++++++++++++++++++++++++++-------
1 file changed, 28 insertions(+), 7 deletions(-)
Thanks.
--
Dmitry
^ permalink raw reply
* [PATCH v2] HID: ft260: validate i2c input report length
From: Michael Zaidman @ 2026-04-11 6:24 UTC (permalink / raw)
To: jikos, benjamin.tissoires
Cc: linux-input, linux-kernel, sebasjosue84, Michael Zaidman
In-Reply-To: <CAPnwWgPhb+owa69-pTADpqk=KMWH71EUT6cxwCeT5KGnBWk+Xg@mail.gmail.com>
Add two checks to ft260_raw_event() to prevent out-of-bounds reads
from malicious or malfunctioning devices:
First, reject reports shorter than the 2-byte header (report ID +
length fields). Without this, even accessing xfer->length on a
1-byte report is an OOB read.
Second, validate xfer->length against the actual data capacity of
the received HID report. Each I2C data report ID (0xD0 through
0xDE) defines a different report size in the HID descriptor, so the
available payload varies per report. A corrupted length field could
cause memcpy to read beyond the report buffer.
Reported-by: Sebastián Josué Alba Vives <sebasjosue84@gmail.com>
Signed-off-by: Michael Zaidman <michael.zaidman@gmail.com>
---
Changes in v2:
- Add minimum report size check before accessing header fields to
prevent OOB read on truncated reports (size < 2)
Tested on FT260 with I2C-attached EEPROM (24c02) behind PCA9548
mux switches. Verified reads of various sizes (1-4 bytes using
report ID 0xD0, and larger reads using higher report IDs) with
debug tracing enabled, confirming xfer->length is correctly
validated against the HID report size for each report ID.
---
drivers/hid/hid-ft260.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 333341e80b0e..70e2eedb465a 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -1068,10 +1068,22 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
struct ft260_device *dev = hid_get_drvdata(hdev);
struct ft260_i2c_input_report *xfer = (void *)data;
+ if (size < offsetof(struct ft260_i2c_input_report, data)) {
+ hid_err(hdev, "short report %d\n", size);
+ return -1;
+ }
+
if (xfer->report >= FT260_I2C_REPORT_MIN &&
xfer->report <= FT260_I2C_REPORT_MAX) {
- ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
- xfer->length);
+ ft260_dbg("i2c resp: rep %#02x len %d size %d\n",
+ xfer->report, xfer->length, size);
+
+ if (xfer->length > size -
+ offsetof(struct ft260_i2c_input_report, data)) {
+ hid_err(hdev, "report %#02x: length %d exceeds HID report size\n",
+ xfer->report, xfer->length);
+ return -1;
+ }
if ((dev->read_buf == NULL) ||
(xfer->length > dev->read_len - dev->read_idx)) {
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v4 07/11] dt-bindings: input: touchscreen: st,stmfts: Introduce reset GPIO
From: Krzysztof Kozlowski @ 2026-04-11 14:01 UTC (permalink / raw)
To: David Heidelberg
Cc: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio, Petr Hodina, linux-input,
linux-stm32, linux-arm-kernel, linux-kernel, devicetree,
linux-arm-msm, phone-devel
In-Reply-To: <20260409-stmfts5-v4-7-64fe62027db5@ixit.cz>
On Thu, Apr 09, 2026 at 12:15:50AM +0200, David Heidelberg wrote:
> FTS has associated reset GPIO, document it.
>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml | 4 ++++
> 1 file changed, 4 insertions(+)
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v4 09/11] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
From: Krzysztof Kozlowski @ 2026-04-11 14:02 UTC (permalink / raw)
To: David Heidelberg
Cc: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio, Petr Hodina, linux-input,
linux-stm32, linux-arm-kernel, linux-kernel, devicetree,
linux-arm-msm, phone-devel
In-Reply-To: <20260409-stmfts5-v4-9-64fe62027db5@ixit.cz>
On Thu, Apr 09, 2026 at 12:15:52AM +0200, David Heidelberg wrote:
> Introduce more recent STM FTS5 touchscreen support.
>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> .../devicetree/bindings/input/touchscreen/st,stmfts.yaml | 15 ++++++++++++++-
> 1 file changed, 14 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> index 64c4f24ea3dd0..441fc92b9a4ed 100644
> --- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> +++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
> @@ -16,10 +16,19 @@ description:
>
> allOf:
> - $ref: touchscreen.yaml#
> + - if:
> + properties:
> + compatible:
> + const: st,stmfts5
> + then:
> + required:
> + - mode-switch-gpios
Does existing variant have these pins? If not, then missing else with
"mode-switch-gpios: false".
Please move entire allOf to the bottom, like in example-schema, so after
"required" block.
>
> properties:
> compatible:
> - const: st,stmfts
> + enum:
> + - st,stmfts
> + - st,stmfts5
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Silvan Jegen @ 2026-04-11 15:29 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <631510d1-2b9a-4288-a0ac-1c3d1253a0fa@endrift.com>
Vicki Pfau <vi@endrift.com> wrote:
> On 4/10/26 12:44, Silvan Jegen wrote:
> > Vicki Pfau <vi@endrift.com> wrote:
> >> Replies inline
> >>
> >> On 4/8/26 12:51, Silvan Jegen wrote:
> >>> Heyhey!
> >>>
> >>> Vicki Pfau <vi@endrift.com> wrote:
> >>>> Hi,
> >>>>
> >>>> Replies inline
> >>>>
> >>>> On 4/2/26 12:09 PM, Silvan Jegen wrote:
> >>>>> Hi
> >>>>>
> >>>>> Thanks for the patch!
> >>>>>
> >>>>> Just some comments and questions inline below.
> >>>>>
> >>>>> Vicki Pfau <vi@endrift.com> wrote:
> >>>>>>
> >>>>>> [...]
> >>>>>>
> >>>>>> +
> >>>>>> +static int switch2_set_report_format(struct switch2_controller *ns2, enum switch2_report_id fmt)
> >>>>>> +{
> >>>>>> + __le32 format_id = __cpu_to_le32(fmt);
> >>>>>> +
> >>>>>> + if (!ns2->cfg)
> >>>>>> + return -ENOTCONN;
> >>>>>> + return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_SELECT_REPORT,
> >>>>>> + &format_id, sizeof(format_id),
> >>>>>> + ns2->cfg);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int switch2_init_controller(struct switch2_controller *ns2)
> >>>>>
> >>>>> This is now a recursive call while in v1 it wasn't. I think I preferred
> >>>>> the non-recursive version as there was one place where init_step
> >>>>> state was changed while now I am not sure where it happens (and whether
> >>>>> there is a code path where we end up in an infinite recursion)
> >>>>>
> >>>>> What is the advantage of the recursive version compared to the
> >>>>> non-recursive one?
> >>> >
> >>>>
> >>>> The old version incremented the step regardless of whether or not it
> >>>> could confirm it had happened. Since the confirmation is now handled
> >>>> with an external step, calling into switch2_init_step_done, the loop
> >>>> condition would become somewhat complicated.
> >>>> I replaced it with explicit tail calls since that make the
> >>>> control flow simplier, and it is always matched with a call to
> >>>> switch2_init_step_done to ensure that the state is always advanced. As
> >>>
> >>> From what I can tell switch2_init_step_done currently only advances
> >>> the state if the current state is the expected one. This seems fine,
> >>> but it also means that if the state is not the expected one, the
> >>> state is not advanced and the recursive call continues anyway (in the
> >>> NS2_INIT_READ_USER_SECONDARY_CALIB case, for example). I assume this
> >>> should never happen but if we end up in this case for some reason we
> >>> will recurse forever.
> >>
> >> That's correct, and the only way it would happen forever is if there's a
> >> bug. The same would be true in a loop version if it doesn't advance the
> >> state properly either, fwiw, which happened during development of this
> >> version. Regardless, I can reduce the chance of introducing such a bug
> >> by passing ns2->init_step instead of a constant, so I'll make that
> >> change in v4.
> >>
> >>>
> >>> The same case also potentially calls switch2_read_flash and it isn't
> >>> clear to me if this means that the initialisation is done (as there
> >>> is no switch2_init_step_done call and we are not in the FINISH state
> >>> either). There is also the possibility of switch2_read_flash calling
> >>> switch2_init_controller again, which one then has to check ... (note
> >>> that this is not the case here though)
> >>
> >> switch2_handle_flash_read will advance the state once it's verified that
> >> the read actually happened. If the step failed for whatever reason, this
> >> same codepath will retry the specific read, as the caller
> >> (switch2_receive_command) will always call into switch2_init_controller
> >> if setup isn't done. This is how the retry logic works.
> >
> > Ah, so the call chain looks something like the below?
> >
> > switch2_read_flash->
> > switch2_usb_send_cmd->
> > switch2_usb_message_in_work (?)->
> > switch2_receive_command->
> > switch2_handle_flash_read->
> > switch2_init_step_done
>
> Yes, and then switch2_init_controller is called again at the end of
> switch2_receive_command
> >
> >>
> >>>
> >>> To me it seems like it would be clearer to do a `ns2->init_step++` and
> >>> then `continue` to make the progress of the state more visible and to
> >>> do an explicit `break` when we are supposed to stop the initialisation.
> >>>
> >>
> >> The problem with the loop approach, in my opinion is due to the fact
> >> that the loop is the *exception*, not the rule. The loop idiom makes it
> >> look like a loop is expected. Further the ns2->init_step++ in the
> >> previous version means that the verification does not occur, so in the
> >> case of any sort of failure it'll plow ahead anyway instead of retrying.
> >> The point of this approach is to avoid that.
> >
> > In my mind a while-loop like you mentioned it above would make the state
> > changes more obvious (since they could all be done in the loop body), while
> > still allowing for retries. Something like the below, perhaps (untested).
> >
> > while (ns2->init_step < NS2_INIT_DONE) {
> > switch (ns2->init_step) {
> > ...
> > case NS2_INIT_READ_FACTORY_TRIGGER_CALIB:
> > if (ns2->ctlr_type != NS2_CTLR_TYPE_GC) {
> > ns->init_step++
> > continue;
> > }
> >
> > ret = switch2_read_flash(ns2, NS2_FLASH_ADDR_FACTORY_TRIGGER_CALIB,
> > NS2_FLASH_SIZE_FACTORY_TRIGGER_CALIB);
> > if (ret) {
> > // if it makes sense to retry here
> > continue;
> > }
> >
> > ns->init_step++
> > break;
> >
> > case ...
> > }
> > }
> >
>
> This can't be done because the process is fundamentally asynchronous.
> We'd have to block on waiting for a reply to the USB packet, which is
> not a good idea. This is why switch2_receive_command calls into
> switch2_init_controller at the end: it's resuming where it left off.
Ah, that wasn't clear to me either.
I assume having the code wait for the reply is not allowed because
otherwise it would stall the whole bootup process (or will there be some
sort of dedicated Kthread for this)?
Are you aware of any documentation where I can read up on how the probing
of USB HID devices work in the Linux Kernel?
Thanks for the help!
> Each of those returns is a step that interacts with the hardware, and we
> need to wait for the hardware to reply.
>
> There is a potential weird interaction here whereby if we get an
> unprompted command and/or reply from the controller it will retry a step
> before it gets a reply for it, but in practice this doesn't happen. The
> controllers, as far as we know, only reply and never initiate any
> commands. Furthermore, all of these steps are also idempotent, so it's
> not a big deal of they get repeated erroneously.
Could there be another reply incoming while the driver is still processing
the previous one? I assume at least at probing time that shouldn't be
the case. I wouldn't expect an USB HID device to send unsolicited replies
in general ...
Cheers,
Silvan
^ permalink raw reply
* [PATCH] HID: sony: remove unneeded WARN_ON() in sony_leds_init()
From: Rosalie Wanders @ 2026-04-11 15:32 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit removes the unneeded WARN_ON() macro usage in
sony_leds_init(), this is unneeded because the sony_leds_init() function
call is already gated behind a SONY_LED_SUPPORT check in
sony_input_configured()
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 23db406092ef..6a860b9ef677 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1640,9 +1640,6 @@ static int sony_leds_init(struct sony_sc *sc)
u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
u8 use_hw_blink[MAX_LEDS] = { 0 };
- if (WARN_ON(!(sc->quirks & SONY_LED_SUPPORT)))
- return -EINVAL;
-
if (sc->quirks & BUZZ_CONTROLLER) {
sc->led_count = 4;
use_color_names = 0;
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: sony: use input_dev from sc struct in sony_init_ff()
From: Rosalie Wanders @ 2026-04-11 15:53 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit makes sony_init_ff() use the input_dev from the sc struct,
this simplifies the sony_init_ff() function.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 17 ++++-------------
1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 6a860b9ef677..ad394ac57fa5 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1853,18 +1853,8 @@ static int sony_play_effect(struct input_dev *dev, void *data,
static int sony_init_ff(struct sony_sc *sc)
{
- struct hid_input *hidinput;
- struct input_dev *input_dev;
-
- if (list_empty(&sc->hdev->inputs)) {
- hid_err(sc->hdev, "no inputs found\n");
- return -ENODEV;
- }
- hidinput = list_entry(sc->hdev->inputs.next, struct hid_input, list);
- input_dev = hidinput->input;
-
- input_set_capability(input_dev, EV_FF, FF_RUMBLE);
- return input_ff_create_memless(input_dev, NULL, sony_play_effect);
+ input_set_capability(sc->input_dev, EV_FF, FF_RUMBLE);
+ return input_ff_create_memless(sc->input_dev, NULL, sony_play_effect);
}
#else
@@ -2151,6 +2141,8 @@ static int sony_input_configured(struct hid_device *hdev,
int append_dev_id;
int ret;
+ sc->input_dev = hidinput->input;
+
ret = sony_set_device_id(sc);
if (ret < 0) {
hid_err(hdev, "failed to allocate the device id\n");
@@ -2311,7 +2303,6 @@ static int sony_input_configured(struct hid_device *hdev,
goto err_close;
}
- sc->input_dev = hidinput->input;
return 0;
err_close:
hid_hw_close(hdev);
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: magicmouse: enable battery polling for 2024 Magic Trackpad
From: Dmitri Ollari @ 2026-04-11 16:38 UTC (permalink / raw)
To: linux-input; +Cc: jikos, Dmitri Ollari
[-- Attachment #1.1: Type: text/plain, Size: 6501 bytes --]
The 2024 Magic Trackpad USB-C (PID 0x0324) does not report battery
strength via HID descriptor fields over Bluetooth. Instead it requires
an explicit HID_REQ_GET_REPORT request to retrieve the battery level.
This patch makes the following changes:
1. Replace the battery_timer (timer_list) with battery_work (delayed_work)
so that HID_REQ_GET_REPORT can be issued from a sleepable context.
Timers run in atomic context and cannot block, which caused deadlocks
on the Bluetooth transport path.
2. Extend the fetch guard and probe scheduling block to include the 2024
Magic Trackpad USB-C when connected over Bluetooth (vendor 0x004C,
product 0x0324 via BT_VENDOR_ID_APPLE).
3. Schedule battery_work immediately at probe (delay=0) instead of
issuing a direct magicmouse_fetch_battery() call. The direct call
bypassed the cold-start correction logic and could publish a stale
value before the work handler had a chance to validate it.
4. Add a cold-start
double-poll: the device may return a stale battery
value (e.g. 4%) on the very first GET_REPORT after power-on. On the
first successful poll battery_validated is set and a second poll is
scheduled 3 seconds later to obtain the real value. Subsequent polls
use the normal 60-second interval.
5. Remove the early-return guard that skipped polling when
battery_capacity equalled battery_max. This prevented the second
corrective poll from firing when the first stale response happened
to equal 100.
Signed-off-by: Dmitri Ollari <dmitri.ollari@protonmail.com>
---
hid-magicmouse.c | 55 +++++++++++++++++++++++++++++-------------------
1 file changed, 33 insertions(+), 22 deletions(-)
diff --git a/hid-magicmouse.c b/hid-magicmouse.c
index 9eadf32..bc9c467 100644
--- a/hid-magicmouse.c
+++ b/hid-magicmouse.c
@@ -123,7 +123,10 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
* @tracking_ids: Mapping of current touch
input data to @touches.
* @hdev: Pointer to the underlying HID device.
* @work: Workqueue to handle initialization retry for quirky devices.
- * @battery_timer: Timer for obtaining battery level information.
+ * @battery_work: Delayed work for periodic battery level polling.
+ * @battery_validated: Set after the first successful poll; gates the
+ * second poll that corrects the stale value the device may report
+ * on cold start.
*/
struct magicmouse_sc {
struct input_dev *input;
@@ -148,7 +151,8 @@ struct magicmouse_sc {
struct hid_device *hdev;
struct delayed_work work;
- struct timer_list battery_timer;
+ struct delayed_work battery_work;
+ bool battery_validated;
};
static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -820,7 +824,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
if (!hdev->battery ||
(!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
- !is_usb_magictrackpad2(hdev->vendor, hdev->p
roduct)))
+ !is_usb_magictrackpad2(hdev->vendor, hdev->product) &&
+ hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)) /* 2024 Magic Trackpad USB-C over Bluetooth */
return -1;
report_enum = &hdev->report_enum[hdev->battery_report_type];
@@ -829,9 +834,6 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
if (!report || report->maxfield < 1)
return -1;
- if (hdev->battery_capacity == hdev->battery_max)
- return -1;
-
hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
return 0;
#else
@@ -839,14 +841,23 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
#endif
}
-static void magicmouse_battery_timer_tick(struct timer_list *t)
+static void magicmouse_battery_work(struct work_struct *work)
{
- struct magicmouse_sc *msc = timer_container_of(msc, t, battery_timer);
+ struct magicmouse_sc *msc = container_of(work, struct magicmouse_sc, battery_work.work);
struct hid_device *hdev = msc->hdev;
i
f (magicmouse_fetch_battery(hdev) == 0) {
- mod_timer(&msc->battery_timer,
- jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+ if (!msc->battery_validated) {
+ /* The device may return a stale value (e.g. 4%) on the
+ * first GET_REPORT after cold start. Schedule a second
+ * poll shortly after to get the real value, then settle
+ * into the normal 60s interval.
+ */
+ msc->battery_validated = true;
+ schedule_delayed_work(&msc->battery_work, secs_to_jiffies(3));
+ } else {
+ schedule_delayed_work(&msc->battery_work, secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
+ }
}
}
@@ -866,6 +877,7 @@ static int magicmouse_probe(struct hid_device *hdev,
msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
msc->hdev = hdev;
INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);
+ INIT_DELAYED_WORK(&msc->battery_work, magicmouse_battery_work);
msc->quirks = id->driver_data;
hid_set_drvdata(hdev, msc);
@@ -883,11 +895,13 @@ static int
magicmouse_probe(struct hid_device *hdev,
}
if (is_usb_magicmouse2(id->vendor, id->product) ||
- is_usb_magictrackpad2(id->vendor, id->product)) {
- timer_setup(&msc->battery_timer, magicmouse_battery_timer_tick, 0);
- mod_timer(&msc->battery_timer,
- jiffies + secs_to_jiffies(USB_BATTERY_TIMEOUT_SEC));
- magicmouse_fetch_battery(hdev);
+ is_usb_magictrackpad2(id->vendor, id->product) ||
+ id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
+ /* Schedule immediately so battery_work runs ASAP, sets battery_validated,
+ * then reschedules every 60s. Avoids direct fetch which bypasses
+ * battery_validated and would publish a stale startup value.
+ */
+ schedule_delayed_work(&msc->battery_work, 0);
}
if (is_usb_magicmouse2(id->vendor, id->product) ||
@@ -955,10 +969,8 @@ static int magicmouse_probe(struct hid_device *hdev,
return 0;
err_stop_hw:
- if (is_usb_magicmouse2(id->vendor, id->product) ||
- is_usb_magi
ctrackpad2(id->vendor, id->product))
- timer_delete_sync(&msc->battery_timer);
-
+ /* Clean up battery work on error */
+ cancel_delayed_work_sync(&msc->battery_work);
hid_hw_stop(hdev);
return ret;
}
@@ -969,9 +981,8 @@ static void magicmouse_remove(struct hid_device *hdev)
if (msc) {
cancel_delayed_work_sync(&msc->work);
- if (is_usb_magicmouse2(hdev->vendor, hdev->product) ||
- is_usb_magictrackpad2(hdev->vendor, hdev->product))
- timer_delete_sync(&msc->battery_timer);
+ /* Cancel battery polling on device removal */
+ cancel_delayed_work_sync(&msc->battery_work);
}
hid_hw_stop(hdev);
--
2.53.0
[-- Attachment #1.2: publickey - dmitri.ollari@protonmail.com - 0xF53BC391.asc --]
[-- Type: application/pgp-keys, Size: 722 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 322 bytes --]
^ permalink raw reply related
* Re: [git pull] Input updates for v7.0-rc7
From: pr-tracker-bot @ 2026-04-11 19:58 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: Linus Torvalds, linux-kernel, linux-input
In-Reply-To: <adnMg9E_ZAjxcv1k@google.com>
The pull request you sent on Fri, 10 Apr 2026 21:23:07 -0700:
> git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.0-rc7
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/778322a06e217e768ba3dc550a6f599f73ed781d
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* HID: core: regression in 6.19.12 - short report handling floods dmesg for working devices
From: Anj D @ 2026-04-12 0:24 UTC (permalink / raw)
To: linux-input@vger.kernel.org
Cc: benjamin.tissoires@redhat.com, lee@kernel.org, jikos@kernel.org
Since 6.19.12, my dmesg is flooded with the following -
hid-generic 0003:051D:0002.0007: Event data for report 22 was too short (4 vs 2)
This message fires continuously at polling rate (~every 320ms), with the
rate limiter suppressing batches and then re-emitting, producing significant
dmesg noise on any system with this device attached.
The affected hardware is an APC UPS (USB VID/PID 051D:0002)
Bisected to commit 0a3fe972a7cb1404f693d6f1711f32bc1d244b1c introduced in 6.19.12.
Reverting this single commit on 6.19.12 restores the previous behaviour.
The APC UPS firmware has always sent HID report 22 with 2 bytes when
the descriptor declares 4 bytes. The previous memset() silently padded
the short data. The new early-return code logs the mismatch on every
poll, flooding dmesg. The UPS functions correctly otherwise.
Suggestion: Since we previously only reported these events when HID_DEBUG was enabled.
Perhaps we can continue to do the same? i.e.
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2057,7 +2057,7 @@
rsize = max_buffer_size;
if (csize < rsize) {
- hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %d)\n",
+ dbg_hid("Event data for report %d was too short (%d vs %d)\n",
report->id, rsize, csize);
ret = -EINVAL;
goto out;
Or if it we feel it is necessary to report the event, do so only once per device?
Best regards,
Anj Duvnjak
^ permalink raw reply
* [PATCH] HID: sony: add missing size validation for SMK-Link remotes
From: Rosalie Wanders @ 2026-04-12 1:08 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit adds the missing size validation for SMK-Link remotes in
sony_raw_event(), this prevents a malicious device from allowing
hid-sony to read out of bounds of the provided buffer.
I do not own these devices so the size check only forces that the buffer
is large enough for nsg_mrxu_parse_report().
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index ad394ac57fa5..2260d55a17b0 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1169,10 +1169,9 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
sixaxis_parse_report(sc, rd, size);
} else if ((sc->quirks & MOTION_CONTROLLER_BT) && rd[0] == 0x01 && size == 49) {
sixaxis_parse_report(sc, rd, size);
- } else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 &&
- size == 49) {
+ } else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 && size == 49) {
sixaxis_parse_report(sc, rd, size);
- } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) {
+ } else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02 && size >= 12) {
nsg_mrxu_parse_report(sc, rd, size);
return 1;
} else if ((sc->quirks & RB4_GUITAR_PS4_USB) && rd[0] == 0x01 && size == 64) {
--
2.53.0
^ permalink raw reply related
* [PATCH] HID: sony: add missing size validation for Rock Band 3 Pro instruments
From: Rosalie Wanders @ 2026-04-12 1:12 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Rosalie Wanders, linux-input, linux-kernel
This commit adds the missing size validation for Rock Band 3 PS3 Pro
instruments in sony_raw_event(), this prevents a malicious device from
allowing hid-sony to read out of bounds of the provided buffer.
Signed-off-by: Rosalie Wanders <rosalie@mailbox.org>
---
drivers/hid/hid-sony.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 2260d55a17b0..e75246d29e16 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -1188,7 +1188,7 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
/* Rock Band 3 PS3 Pro instruments set rd[24] to 0xE0 when they're
* sending full reports, and 0x02 when only sending navigation.
*/
- if ((sc->quirks & RB3_PRO_INSTRUMENT) && rd[24] == 0x02) {
+ if ((sc->quirks & RB3_PRO_INSTRUMENT) && size >= 25 && rd[24] == 0x02) {
/* Only attempt to enable full report every 8 seconds */
if (time_after(jiffies, sc->rb3_pro_poke_jiffies)) {
sc->rb3_pro_poke_jiffies = jiffies + secs_to_jiffies(8);
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2] HID: pulsar: add driver for Pulsar gaming mice
From: kernel test robot @ 2026-04-12 4:53 UTC (permalink / raw)
To: Nikolas Koesling, Jiri Kosina, Benjamin Tissoires
Cc: oe-kbuild-all, linux-input, linux-kernel, Leo
In-Reply-To: <20260401185708.286359-1-nikolas@koesling.info>
Hi Nikolas,
kernel test robot noticed the following build warnings:
[auto build test WARNING on v7.0-rc7]
[cannot apply to hid/for-next linus/master next-20260410]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Nikolas-Koesling/HID-pulsar-add-driver-for-Pulsar-gaming-mice/20260411-084544
base: v7.0-rc7
patch link: https://lore.kernel.org/r/20260401185708.286359-1-nikolas%40koesling.info
patch subject: [PATCH v2] HID: pulsar: add driver for Pulsar gaming mice
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20260412/202604121253.ZYbxnPmo-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260412/202604121253.ZYbxnPmo-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202604121253.ZYbxnPmo-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/hid/hid-pulsar.c: In function 'pulsar_init_work':
>> drivers/hid/hid-pulsar.c:601:27: warning: '%s' directive output may be truncated writing up to 127 bytes into a region of size 32 [-Wformat-truncation=]
601 | "%s", hdev->name);
| ^~
drivers/hid/hid-pulsar.c:600:17: note: 'snprintf' output between 1 and 128 bytes into a destination of size 32
600 | snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
601 | "%s", hdev->name);
| ~~~~~~~~~~~~~~~~~
drivers/hid/hid-pulsar.c:586:59: warning: '%s' directive output may be truncated writing up to 127 bytes into a region of size 32 [-Wformat-truncation=]
586 | sizeof(drvdata->battery.model), "%s", hdev->name);
| ^~
drivers/hid/hid-pulsar.c:585:17: note: 'snprintf' output between 1 and 128 bytes into a destination of size 32
585 | snprintf(drvdata->battery.model,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
586 | sizeof(drvdata->battery.model), "%s", hdev->name);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
vim +601 drivers/hid/hid-pulsar.c
553
554 static void pulsar_init_work(struct work_struct *work)
555 {
556 struct pulsar_data *drvdata;
557 struct hid_device *hdev;
558 struct power_supply_config psy_cfg;
559 int ret;
560 u8 data[DEV_INFO_LEN];
561
562 drvdata = container_of(work, struct pulsar_data, init_work.work);
563 hdev = drvdata->hdev;
564
565 ret = read_device_info(drvdata, data);
566 if (ret == -ETIMEDOUT) {
567 if (drvdata->init_retries--) {
568 hid_dbg(hdev,
569 "device info read timed out, retrying (%u left)\n",
570 drvdata->init_retries);
571 schedule_delayed_work(&drvdata->init_work,
572 msecs_to_jiffies
573 (INIT_DELAY_MSEC));
574 return;
575 }
576 hid_err(hdev, "device info read timed out, giving up\n");
577 return;
578 }
579 if (ret < 0) {
580 if (drvdata->type == TYPE_PULSAR) {
581 hid_err(hdev, "failed to read device info: %d\n", ret);
582 return;
583 }
584 hid_dbg(hdev, "failed to read device info: %d\n", ret);
585 snprintf(drvdata->battery.model,
586 sizeof(drvdata->battery.model), "%s", hdev->name);
587 goto register_battery;
588 }
589
590 hid_dbg(hdev, "device info: %*ph (%d)\n", DEV_INFO_LEN, data, ret);
591
592 switch (drvdata->type) {
593 case TYPE_PULSAR:
594 model_pulsar(data, drvdata);
595 break;
596 case TYPE_ATK:
597 model_atk(data, drvdata);
598 break;
599 default:
600 snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
> 601 "%s", hdev->name);
602 }
603
604 register_battery:
605 init_power_supply_desc(drvdata);
606
607 psy_cfg = (struct power_supply_config) {.drv_data = drvdata };
608 drvdata->battery.ps =
609 devm_power_supply_register(&hdev->dev, &drvdata->battery.desc,
610 &psy_cfg);
611 if (IS_ERR(drvdata->battery.ps)) {
612 hid_err(hdev, "failed to register battery: %ld\n",
613 PTR_ERR(drvdata->battery.ps));
614 drvdata->battery.ps = NULL;
615 return;
616 }
617
618 atomic_set(&drvdata->device_verified, 1);
619 hid_info(hdev, "device verified, battery registered\n");
620 }
621
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH v3] HID: pulsar: add driver for Pulsar gaming mice
From: Nikolas Koesling @ 2026-04-12 7:53 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Lode Willems, linux-input, linux-kernel, Leo
Add a HID driver for Pulsar wireless gaming mice (X2 V2, X2H, X2A,
Xlite V3). The driver exposes battery level, voltage, and charging
status through the power supply framework. It supports wired, 1kHz,
and 4kHz wireless dongle connections.
The driver also supports Kysona M600 ATK, VXE R1 SE+ and
VXE Dragonfly R1 Pro, which use the same protocol for reading
battery status and availability.
The protocol used by this driver is based on findings from
python-pulsar-mouse-tool by Andrew Rabert (MIT License):
https://github.com/andrewrabert/python-pulsar-mouse-tool
ATK vendor and device IDs were provided by Leo <leo@managarm.org>.
VXE and Kysona vendor and device IDS are from hid-kysona.c by
Lode Willems <me@lodewillems.com>
Tested-by: Leo <leo@managarm.org>
Signed-off-by: Nikolas Koesling <nikolas@koesling.info>
---
Changes in v2:
- Add support for Kysona M600, ATK VXE R1 SE+, and VXE Dragonfly R1 Pro
- Add device type enum to distinguish vendors and generate proper
battery names per vendor/model
- Add mutual exclusion with HID_KYSONA in Kconfig
- Add ATK and VXE vendor/device IDs to hid-ids.h
- Refactor model name generation: extract model_pulsar() and add
model_atk() for vendor-specific battery naming
- Fall back to hdev->name for battery model when device info read
fails on non-Pulsar devices (downgrade error to debug log)
- Remove POWER_SUPPLY_PROP_MANUFACTURER property
- Pass device type via driver_data in hid_device_id table
Changes in v3:
- Increase size of battery model name to hid device name size
---
MAINTAINERS | 6 +
drivers/hid/Kconfig | 15 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 15 +
drivers/hid/hid-pulsar.c | 754 +++++++++++++++++++++++++++++++++++++++
5 files changed, 791 insertions(+)
create mode 100644 drivers/hid/hid-pulsar.c
diff --git a/MAINTAINERS b/MAINTAINERS
index c3fe46d7c4bc..207216632918 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11352,6 +11352,12 @@ L: linux-input@vger.kernel.org
S: Supported
F: drivers/hid/hid-playstation.c
+HID PULSAR DRIVER
+M: Nikolas Koesling <nikolas@koesling.info>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-pulsar.c
+
HID SENSOR HUB DRIVERS
M: Jiri Kosina <jikos@kernel.org>
M: Jonathan Cameron <jic23@kernel.org>
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index c1d9f7c6a5f2..333d165554ee 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -511,12 +511,15 @@ config HID_KYE
config HID_KYSONA
tristate "Kysona devices"
depends on USB_HID
+ depends on !HID_PULSAR
help
Support for Kysona mice.
Say Y here if you have a Kysona M600 mouse
and want to be able to read its battery capacity.
+ Note: The Kysona M600 is also supported by HID_PULSAR.
+
config HID_UCLOGIC
tristate "UC-Logic"
depends on USB_HID
@@ -1280,6 +1283,18 @@ config HID_UNIVERSAL_PIDFF
Supports Moza Racing, Cammus, VRS, FFBeast and more.
+config HID_PULSAR
+ tristate "Pulsar gaming mouse support"
+ depends on USB_HID
+ select POWER_SUPPLY
+ help
+ Support for Pulsar gaming mice (X2 V2, X2H, X2A, Xlite V3)
+ connected via 1kHz/4kHz USB dongle or wired.
+ Provides battery level, voltage, and charging status
+ monitoring via the power supply framework.
+
+ Additional supported devices: Kysona M600, ATK VXE R1 SE+
+
config HID_WACOM
tristate "Wacom Intuos/Graphire tablet support (USB)"
depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e01838239ae6..67ad39b47df1 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -112,6 +112,7 @@ hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o
obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o
obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o
obj-$(CONFIG_HID_PRIMAX) += hid-primax.o
+obj-$(CONFIG_HID_PULSAR) += hid-pulsar.o
obj-$(CONFIG_HID_PXRC) += hid-pxrc.o
obj-$(CONFIG_HID_RAPOO) += hid-rapoo.o
obj-$(CONFIG_HID_RAZER) += hid-razer.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index afcee13bad61..5ce542150d61 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -248,6 +248,12 @@
#define USB_VENDOR_ID_ATMEL_V_USB 0x16c0
#define USB_DEVICE_ID_ATMEL_V_USB 0x05df
+#define USB_VENDOR_ID_ATK 0x373B
+#define USB_DEVICE_ID_ATK_VXE_R1_SE_WIRED 0xF58F
+
+#define USB_VENDOR_ID_ATK_ALT 0x3554
+#define USB_DEVICE_ID_ATK_VXE_R1_SE_DONGLE 0x1085
+
#define USB_VENDOR_ID_AUREAL 0x0755
#define USB_DEVICE_ID_AUREAL_W01RN 0x2626
@@ -1169,6 +1175,11 @@
#define USB_VENDOR_ID_PRODIGE 0x05af
#define USB_DEVICE_ID_PRODIGE_CORDLESS 0x3062
+#define USB_VENDOR_ID_PULSAR 0x3554
+#define USB_DEVICE_ID_PULSAR_WIRED 0xf507
+#define USB_DEVICE_ID_PULSAR_1KHZ 0xf508
+#define USB_DEVICE_ID_PULSAR_4KHZ 0xf509
+
#define I2C_VENDOR_ID_QTEC 0x6243
#define USB_VENDOR_ID_QUANTA 0x0408
@@ -1471,6 +1482,10 @@
#define USB_VENDOR_ID_VTL 0x0306
#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f
+#define USB_VENDOR_ID_VXE 0x3554
+#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE 0xf58a
+#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED 0xf58c
+
#define USB_VENDOR_ID_WACOM 0x056a
#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81
#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD
diff --git a/drivers/hid/hid-pulsar.c b/drivers/hid/hid-pulsar.c
new file mode 100644
index 000000000000..f95b7a33d9e5
--- /dev/null
+++ b/drivers/hid/hid-pulsar.c
@@ -0,0 +1,754 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for pulsar mice
+ *
+ * Supported pulsar devices:
+ * - Pulsar
+ * - X2 V2
+ * - X2H
+ * - X2A
+ * - Xlite V3
+ * - Kysona
+ * -M600
+ * - ATK
+ * - VXE R1 SE+
+ * - VXE
+ * - Dragonfly R1 Pro
+ *
+ * Copyright (c) 2026 Nikolas Koesling
+ */
+
+#include <linux/hid.h>
+#include <linux/usb.h>
+#include <linux/power_supply.h>
+#include "hid-ids.h"
+
+/* ----- driver settings ----- */
+#define CMD_TIMEOUT_MSEC 100
+#define MAX_BATTERY_AGE_NS 60000000000ULL /* 60s */
+#define MAX_UNAVAIL_AGE_NS 5000000000ULL /* 5s */
+#define INIT_RETRIES 1
+#define INIT_DELAY_MSEC 1000
+
+/* ----- constants ----- */
+#define USB_INTERFACE 1
+#define USB_PAYLOAD_LEN 17
+#define CMD_HID_REPORT_ID 0x08
+#define CHECKSUM_MAGIC 0x55
+#define DEV_INFO_LEN 4
+#define CON_1K 0x00
+#define CON_4K 0x01
+#define CON_WIRED 0x02
+
+/* ----- device commands ----- */
+enum pulsar_cmd {
+ CMD_NONE = 0,
+ CMD_INFO = 0x01,
+ CMD_STATUS = 0x03,
+ CMD_POWER = 0x04,
+ CMD_EVENT = 0x0a, /* recv only */
+};
+
+#define EVENT_PWR 0x40 /* power status change */
+#define EVENT_PWR_CHK 0xf9
+
+/* ----- device types ----- */
+enum dev_type {
+ TYPE_UNKNOWN,
+ TYPE_PULSAR,
+ TYPE_KYSONA,
+ TYPE_ATK,
+ TYPE_VXE,
+};
+
+/* ----- structs ----- */
+struct pulsar_battery {
+ struct power_supply *ps;
+ struct power_supply_desc desc;
+ char name[48];
+ char model[MAX(32, sizeof((struct hid_device){}).name)];
+ u8 level; /* percent */
+ u16 voltage; /* millivolts */
+ bool conn;
+ bool available;
+ u64 last_read;
+ u64 last_status;
+};
+
+struct pulsar_data {
+ struct hid_device *hdev;
+
+ enum dev_type type;
+
+ spinlock_t raw_event_lock; /* protects response_buf, pending_event */
+ struct mutex lock_cmd; /* serializes device command execution */
+ struct rw_semaphore lock_bat; /* protects battery state */
+
+ struct completion response_ready;
+ u8 response_buf[USB_PAYLOAD_LEN];
+ u8 pending_event;
+ struct work_struct power_uevent_work;
+ struct delayed_work init_work;
+ unsigned int init_retries;
+ atomic_t device_verified;
+ atomic_t stopping;
+
+ struct pulsar_battery battery;
+};
+
+static u8 calc_checksum(const u8 *data, size_t len)
+{
+ u8 sum = 0;
+
+ for (size_t i = 0; i < len - 1; i++)
+ sum += data[i];
+
+ return (u8)CHECKSUM_MAGIC - sum;
+}
+
+static int send_cmd(struct hid_device *hdev, const u8 *buf, size_t len)
+{
+ int ret;
+ u8 *dmabuf;
+
+ hid_dbg(hdev, "send command: %*ph\n", (int)len, buf);
+
+ dmabuf = kmemdup(buf, len, GFP_KERNEL);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ /* device listens only to control transfers */
+ ret = hid_hw_raw_request(hdev, dmabuf[0], dmabuf, len,
+ HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+
+ kfree(dmabuf);
+
+ if (ret < 0)
+ return ret;
+ if (ret != len)
+ return -EIO;
+
+ return 0;
+}
+
+static int exec_cmd(struct pulsar_data *drvdata, const u8 *payload,
+ u8 *response, unsigned int timeout_msec)
+{
+ struct hid_device *hdev = drvdata->hdev;
+ unsigned long flags;
+ int ret;
+ unsigned long timeout;
+ u8 checksum;
+
+ if (atomic_read(&drvdata->stopping))
+ return -ENODEV;
+
+ mutex_lock(&drvdata->lock_cmd);
+
+ if (atomic_read(&drvdata->stopping)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ spin_lock_irqsave(&drvdata->raw_event_lock, flags);
+ reinit_completion(&drvdata->response_ready);
+ drvdata->pending_event = payload[1];
+ spin_unlock_irqrestore(&drvdata->raw_event_lock, flags);
+
+ ret = send_cmd(hdev, payload, USB_PAYLOAD_LEN);
+
+ if (ret < 0) {
+ spin_lock_irqsave(&drvdata->raw_event_lock, flags);
+ drvdata->pending_event = CMD_NONE;
+ spin_unlock_irqrestore(&drvdata->raw_event_lock, flags);
+ hid_err(hdev, "failed to send command 0x%02x: %d\n",
+ payload[1], ret);
+ goto out;
+ }
+
+ timeout = wait_for_completion_timeout(&drvdata->response_ready,
+ msecs_to_jiffies(timeout_msec));
+
+ if (timeout == 0) {
+ spin_lock_irqsave(&drvdata->raw_event_lock, flags);
+ drvdata->pending_event = CMD_NONE;
+ spin_unlock_irqrestore(&drvdata->raw_event_lock, flags);
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ spin_lock_irqsave(&drvdata->raw_event_lock, flags);
+ memcpy(response, drvdata->response_buf, USB_PAYLOAD_LEN);
+ spin_unlock_irqrestore(&drvdata->raw_event_lock, flags);
+
+ /* validate checksum */
+ checksum = calc_checksum(response, USB_PAYLOAD_LEN);
+
+ if (response[USB_PAYLOAD_LEN - 1] != checksum) {
+ hid_err(hdev,
+ "invalid checksum in response: 0x%02x (expected 0x%02x)\n",
+ response[USB_PAYLOAD_LEN - 1], checksum);
+ ret = -EIO;
+ goto out;
+ }
+
+ ret = 0;
+out:
+ mutex_unlock(&drvdata->lock_cmd);
+ return ret;
+}
+
+static inline void finalize_payload(u8 *payload, u8 cmd)
+{
+ payload[0] = CMD_HID_REPORT_ID;
+ payload[1] = cmd;
+ payload[USB_PAYLOAD_LEN - 1] = calc_checksum(payload, USB_PAYLOAD_LEN);
+}
+
+static int read_status(struct pulsar_data *drvdata)
+{
+ int ret;
+ u8 payload[USB_PAYLOAD_LEN] = { 0 };
+ u8 response[USB_PAYLOAD_LEN];
+
+ finalize_payload(payload, CMD_STATUS);
+
+ ret = exec_cmd(drvdata, payload, response, CMD_TIMEOUT_MSEC);
+ if (ret < 0)
+ return ret;
+ if (response[6] > 0x01)
+ return -EIO;
+
+ return (int)response[6]; /* 1: available, 0: not available */
+}
+
+static int read_device_info(struct pulsar_data *drvdata, u8 *data)
+{
+ int ret;
+ u8 payload[USB_PAYLOAD_LEN] = { 0 };
+ u8 response[USB_PAYLOAD_LEN];
+
+ payload[5] = DEV_INFO_LEN * 2;
+ get_random_bytes(payload + 6, DEV_INFO_LEN);
+ finalize_payload(payload, CMD_INFO);
+
+ ret = exec_cmd(drvdata, payload, response, CMD_TIMEOUT_MSEC);
+ if (ret < 0)
+ return ret;
+
+ if (data)
+ memcpy(data, response + 6 + DEV_INFO_LEN, DEV_INFO_LEN);
+
+ response[8 + DEV_INFO_LEN] = 0;
+ response[9 + DEV_INFO_LEN] = 0;
+
+ /*
+ * Verify challenge-response. Response layout from offset 6:
+ * [0..3] encoded response [4..7] device info (ID + conn type)
+ *
+ * resp[i] = challenge[i] * (i+1) + challenge[(i+1) % 4] + device_id[i]
+ *
+ * bytes 6..7 are zeroed for verification.
+ */
+ for (int i = 0; i < DEV_INFO_LEN; i++) {
+ u8 expect = response[6 + DEV_INFO_LEN + i];
+ u8 actual = response[6 + i] - (i + 1) * payload[6 + i] -
+ payload[6 + (i + 1) % DEV_INFO_LEN];
+
+ if (expect != actual) {
+ hid_warn(drvdata->hdev,
+ "device info[%d] mismatch: %02x != %02x\n",
+ i, expect, actual);
+ return -EIO;
+ }
+ }
+
+ return 0;
+}
+
+static int read_power(struct pulsar_data *drvdata)
+{
+ u64 now;
+ bool need_status, need_power;
+ int ret = 0;
+ u8 payload[USB_PAYLOAD_LEN] = { 0 };
+ u8 response[USB_PAYLOAD_LEN];
+ struct pulsar_battery *battery = &drvdata->battery;
+
+ now = ktime_get_ns();
+
+ down_write(&drvdata->lock_bat);
+
+ need_status = (now - battery->last_status >= MAX_UNAVAIL_AGE_NS);
+ need_power = battery->available &&
+ (now - battery->last_read >= MAX_BATTERY_AGE_NS);
+
+ if (!need_status && !need_power)
+ goto unlock;
+
+ if (need_status) {
+ ret = read_status(drvdata);
+ if (ret < 0) {
+ hid_err(drvdata->hdev,
+ "%s: failed to read status: %d\n",
+ __func__, ret);
+ goto unlock;
+ }
+
+ battery->last_status = now;
+
+ if (!ret) {
+ battery->available = false;
+ goto unlock;
+ }
+
+ /* device just became available, force power read */
+ if (!battery->available)
+ need_power = true;
+ }
+
+ if (!need_power)
+ goto unlock;
+
+ finalize_payload(payload, CMD_POWER);
+
+ ret = exec_cmd(drvdata, payload, response, CMD_TIMEOUT_MSEC);
+ if (ret < 0) {
+ hid_err(drvdata->hdev, "%s: failed to read power: %d\n",
+ __func__, ret);
+ goto unlock;
+ }
+
+ if (response[6] > 100 || response[7] > 0x01) {
+ ret = -EIO;
+ goto unlock;
+ }
+
+ battery->available = true;
+ battery->level = response[6];
+ battery->conn = response[7] == 1;
+ battery->voltage = (response[8] << 8) | response[9];
+ battery->last_read = now;
+
+ hid_dbg(drvdata->hdev, "%s: level=%d, conn=%d, voltage=%d\n",
+ __func__, battery->level, battery->conn, battery->voltage);
+
+unlock:
+ up_write(&drvdata->lock_bat);
+ return ret;
+}
+
+static int battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pulsar_data *drvdata;
+ int ret;
+
+ drvdata = power_supply_get_drvdata(psy);
+
+ ret = read_power(drvdata);
+ if (ret)
+ return ret;
+
+ down_read(&drvdata->lock_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!drvdata->battery.available)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else if (drvdata->battery.conn && drvdata->battery.level < 100)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (drvdata->battery.conn && drvdata->battery.level >= 100)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = drvdata->battery.level;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = drvdata->battery.voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = drvdata->battery.available;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = drvdata->battery.model;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ up_read(&drvdata->lock_bat);
+ return ret;
+}
+
+static void power_uevent_work_handler(struct work_struct *work)
+{
+ struct pulsar_data *drvdata;
+ int ret;
+
+ drvdata = container_of(work, struct pulsar_data, power_uevent_work);
+
+ if (atomic_read(&drvdata->stopping))
+ return;
+
+ down_write(&drvdata->lock_bat);
+ drvdata->battery.last_read = 0;
+ drvdata->battery.last_status = 0;
+ up_write(&drvdata->lock_bat);
+
+ ret = read_power(drvdata);
+ if (ret < 0) {
+ hid_err(drvdata->hdev, "%s: failed to read power: %d\n",
+ __func__, ret);
+ return;
+ }
+
+ power_supply_changed(drvdata->battery.ps);
+}
+
+static int pulsar_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct pulsar_data *drvdata;
+
+ drvdata = hid_get_drvdata(hdev);
+ if (!drvdata)
+ return 0;
+
+ hid_dbg(hdev, "received raw event: %*ph\n", size, data);
+
+ if (size != USB_PAYLOAD_LEN || data[0] != CMD_HID_REPORT_ID)
+ return 0;
+
+ if (data[1] != CMD_EVENT) {
+ spin_lock(&drvdata->raw_event_lock);
+ if (drvdata->pending_event != data[1]) {
+ spin_unlock(&drvdata->raw_event_lock);
+ return 0;
+ }
+ memcpy(drvdata->response_buf, data, size);
+ drvdata->pending_event = CMD_NONE;
+ complete(&drvdata->response_ready);
+ spin_unlock(&drvdata->raw_event_lock);
+ return 1;
+ }
+
+ if (!atomic_read(&drvdata->device_verified))
+ return 0;
+
+ if (data[6] == EVENT_PWR && data[USB_PAYLOAD_LEN - 1] == EVENT_PWR_CHK) {
+ schedule_work(&drvdata->power_uevent_work);
+ hid_dbg(hdev, "received power event\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static const enum power_supply_property pulsar_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_PRESENT
+};
+
+static void init_power_supply_desc(struct pulsar_data *drvdata)
+{
+ drvdata->battery.desc.name = drvdata->battery.name;
+ drvdata->battery.desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drvdata->battery.desc.properties = pulsar_battery_props;
+ drvdata->battery.desc.num_properties = ARRAY_SIZE(pulsar_battery_props);
+ drvdata->battery.desc.get_property = battery_get_property;
+}
+
+static void model_pulsar(u8 *device_id, struct pulsar_data *drvdata)
+{
+ u16 model_id;
+ const char *con_type = "unknown";
+
+ model_id = device_id[0] << 8 | device_id[1];
+
+ switch (device_id[2]) {
+ case CON_1K:
+ con_type = "1kHz";
+ break;
+ case CON_4K:
+ con_type = "4kHz";
+ break;
+ case CON_WIRED:
+ con_type = "wired";
+ break;
+ }
+
+ switch (model_id) {
+ case 0x060a:
+ case 0x060b:
+ case 0x0612:
+ case 0x0613:
+ case 0x0614:
+ case 0x0615:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "Pulsar X2 V2 (%s)", con_type);
+ break;
+ case 0x060c:
+ case 0x060d:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "Pulsar X2H (%s)", con_type);
+ break;
+ case 0x0607:
+ case 0x060e:
+ case 0x060f:
+ case 0x0610:
+ case 0x0611:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "Pulsar Xlite V3 (%s)", con_type);
+ break;
+ case 0x0608:
+ case 0x0609:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "Pulsar X2A (%s)", con_type);
+ break;
+ default:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "Pulsar unknown (%s)", con_type);
+ }
+}
+
+static void model_atk(u8 *device_id, struct pulsar_data *drvdata)
+{
+ u16 model_id;
+ const char *con_type = "unknown";
+
+ model_id = device_id[0] << 8 | device_id[1];
+
+ switch (device_id[2]) {
+ case CON_1K:
+ con_type = "1kHz";
+ break;
+ case CON_4K:
+ con_type = "4kHz";
+ break;
+ case CON_WIRED:
+ con_type = "wired";
+ break;
+ }
+
+ switch (model_id) {
+ case 0x0220:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "ATK VXE R1 SE+ (%s)", con_type);
+ break;
+ default:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "Unknown ATK (%s)", con_type);
+ }
+}
+
+static void pulsar_init_work(struct work_struct *work)
+{
+ struct pulsar_data *drvdata;
+ struct hid_device *hdev;
+ struct power_supply_config psy_cfg;
+ int ret;
+ u8 data[DEV_INFO_LEN];
+
+ drvdata = container_of(work, struct pulsar_data, init_work.work);
+ hdev = drvdata->hdev;
+
+ ret = read_device_info(drvdata, data);
+ if (ret == -ETIMEDOUT) {
+ if (drvdata->init_retries--) {
+ hid_dbg(hdev,
+ "device info read timed out, retrying (%u left)\n",
+ drvdata->init_retries);
+ schedule_delayed_work(&drvdata->init_work,
+ msecs_to_jiffies
+ (INIT_DELAY_MSEC));
+ return;
+ }
+ hid_err(hdev, "device info read timed out, giving up\n");
+ return;
+ }
+ if (ret < 0) {
+ if (drvdata->type == TYPE_PULSAR) {
+ hid_err(hdev, "failed to read device info: %d\n", ret);
+ return;
+ }
+ hid_dbg(hdev, "failed to read device info: %d\n", ret);
+ snprintf(drvdata->battery.model,
+ sizeof(drvdata->battery.model), "%s", hdev->name);
+ goto register_battery;
+ }
+
+ hid_dbg(hdev, "device info: %*ph (%d)\n", DEV_INFO_LEN, data, ret);
+
+ switch (drvdata->type) {
+ case TYPE_PULSAR:
+ model_pulsar(data, drvdata);
+ break;
+ case TYPE_ATK:
+ model_atk(data, drvdata);
+ break;
+ default:
+ snprintf(drvdata->battery.model, sizeof(drvdata->battery.model),
+ "%s", hdev->name);
+ }
+
+register_battery:
+ init_power_supply_desc(drvdata);
+
+ psy_cfg = (struct power_supply_config) {.drv_data = drvdata };
+ drvdata->battery.ps =
+ devm_power_supply_register(&hdev->dev, &drvdata->battery.desc,
+ &psy_cfg);
+ if (IS_ERR(drvdata->battery.ps)) {
+ hid_err(hdev, "failed to register battery: %ld\n",
+ PTR_ERR(drvdata->battery.ps));
+ drvdata->battery.ps = NULL;
+ return;
+ }
+
+ atomic_set(&drvdata->device_verified, 1);
+ hid_info(hdev, "device verified, battery registered\n");
+}
+
+static int pulsar_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+ struct usb_interface *intf;
+ struct usb_device *usbdev;
+ struct pulsar_data *drvdata;
+ struct hid_report *report_in;
+ struct hid_report *report_out;
+
+ if (!hid_is_usb(hdev))
+ return -ENODEV;
+
+ ret = hid_parse(hdev);
+ if (ret < 0) {
+ hid_err(hdev, "hid_parse failed: %d\n", ret);
+ return ret;
+ }
+
+ intf = to_usb_interface(hdev->dev.parent);
+ report_in =
+ hdev->report_enum[HID_INPUT_REPORT].report_id_hash[CMD_HID_REPORT_ID];
+ report_out =
+ hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[CMD_HID_REPORT_ID];
+
+ if (!report_in || !report_out ||
+ hid_report_len(report_in) != USB_PAYLOAD_LEN ||
+ hid_report_len(report_out) != USB_PAYLOAD_LEN ||
+ intf->cur_altsetting->desc.bInterfaceNumber != USB_INTERFACE)
+ return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+
+ drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->hdev = hdev;
+ drvdata->type = id->driver_data;
+
+ mutex_init(&drvdata->lock_cmd);
+ init_rwsem(&drvdata->lock_bat);
+
+ usbdev = interface_to_usbdev(intf);
+
+ spin_lock_init(&drvdata->raw_event_lock);
+ hid_set_drvdata(hdev, drvdata);
+ init_completion(&drvdata->response_ready);
+ INIT_WORK(&drvdata->power_uevent_work, power_uevent_work_handler);
+ INIT_DELAYED_WORK(&drvdata->init_work, pulsar_init_work);
+ drvdata->init_retries = INIT_RETRIES;
+
+ snprintf(drvdata->battery.name, sizeof(drvdata->battery.name),
+ "pulsar_%s_battery", usbdev->devpath);
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret < 0) {
+ hid_err(hdev, "hw start failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_open(hdev);
+ if (ret < 0) {
+ hid_err(hdev, "hw open failed\n");
+ goto err_open;
+ }
+
+ schedule_delayed_work(&drvdata->init_work, 0);
+
+ return 0;
+
+err_open:
+ cancel_work_sync(&drvdata->power_uevent_work);
+ hid_hw_stop(hdev);
+ return ret;
+}
+
+static void pulsar_remove(struct hid_device *hdev)
+{
+ struct pulsar_data *drvdata;
+
+ drvdata = hid_get_drvdata(hdev);
+ if (!drvdata) {
+ hid_hw_stop(hdev);
+ return;
+ }
+
+ atomic_set(&drvdata->stopping, 1);
+ cancel_delayed_work_sync(&drvdata->init_work);
+ cancel_work_sync(&drvdata->power_uevent_work);
+
+ /* wait for active device i/o (exec_cmd) */
+ mutex_lock(&drvdata->lock_cmd);
+ hid_hw_close(hdev);
+ mutex_unlock(&drvdata->lock_cmd);
+
+ hid_hw_stop(hdev);
+ mutex_destroy(&drvdata->lock_cmd);
+}
+
+static const struct hid_device_id pulsar_table[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_PULSAR, USB_DEVICE_ID_PULSAR_WIRED),
+ .driver_data = TYPE_PULSAR },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PULSAR, USB_DEVICE_ID_PULSAR_1KHZ),
+ .driver_data = TYPE_PULSAR },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PULSAR, USB_DEVICE_ID_PULSAR_4KHZ),
+ .driver_data = TYPE_PULSAR },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE),
+ .driver_data = TYPE_KYSONA },
+ { HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED),
+ .driver_data = TYPE_KYSONA },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ATK, USB_DEVICE_ID_ATK_VXE_R1_SE_DONGLE),
+ .driver_data = TYPE_ATK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ATK_ALT, USB_DEVICE_ID_ATK_VXE_R1_SE_WIRED),
+ .driver_data = TYPE_ATK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE),
+ .driver_data = TYPE_VXE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED),
+ .driver_data = TYPE_VXE },
+ { }
+};
+
+static struct hid_driver pulsar_driver = {
+ .name = "pulsar",
+ .id_table = pulsar_table,
+ .probe = pulsar_probe,
+ .remove = pulsar_remove,
+ .raw_event = pulsar_raw_event,
+};
+
+module_hid_driver(pulsar_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID driver for pulsar mice");
+MODULE_AUTHOR("Nikolas Koesling");
+MODULE_DEVICE_TABLE(hid, pulsar_table);
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox