Linux Input/HID development
 help / color / mirror / Atom feed
* Re: [PATCH v11 1/3] HID: nintendo: Add preliminary Switch 2 controller driver
From: Silvan Jegen @ 2026-07-04 19:36 UTC (permalink / raw)
  To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260702214704.1859350-2-vi@endrift.com>

Heyhey!

Some comments and questions 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 designed, along with
> bringup 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                    | 1278 ++++++++++++++++-
>  drivers/hid/hid-nintendo.h                    |   72 +
>  drivers/input/joystick/Kconfig                |   11 +
>  drivers/input/joystick/Makefile               |    1 +
>  drivers/input/joystick/nintendo-switch2-usb.c |  468 ++++++
>  8 files changed, 1836 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 4ecd282f8f52..778982ab298e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19051,6 +19051,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 f9bcaeb66385..19c77c323ec9 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -843,10 +843,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 1059922baaac..9ba62b8fb894 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -1103,6 +1103,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 e7302ec01ff1..e21c36921832 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:
> @@ -2662,7 +2669,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);
> @@ -2673,7 +2680,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;
> @@ -2777,7 +2784,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;
> @@ -2796,7 +2803,7 @@ static void nintendo_hid_remove(struct hid_device *hdev)
>  	hid_hw_stop(hdev);
>  }
>  
> -static int nintendo_hid_resume(struct hid_device *hdev)
> +static int joycon_resume(struct hid_device *hdev)
>  {
>  	struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
>  	int ret;
> @@ -2819,7 +2826,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);
>  
> @@ -2838,7 +2845,1208 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
>  	return 0;
>  }
>  
> +/*
> + * =============================================================================
> + * 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)

Two tabs instead of one?


> +#define NS2_FEATURE_MOUSE	BIT(4)

This one doesn't seem to be used (yet). Not sure if that is intended
or not.

> +#define NS2_FEATURE_RUMBLE	BIT(5)
> +#define NS2_FEATURE_MAGNETO	BIT(7)

Ditto.

> +
> +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_INPUT,
> +	NS2_INIT_SET_PLAYER_LEDS,
> +	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;
> +	struct kref refcount;
> +
> +	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;
> +	int init_retries;
> +	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;
> +};
> +
> +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 int switch2_init_controller(struct switch2_controller *ns2);
> +
> +static void switch2_init_step_done(struct switch2_controller *ns2, enum switch2_init_step step)
> +{
> +	if (ns2->init_step != step)
> +		return;
> +
> +	ns2->init_retries = 0;
> +	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 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) {
> +			if (kref_get_unless_zero(&ns2->refcount))
> +				return ns2;
> +		}
> +	}
> +	ns2 = kzalloc(sizeof(*ns2), GFP_KERNEL);
> +	if (!ns2)
> +		return ERR_PTR(-ENOMEM);
> +
> +	kref_init(&ns2->refcount);
> +	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;
> +
> +	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;
> +	mutex_unlock(&ns2->lock);
> +
> +	if (input)
> +		input_unregister_device(input);
> +}
> +
> +static void switch2_kref_put(struct kref *refcount)
> +{
> +	struct switch2_controller *ns2 = container_of(refcount,
> +					struct switch2_controller, refcount);
> +
> +	guard(mutex)(&switch2_controllers_lock);
> +	list_del_init(&ns2->entry);
> +	mutex_destroy(&ns2->lock);
> +	kfree(ns2);
> +}
> +
> +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 input_dev *input = to_input_dev(dev);
> +	struct switch2_controller *ns2 = input_get_drvdata(input);
> +
> +	if (!ns2)
> +		return -ENODEV;
> +
> +	guard(mutex)(&ns2->lock);
> +	return switch2_set_leds(ns2);
> +}
> +
> +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_input_ref(struct input_dev *input)
> +{
> +	struct switch2_controller *ns2 = input_get_drvdata(input);
> +
> +	kref_get(&ns2->refcount);
> +
> +	return 0;
> +}
> +
> +static void switch2_input_deref(struct input_dev *input)
> +{
> +	struct switch2_controller *ns2 = input_get_drvdata(input);
> +
> +	kref_put(&ns2->refcount, switch2_kref_put);
> +}
> +
> +static int switch2_init_input(struct switch2_controller *ns2)
> +{
> +	struct input_dev *input;
> +	struct hid_device *hdev = ns2->hdev;
> +	int player_led_pattern;
> +	int i;
> +	int ret;
> +
> +	rcu_read_lock();
> +	input = rcu_dereference(ns2->input);
> +	rcu_read_unlock();
> +
> +	if (input) {
> +		switch2_init_step_done(ns2, NS2_INIT_INPUT);
> +		return 0;
> +	}
> +
> +	input = input_allocate_device();
> +	if (!input)
> +		return -ENOMEM;
> +
> +	input_set_drvdata(input, ns2);
> +	input->open = switch2_input_ref;
> +	input->close = switch2_input_deref;
> +	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);
> +		input_free_device(input);
> +		return ret;
> +	}
> +
> +	player_led_pattern = ns2->player_id % JC_NUM_LED_PATTERNS;
> +	hid_dbg(hdev, "assigned player %d led pattern", player_led_pattern + 1);
> +
> +	ns2->leds = devm_kcalloc(&input->dev, JC_NUM_LEDS, sizeof(*ns2->leds), GFP_KERNEL);
> +	if (!ns2->leds) {
> +		hid_err(ns2->hdev, "Failed to allocate LEDs\n");
> +		input_unregister_device(input);
> +		return -ENOMEM;
> +	}
> +
> +	for (i = 0; i < JC_NUM_LEDS; i++) {
> +		struct led_classdev *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 | LED_RETAIN_AT_SHUTDOWN;
> +		char *name = devm_kasprintf(&input->dev, GFP_KERNEL, "%s:%s:%s",
> +				      dev_name(&input->dev),
> +				      "green",
> +				      joycon_player_led_names[i]);
> +
> +		if (!name) {
> +			dev_err(&input->dev, "Failed to allocate name for player %d LED; ret=%d\n",
> +				i + 1, ret);
> +			break;
> +		}
> +
> +		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);
> +			break;
> +		}
> +	}
> +
> +	rcu_assign_pointer(ns2->input, input);
> +	synchronize_rcu();
> +
> +	switch2_init_step_done(ns2, NS2_INIT_INPUT);
> +	return switch2_init_controller(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 (ns2->hdev) {
> +			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 (ns2->hdev) {
> +			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];
> +
> +			if (ns2->hdev) {
> +				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 if (ns2->hdev) {
> +			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 (get_unaligned_le16((__le16 *)data) != NS2_USER_CALIB_MAGIC) {
> +			if (ns2->hdev)
> +				hid_dbg(ns2->hdev, "No user primary stick calibration present\n");
> +			break;
> +		}
> +
> +		ok = switch2_parse_stick_calibration(&ns2->stick_calib[0], &data[2]);
> +		if (ns2->hdev) {
> +			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 (get_unaligned_le16((__le16 *)data) != NS2_USER_CALIB_MAGIC) {
> +			if (ns2->hdev)
> +				hid_dbg(ns2->hdev, "No user secondary stick calibration present\n");
> +			break;
> +		}
> +
> +		ok = switch2_parse_stick_calibration(&ns2->stick_calib[1], &data[2]);
> +		if (ns2->hdev) {
> +			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;

nit: this break is redundant and could be removed.

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

We might want to make sure that calib->{negative,positive} are not zero
(at calibration parsing time) in order to avoid a div-by-zero issue

> +		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);
> +
> +	if (zero != 232)
> +		value /= (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)

From what I can tell switch2_features_enable is only called from
switch2_init_controller, which is already checking that ns2->cfg is not
NULL (while holding the ns2->lock). So I would assume that this check is
not necessary

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

See above.

> +	put_unaligned_le32(address, &message[4]);
> +	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);
> +}
> +
> +int switch2_init_controller(struct switch2_controller *ns2)
> +{
> +	if (ns2->init_step == NS2_INIT_DONE)
> +		return 0;
> +
> +	if (!ns2->cfg)
> +		return -ENOTCONN;
> +
> +	if (ns2->init_retries > NS2_MAX_INIT_RETRIES) {
> +		if (ns2->init_retries == NS2_MAX_INIT_RETRIES + 1) {

Couldn't we get rid of this if-clause and the ns2->init_retries++ after
it? That would mean we would potentially log the error message earlier
but I don't think that would be an issue


> +			if (ns2->cfg)

We check this a few lines up so unless this somehow can get set
to NULL again (even though we seem to be holding ns2->lock whenever
switch2_init_controller is called) I don't think we need to check this
a second time

> +				dev_err(ns2->cfg->dev, "Failed to configure controller\n");
> +			ns2->init_retries++;
> +		}
> +		return -EIO;
> +	}
> +
> +	ns2->init_retries++;
> +	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_step);
> +			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_step);
> +			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_step);
> +			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_step);
> +			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_step);
> +			return switch2_init_controller(ns2);
> +		}
> +	case NS2_INIT_INPUT:
> +		if (ns2->hdev)
> +			return switch2_init_input(ns2);
> +		break;
> +	case NS2_INIT_SET_PLAYER_LEDS:
> +		return switch2_set_player_id(ns2, ns2->player_id);
> +	case NS2_INIT_FINISH:
> +		return ns2->cfg->send_command(NS2_CMD_INIT, NS2_SUBCMD_INIT_USB,
> +			switch2_init_cmd_data, sizeof(switch2_init_cmd_data), ns2->cfg);
> +	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)

Shouldn't this check for 'length < 9' as otherwise

message = &message[8];

would index the ninth byte which is out of range (i.e. off-by-one issue)?


> +		return -EINVAL;
> +
> +	print_hex_dump_debug("got cmd: ", DUMP_PREFIX_OFFSET, 16, 1, message, length, false);
> +
> +	mutex_lock(&ns2->lock);
> +
> +	header = (const struct switch2_cmd_header *)message;
> +	if (!(header->flags & NS2_FLAG_OK)) {
> +		if (ns2->cfg)
> +			dev_warn(ns2->cfg->dev, "Packet error %02x replying to command %x:%x",
> +				header->flags, header->command, header->subcommand);
> +		ret = -EIO;
> +		goto exit;
> +	}
> +	message = &message[8];
> +	length -= 8;

Shouldn't this be 'length -= 9' (as arrays are zero-indexed)?

> +
> +	switch (header->command) {
> +	case NS2_CMD_FLASH:
> +		if (header->subcommand == NS2_SUBCMD_FLASH_READ) {
> +			uint8_t read_size;
> +			uint32_t read_address;
> +
> +			if (length < 8) {

Shouldn't this be 'if (length < 9)' as well?

> +				ret = -EINVAL;
> +				goto exit;
> +			}
> +			read_size = message[0];
> +			read_address = get_unaligned_le32(&message[4]);
> +			if (length < read_size + 8) {

Should it check for

if (length < read_size + 9) {

as we are taking a pointer to the ninth byte with '&message[8]'?


> +				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_FINISH);
> +		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);
> +
> +	mutex_unlock(&ns2->lock);
> +	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);
> +	int ret = 0;
> +
> +	if (IS_ERR(ns2))
> +		return PTR_ERR(ns2);
> +
> +	mutex_lock(&ns2->lock);
> +	if (ns2->cfg) {
> +		ret = -EBUSY;
> +		goto out;
> +	}
> +	cfg->parent = ns2;
> +	ns2->cfg = cfg;
> +
> +	if (ns2->hdev)
> +		ret = switch2_init_controller(ns2);
> +
> +	if (ret < 0) {
> +		cfg->parent = NULL;
> +		ns2->cfg = NULL;
> +	}
> +
> +out:
> +	mutex_unlock(&ns2->lock);
> +
> +	if (ret < 0)
> +		kref_put(&ns2->refcount, switch2_kref_put);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(switch2_controller_attach_cfg);
> +
> +void switch2_controller_detach_cfg(struct switch2_controller *ns2)
> +{
> +	mutex_lock(&ns2->lock);
> +	if (!ns2->cfg || WARN_ON(ns2 != ns2->cfg->parent)) {
> +		mutex_unlock(&ns2->lock);
> +		return;
> +	}
> +	ns2->cfg->parent = NULL;
> +	ns2->cfg = NULL;
> +	mutex_unlock(&ns2->lock);
> +	switch2_controller_put(ns2);
> +	kref_put(&ns2->refcount, switch2_kref_put);
> +}
> +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;
> +	}
> +
> +	ns2 = switch2_get_controller(phys);
> +	if (IS_ERR(ns2))
> +		return PTR_ERR(ns2);
> +
> +	mutex_lock(&ns2->lock);
> +	if (ns2->hdev) {
> +		mutex_unlock(&ns2->lock);
> +		hid_err(hdev,
> +			"Second hdev tried to claim same controller, first=%p vs second=%p\n",
> +			ns2->hdev, hdev);
> +		kref_put(&ns2->refcount, switch2_kref_put);
> +		return -EBUSY;
> +	}
> +	ns2->hdev = hdev;
> +	hid_set_drvdata(hdev, ns2);
> +
> +	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;
> +
> +	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
> +	if (ret) {
> +		hid_err(hdev, "hw_start failed %d\n", ret);
> +		goto err_cleanup;
> +	}
> +
> +	ret = hid_hw_open(hdev);
> +	if (ret) {
> +		hid_err(hdev, "hw_open failed %d\n", ret);
> +		goto err_stop;
> +	}
> +
> +	ret = 0;
> +	if (ns2->cfg)
> +		ret = switch2_init_controller(ns2);
> +
> +	if (!ret) {
> +		mutex_unlock(&ns2->lock);
> +		return 0;
> +	}
> +
> +	hid_hw_close(hdev);
> +err_stop:
> +	hid_hw_stop(hdev);
> +err_cleanup:
> +	ida_free(&nintendo_player_id_allocator, ns2->player_id);
> +	ns2->hdev = NULL;
> +	mutex_unlock(&ns2->lock);
> +	switch2_controller_put(ns2);
> +	kref_put(&ns2->refcount, switch2_kref_put);
> +
> +	return ret;
> +}
> +
> +static void switch2_remove(struct hid_device *hdev)
> +{
> +	struct switch2_controller *ns2 = hid_get_drvdata(hdev);
> +
> +	switch2_controller_put(ns2);
> +	mutex_lock(&ns2->lock);
> +	ns2->hdev = NULL;
> +	ida_free(&nintendo_player_id_allocator, ns2->player_id);
> +	mutex_unlock(&ns2->lock);
> +	kref_put(&ns2->refcount, switch2_kref_put);
> +	hid_hw_close(hdev);
> +	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,
> @@ -2863,10 +4071,67 @@ static const struct hid_device_id nintendo_hid_devices[] = {
>  			 USB_DEVICE_ID_NINTENDO_N64CON) },
>  	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HORI,
>  			 USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD) },
> +	/* 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);
> +}
> +
> +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);
> +}
> +
>  static struct hid_driver nintendo_hid_driver = {
>  	.name		= "nintendo",
>  	.id_table	= nintendo_hid_devices,
> @@ -2894,4 +4159,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 000000000000..7aff22f30266
> --- /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 7755e5b454d2..868262c6ccd9 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 9976f596a920..8f92900ae885 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 000000000000..a6999a0a26ae
> --- /dev/null
> +++ b/drivers/input/joystick/nintendo-switch2-usb.c
> @@ -0,0 +1,468 @@
> +// 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;
> +
> +enum switch2_urb_state {
> +	NS2_URB_FREE,
> +	NS2_URB_OUT,
> +	NS2_URB_IN,
> +};
> +
> +struct switch2_urb {
> +	struct urb *urb;
> +	uint8_t *data;
> +	enum switch2_urb_state state;
> +};
> +
> +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;
> +	bool shutdown;
> +	spinlock_t bulk_in_lock;
> +
> +	struct switch2_urb bulk_out[NS2_OUT_URBS];
> +	struct usb_anchor bulk_out_anchor;
> +	spinlock_t bulk_out_lock;
> +
> +	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:
> +	case -EPIPE:
> +		break;
> +	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) {
> +			if (schedule) {
> +				ns2_usb->bulk_in[i].state = NS2_URB_IN;
> +				continue;
> +			} else {

nit: this else is redundant as we are continue-ing above anyways.

> +				ns2_usb->bulk_in[i].state = NS2_URB_FREE;
> +			}
> +		}
> +
> +		if (ns2_usb->bulk_in[i].state != NS2_URB_FREE)
> +			continue;
> +
> +		/*
> +		 * We want exactly one bulk in URB scheduled at a time, so only
> +		 * reschedule this immediately if nothing else is scheduled
> +		 * currently.
> +		 */
> +		if (!usb_anchor_empty(&ns2_usb->bulk_in_anchor) || ns2_usb->shutdown)
> +			continue;
> +
> +		ns2_urb = &ns2_usb->bulk_in[i];
> +		if (!ns2_urb)

AFAICT ns2_urb will never be NULL here so we can remove this check.

> +			continue;
> +
> +		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->state = NS2_URB_OUT;
> +		}
> +	}
> +	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:
> +	case -EPIPE:
> +		break;
> +	default:
> +		dev_dbg(&ns2_usb->udev->dev, "unknown output urb status: %d\n", urb->status);
> +		break;
> +	}
> +
> +	for (i = 0; i < NS2_OUT_URBS; i++) {
> +		if (ns2_usb->bulk_out[i].urb != urb)
> +			continue;
> +
> +		ns2_usb->bulk_out[i].state = NS2_URB_FREE;
> +		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].state != NS2_URB_FREE)
> +			continue;
> +
> +		urb = &ns2_usb->bulk_out[i];
> +		urb->state = NS2_URB_OUT;
> +		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_KERNEL);
> +	if (ret) {
> +		if (ret != -ENODEV)
> +			dev_warn(&ns2_usb->udev->dev, "failed to submit output urb: %i", ret);
> +		spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> +		urb->state = NS2_URB_FREE;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +		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;
> +	int i;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +	for (i = 0; i < NS2_IN_URBS; i++) {
> +		urb = &ns2_usb->bulk_in[i];
> +		if (urb->state != NS2_URB_IN)
> +			continue;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> +		if (ns2_usb->cfg.parent) {
> +			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);
> +		} else {
> +			dev_err(&ns2_usb->udev->dev,
> +				"Got message before controller is fully set up; discarding\n");
> +		}
> +
> +		spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +		urb->state = NS2_URB_FREE;
> +		/*
> +		 * We want exactly one bulk in URB scheduled at a time, so only
> +		 * reschedule this immediately if nothing else is scheduled
> +		 * currently.
> +		 */
> +		if (!usb_anchor_empty(&ns2_usb->bulk_in_anchor) || ns2_usb->shutdown)
> +			continue;
> +
> +		usb_anchor_urb(urb->urb, &ns2_usb->bulk_in_anchor);
> +		err = usb_submit_urb(urb->urb, GFP_ATOMIC);
> +		if (err) {
> +			usb_unanchor_urb(urb->urb);
> +			dev_dbg(&ns2_usb->udev->dev,
> +				"failed to queue input urb: %d\n", err);
> +		} else {
> +			urb->state = NS2_URB_OUT;
> +		}
> +	}
> +	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;
> +	struct urb *urb;
> +	uint8_t *data;
> +	char phys[64];
> +	int ret;
> +	int i;
> +	unsigned long flags;
> +
> +	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;
> +
> +	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);
> +
> +	ns2_usb->udev = udev;
> +	for (i = 0; i < NS2_IN_URBS; i++) {
> +		urb = usb_alloc_urb(0, GFP_KERNEL);
> +		if (!urb) {
> +			ret = -ENOMEM;
> +			goto err_free_in;
> +		}
> +
> +		data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
> +			&urb->transfer_dma);
> +		if (!data) {
> +			usb_free_urb(urb);
> +			ret = -ENOMEM;
> +			goto err_free_in;
> +		}
> +
> +		spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +		usb_fill_bulk_urb(urb, udev,
> +			usb_rcvbulkpipe(udev, bulk_in->bEndpointAddress),
> +			data, NS2_BULK_SIZE, switch2_bulk_in, ns2_usb);
> +		urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +
> +		ns2_usb->bulk_in[i].urb = urb;
> +		ns2_usb->bulk_in[i].data = data;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +	}
> +
> +	for (i = 0; i < NS2_OUT_URBS; i++) {
> +		urb = usb_alloc_urb(0, GFP_KERNEL);
> +		if (!urb) {
> +			ret = -ENOMEM;
> +			goto err_free_out;
> +		}
> +
> +		data = usb_alloc_coherent(udev, NS2_BULK_SIZE, GFP_KERNEL,
> +			&urb->transfer_dma);
> +		if (!data) {
> +			usb_free_urb(urb);
> +			ret = -ENOMEM;
> +			goto err_free_out;
> +		}
> +
> +		spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> +		usb_fill_bulk_urb(urb, udev,
> +			usb_sndbulkpipe(udev, bulk_out->bEndpointAddress),
> +			data, NS2_BULK_SIZE, switch2_bulk_out, ns2_usb);
> +		urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
> +
> +		ns2_usb->bulk_out[i].urb = urb;
> +		ns2_usb->bulk_out[i].data = data;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +	}
> +
> +	usb_set_intfdata(intf, ns2_usb);
> +
> +	ns2_usb->cfg.dev = &ns2_usb->udev->dev;
> +	ns2_usb->cfg.send_command = switch2_usb_send_cmd;
> +
> +	spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +	ns2_usb->bulk_in[0].state = NS2_URB_OUT;
> +	usb_anchor_urb(ns2_usb->bulk_in[0].urb, &ns2_usb->bulk_in_anchor);
> +	ret = usb_submit_urb(ns2_usb->bulk_in[0].urb, GFP_ATOMIC);
> +	spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> +	if (ret < 0)
> +		goto err_free_out;
> +
> +	ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
> +	if (ret < 0)
> +		goto err_free_out;
> +
> +	return 0;
> +
> +err_free_out:
> +	usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
> +	for (i = 0; i < NS2_OUT_URBS; i++) {
> +		spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> +		urb = ns2_usb->bulk_out[i].urb;
> +		data = ns2_usb->bulk_out[i].data;
> +		if (!urb) {
> +			spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +			continue;
> +		}
> +
> +		ns2_usb->bulk_out[i].urb = NULL;
> +		ns2_usb->bulk_out[i].data = NULL;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +
> +		usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
> +		usb_free_urb(urb);
> +	}
> +err_free_in:
> +	spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +	ns2_usb->shutdown = true;
> +	spin_unlock_irqrestore(&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++) {
> +		spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +		urb = ns2_usb->bulk_in[i].urb;
> +		data = ns2_usb->bulk_in[i].data;
> +		if (!urb) {
> +			spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +			continue;
> +		}
> +
> +		ns2_usb->bulk_in[i].urb = NULL;
> +		ns2_usb->bulk_in[i].data = NULL;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> +		usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
> +		usb_free_urb(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;
> +	struct urb *urb;
> +	uint8_t *data;
> +	int i;
> +
> +	/* Prevent any further IN URBs from being scheduled */
> +	spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +	ns2_usb->shutdown = true;
> +	spin_unlock_irqrestore(&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++) {
> +		spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
> +		urb = ns2_usb->bulk_in[i].urb;

In other clean-up cases we are checking this to be NULL. We might have
to do so here as well unless we know that it will never be NULL on
switch2_usb_disconnect (otherwise we will have a NULL pointer deref a
few lines down).

> +		data = ns2_usb->bulk_in[i].data;
> +		ns2_usb->bulk_in[i].urb = NULL;
> +		ns2_usb->bulk_in[i].data = NULL;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
> +
> +		usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
> +		usb_free_urb(urb);
> +	}
> +
> +	/*
> +	 * We need to detach *before* we kill the out URBs to make sure no
> +	 * further URBs get scheduled by the HID endpoint in the meantime.
> +	 */
> +	switch2_controller_detach_cfg(ns2_usb->cfg.parent);
> +
> +	usb_kill_anchored_urbs(&ns2_usb->bulk_out_anchor);
> +	for (i = 0; i < NS2_OUT_URBS; i++) {
> +		spin_lock_irqsave(&ns2_usb->bulk_out_lock, flags);
> +		urb = ns2_usb->bulk_out[i].urb;

See above.

> +		data = ns2_usb->bulk_out[i].data;
> +		ns2_usb->bulk_out[i].urb = NULL;
> +		ns2_usb->bulk_out[i].data = NULL;
> +		spin_unlock_irqrestore(&ns2_usb->bulk_out_lock, flags);
> +
> +		usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
> +		usb_free_urb(urb);
> +	}
> +}
> +
> +#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		= "nintendo-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");

Thanks for all the work on this!

Cheers,
Silvan

^ permalink raw reply

* Re: [PATCH v11 2/3] HID: nintendo: Add rumble support for Switch 2 controllers
From: Silvan Jegen @ 2026-07-05 14:13 UTC (permalink / raw)
  To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260702214704.1859350-3-vi@endrift.com>

Heyhey!

Just one comment below.

Vicki Pfau <vi@endrift.com> wrote:
> This adds rumble support for both the "HD Rumble" linear resonant actuator
> type as used in the Joy-Cons and Pro Controller, as well as the eccentric
> rotating mass type used in the GameCube controller. Note that since there's
> currently no API for exposing full control of LRAs with evdev, it only
> simulates a basic rumble for now.
> 
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> ---
>  drivers/hid/Kconfig        |   8 +-
>  drivers/hid/hid-nintendo.c | 211 ++++++++++++++++++++++++++++++++++++-
>  2 files changed, 213 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 19c77c323ec9..851eed76c236 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -859,10 +859,10 @@ config NINTENDO_FF
>  	depends on HID_NINTENDO
>  	select INPUT_FF_MEMLESS
>  	help
> -	Say Y here if you have a Nintendo Switch controller and want to enable
> -	force feedback support for it. This works for both joy-cons, the pro
> -	controller, and the NSO N64 controller. For the pro controller, both
> -	rumble motors can be controlled individually.
> +	Say Y here if you have a Nintendo Switch or Switch 2 controller and want
> +	to enable force feedback support for it. This works for Joy-Cons, the Pro
> +	Controllers, and the NSO N64 and GameCube controller. For the Pro
> +	Controller, both rumble motors can be controlled individually.
>  
>  config HID_NTI
>  	tristate "NTI keyboard adapters"
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index e21c36921832..a36f4fd9a1da 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -37,6 +37,7 @@
>  #include <linux/unaligned.h>
>  #include <linux/delay.h>
>  #include <linux/device.h>
> +#include <linux/devm-helpers.h>
>  #include <linux/kernel.h>
>  #include <linux/hid.h>
>  #include <linux/idr.h>
> @@ -2989,6 +2990,7 @@ enum switch2_init_step {
>  	NS2_INIT_READ_USER_SECONDARY_CALIB,
>  	NS2_INIT_SET_FEATURE_MASK,
>  	NS2_INIT_ENABLE_FEATURES,
> +	NS2_INIT_ENABLE_RUMBLE,
>  	NS2_INIT_GRIP_BUTTONS,
>  	NS2_INIT_REPORT_FORMAT,
>  	NS2_INIT_INPUT,
> @@ -3020,6 +3022,18 @@ struct switch2_stick_calibration {
>  	struct switch2_axis_calibration y;
>  };
>  
> +struct switch2_hd_rumble {
> +	uint16_t hi_freq : 10;
> +	uint16_t hi_amp : 10;
> +	uint16_t lo_freq : 10;
> +	uint16_t lo_amp : 10;
> +} __packed;
> +
> +struct switch2_erm_rumble {
> +	uint16_t error;
> +	uint16_t amplitude;
> +};
> +
>  struct switch2_controller {
>  	struct hid_device *hdev;
>  	struct switch2_cfg_intf *cfg;
> @@ -3043,8 +3057,45 @@ struct switch2_controller {
>  
>  	uint32_t player_id;
>  	struct led_classdev *leds;
> +
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> +	spinlock_t rumble_lock;
> +	uint8_t rumble_seq;
> +	union {
> +		struct switch2_hd_rumble hd;
> +		struct switch2_erm_rumble sd;
> +	} rumble;
> +	uint64_t last_rumble_work;
> +	struct delayed_work rumble_work;
> +	uint8_t *rumble_buffer;
> +#endif
>  };
>  
> +enum gc_rumble {
> +	GC_RUMBLE_OFF = 0,
> +	GC_RUMBLE_ON = 1,
> +	GC_RUMBLE_STOP = 2,
> +};
> +
> +/*
> + * The highest rumble level for "HD Rumble" is strong enough to potentially damage the controller,
> + * and also leaves your hands feeling like melted jelly, so we set a semi-arbitrary scaling factor
> + * to artificially limit the maximum for safety and comfort. It is currently unknown if the Switch
> + * 2 itself does something similar, but it's quite likely.
> + *
> + * This value must be between 0 and 1024, otherwise the math below will overflow.
> + */
> +#define RUMBLE_MAX 450u
> +
> +/*
> + * Semi-arbitrary values used to simulate the "rumble" sensation of an eccentric rotating
> + * mass type haptic motor on the Switch 2 controllers' linear resonant actuator type haptics.
> + *
> + * The units used are unknown, but the values must be between 0 and 1023.
> + */
> +#define RUMBLE_HI_FREQ 0x187
> +#define RUMBLE_LO_FREQ 0x112
> +
>  static DEFINE_MUTEX(switch2_controllers_lock);
>  static LIST_HEAD(switch2_controllers);
>  
> @@ -3136,7 +3187,7 @@ static const uint8_t switch2_init_cmd_data[] = {
>  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,
> +	NS2_FEATURE_BUTTONS | NS2_FEATURE_ANALOG | NS2_FEATURE_IMU | NS2_FEATURE_RUMBLE,
>  	0x00, 0x00, 0x00
>  };
>  
> @@ -3209,6 +3260,125 @@ static void switch2_kref_put(struct kref *refcount)
>  	kfree(ns2);
>  }
>  
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> +static void switch2_encode_rumble(struct switch2_hd_rumble *rumble, uint8_t buffer[5])
> +{
> +	buffer[0] = rumble->hi_freq;
> +	buffer[1] = (rumble->hi_freq >> 8) | (rumble->hi_amp << 2);
> +	buffer[2] = (rumble->hi_amp >> 6) | (rumble->lo_freq << 4);
> +	buffer[3] = (rumble->lo_freq >> 4) | (rumble->lo_amp << 6);
> +	buffer[4] = rumble->lo_amp >> 2;
> +}
> +
> +static int switch2_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
> +{
> +	struct switch2_controller *ns2 = input_get_drvdata(dev);

We might want to check ns2 for NULL here (like we do in
switch2_player_led_brightness_set).

Cheers,
Silvan

> +	unsigned long flags;
> +
> +	if (effect->type != FF_RUMBLE)
> +		return 0;
> +
> +	spin_lock_irqsave(&ns2->rumble_lock, flags);
> +	if (ns2->ctlr_type == NS2_CTLR_TYPE_GC) {
> +		ns2->rumble.sd.amplitude = max(effect->u.rumble.strong_magnitude,
> +			(uint16_t) (effect->u.rumble.weak_magnitude >> 1));
> +	} else {
> +		ns2->rumble.hd.hi_freq = RUMBLE_HI_FREQ;
> +		ns2->rumble.hd.lo_freq = RUMBLE_LO_FREQ;
> +		ns2->rumble.hd.hi_amp = effect->u.rumble.weak_magnitude * RUMBLE_MAX >> 16;
> +		ns2->rumble.hd.lo_amp = effect->u.rumble.strong_magnitude * RUMBLE_MAX >> 16;
> +	}
> +	spin_unlock_irqrestore(&ns2->rumble_lock, flags);
> +
> +	schedule_delayed_work(&ns2->rumble_work, 0);
> +
> +	return 0;
> +}
> +
> +static void switch2_rumble_work(struct work_struct *work)
> +{
> +	struct switch2_controller *ns2 = container_of(to_delayed_work(work),
> +						      struct switch2_controller, rumble_work);
> +	unsigned long flags;
> +	bool active;
> +	int ret = 0;
> +
> +	spin_lock_irqsave(&ns2->rumble_lock, flags);
> +	ns2->rumble_buffer[0x1] = 0x50 | ns2->rumble_seq;
> +	if (ns2->ctlr_type == NS2_CTLR_TYPE_GC) {
> +		ns2->rumble_buffer[0] = 3;
> +		if (ns2->rumble.sd.amplitude == 0) {
> +			ns2->rumble_buffer[2] = GC_RUMBLE_STOP;
> +			ns2->rumble.sd.error = 0;
> +			active = false;
> +		} else {
> +			if (ns2->rumble.sd.error < ns2->rumble.sd.amplitude) {
> +				ns2->rumble_buffer[2] = GC_RUMBLE_ON;
> +				ns2->rumble.sd.error += U16_MAX - ns2->rumble.sd.amplitude;
> +			} else {
> +				ns2->rumble_buffer[2] = GC_RUMBLE_OFF;
> +				ns2->rumble.sd.error -= ns2->rumble.sd.amplitude;
> +			}
> +			active = true;
> +		}
> +	} else {
> +		ns2->rumble_buffer[0] = 1;
> +		switch2_encode_rumble(&ns2->rumble.hd, &ns2->rumble_buffer[0x2]);
> +		active = ns2->rumble.hd.hi_amp || ns2->rumble.hd.lo_amp;
> +		if (ns2->ctlr_type == NS2_CTLR_TYPE_PRO) {
> +			/*
> +			 * The Pro Controller contains separate LRAs on each
> +			 * side that can be controlled individually.
> +			 */
> +			ns2->rumble_buffer[0] = 2;
> +			ns2->rumble_buffer[0x11] = 0x50 | ns2->rumble_seq;
> +			switch2_encode_rumble(&ns2->rumble.hd, &ns2->rumble_buffer[0x12]);
> +		}
> +	}
> +	ns2->rumble_seq = (ns2->rumble_seq + 1) & 0xF;
> +	spin_unlock_irqrestore(&ns2->rumble_lock, flags);
> +
> +	if (active) {
> +		unsigned long interval = msecs_to_jiffies(4);
> +		uint64_t current_jiffies = get_jiffies_64();
> +
> +		if (!ns2->last_rumble_work)
> +			ns2->last_rumble_work = current_jiffies;
> +		else
> +			ns2->last_rumble_work += interval;
> +
> +		/* Reschedule a little early to make sure the buffer never underruns */
> +		interval -= msecs_to_jiffies(2);
> +		if (ns2->last_rumble_work + interval >= current_jiffies)
> +			schedule_delayed_work(&ns2->rumble_work,
> +				ns2->last_rumble_work + interval - current_jiffies);
> +		else
> +			schedule_delayed_work(&ns2->rumble_work, 0);
> +	} else {
> +		ns2->last_rumble_work = 0;
> +	}
> +
> +	mutex_lock(&ns2->lock);
> +	if (!ns2->hdev) {
> +		cancel_delayed_work(&ns2->rumble_work);
> +	} else {
> +		ret = hid_hw_output_report(ns2->hdev, ns2->rumble_buffer, 64);
> +		/*
> +		 * Don't log on ENODEV, ESHUTDOWN, or EPROTO, which can happen
> +		 * mid-hotplug. Also cancel any further work on ENODEV or
> +		 * ESHUTDOWN as they're clear indications that the endpoint
> +		 * is dead.
> +		 */
> +		if (ret == -ENODEV || ret == -ESHUTDOWN)
> +			cancel_delayed_work(&ns2->rumble_work);
> +		else if (ret < 0 && ret != -EPROTO)
> +			hid_warn_ratelimited(ns2->hdev,
> +				"Failed to send output report ret=%d\n", ret);
> +	}
> +	mutex_unlock(&ns2->lock);
> +}
> +#endif
> +
>  static int switch2_set_leds(struct switch2_controller *ns2)
>  {
>  	int i;
> @@ -3332,6 +3502,26 @@ static int switch2_init_input(struct switch2_controller *ns2)
>  		return -EINVAL;
>  	}
>  
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> +	ns2->rumble_buffer = devm_kzalloc(&input->dev, 64, GFP_KERNEL);
> +	if (!ns2->rumble_buffer) {
> +		input_free_device(input);
> +		return -ENOMEM;
> +	}
> +	ret = devm_delayed_work_autocancel(&input->dev, &ns2->rumble_work, switch2_rumble_work);
> +	if (ret < 0) {
> +		input_free_device(input);
> +		return ret;
> +	}
> +
> +	input_set_capability(input, EV_FF, FF_RUMBLE);
> +	ret = input_ff_create_memless(input, NULL, switch2_play_effect);
> +	if (ret) {
> +		input_free_device(input);
> +		return ret;
> +	}
> +#endif
> +
>  	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)
> @@ -3765,7 +3955,16 @@ int switch2_init_controller(struct switch2_controller *ns2)
>  		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);
> +		return switch2_features_enable(ns2, NS2_FEATURE_BUTTONS |
> +			NS2_FEATURE_ANALOG | NS2_FEATURE_RUMBLE);
> +	case NS2_INIT_ENABLE_RUMBLE:
> +		/*
> +		 * It is unclear what this packet is supposed to be for, but it
> +		 * appears to be needed for rumble to work reliably. The reply
> +		 * data indicates it might be a query of some sort, but we
> +		 * ignore the reply so long as it doesn't return an error.
> +		 */
> +		return ns2->cfg->send_command(0x11, 1, NULL, 0, ns2->cfg);
>  	case NS2_INIT_GRIP_BUTTONS:
>  		if (!switch2_ctlr_is_joycon(ns2->ctlr_type)) {
>  			switch2_init_step_done(ns2, ns2->init_step);
> @@ -3878,6 +4077,10 @@ int switch2_receive_command(struct switch2_controller *ns2,
>  			switch2_init_step_done(ns2, NS2_INIT_GET_FIRMWARE_INFO);
>  		}
>  		break;
> +	case 0x11:
> +		if (header->subcommand == 1)
> +			switch2_init_step_done(ns2, NS2_INIT_ENABLE_RUMBLE);
> +		break;
>  	default:
>  		break;
>  	}
> @@ -3997,6 +4200,10 @@ static int switch2_probe(struct hid_device *hdev, const struct hid_device_id *id
>  	else
>  		ns2->player_id = ret;
>  
> +#if IS_ENABLED(CONFIG_NINTENDO_FF)
> +	spin_lock_init(&ns2->rumble_lock);
> +#endif
> +
>  	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
>  	if (ret) {
>  		hid_err(hdev, "hw_start failed %d\n", ret);



^ permalink raw reply

* Re: [PATCH v11 3/3] HID: nintendo: Add unified report format support
From: Silvan Jegen @ 2026-07-05 14:27 UTC (permalink / raw)
  To: Vicki Pfau; +Cc: Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires, linux-input
In-Reply-To: <20260702214704.1859350-4-vi@endrift.com>

Vicki Pfau <vi@endrift.com> wrote:
> This adds support for the "unified" report format that all controllers also
> support, which has overlapping fields for like buttons and axes between
> them.
> 
> Signed-off-by: Vicki Pfau <vi@endrift.com>

This LGTM!

Reviewed-by: Silvan Jegen <s.jegen@gmail.com>

> ---
>  drivers/hid/hid-nintendo.c | 151 +++++++++++++++++++++++++++++++++++--
>  1 file changed, 146 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index a36f4fd9a1da..ca2126f85bb3 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
> @@ -2873,6 +2873,36 @@ static int joycon_suspend(struct hid_device *hdev, pm_message_t message)
>  #define NS2_BTN3_SR	BIT(6)
>  #define NS2_BTN3_SL	BIT(7)
>  
> +#define NS2_BTN_U1_Y	BIT(0)
> +#define NS2_BTN_U1_X	BIT(1)
> +#define NS2_BTN_U1_B	BIT(2)
> +#define NS2_BTN_U1_A	BIT(3)
> +#define NS2_BTN_U1_SR	BIT(4)
> +#define NS2_BTN_U1_SL	BIT(5)
> +#define NS2_BTN_U1_R	BIT(6)
> +#define NS2_BTN_U1_ZR	BIT(7)
> +
> +#define NS2_BTN_U2_MINUS	BIT(0)
> +#define NS2_BTN_U2_PLUS		BIT(1)
> +#define NS2_BTN_U2_RS		BIT(2)
> +#define NS2_BTN_U2_LS		BIT(3)
> +#define NS2_BTN_U2_HOME		BIT(4)
> +#define NS2_BTN_U2_CAPTURE	BIT(5)
> +#define NS2_BTN_U2_C		BIT(6)
> +
> +#define NS2_BTN_U3_DOWN		BIT(0)
> +#define NS2_BTN_U3_UP		BIT(1)
> +#define NS2_BTN_U3_RIGHT	BIT(2)
> +#define NS2_BTN_U3_LEFT		BIT(3)
> +#define NS2_BTN_U3_SR		BIT(4)
> +#define NS2_BTN_U3_SL		BIT(5)
> +#define NS2_BTN_U3_L		BIT(6)
> +#define NS2_BTN_U3_ZL		BIT(7)
> +
> +#define NS2_BTN_U4_GR		BIT(0)
> +#define NS2_BTN_U4_GL		BIT(1)
> +#define NS2_BTN_U4_HEADSET	BIT(5)
> +
>  #define NS2_BTN_JCR_HOME	BIT(0)
>  #define NS2_BTN_JCR_GR		BIT(2)
>  #define NS2_BTN_JCR_C		NS2_BTN3_C
> @@ -3121,6 +3151,22 @@ static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_mappings[
>  	{ /* sentinel */ },
>  };
>  
> +static const struct switch2_ctlr_button_mapping ns2_left_joycon_button_unified_mappings[] = {
> +	{ BTN_DPAD_LEFT,	2, NS2_BTN_U3_LEFT,	},
> +	{ BTN_DPAD_UP,		2, NS2_BTN_U3_UP,	},
> +	{ BTN_DPAD_DOWN,	2, NS2_BTN_U3_DOWN,	},
> +	{ BTN_DPAD_RIGHT,	2, NS2_BTN_U3_RIGHT,	},
> +	{ BTN_TL,		2, NS2_BTN_U3_L,	},
> +	{ BTN_TL2,		2, NS2_BTN_U3_ZL,	},
> +	{ BTN_SELECT,		1, NS2_BTN_U2_MINUS,	},
> +	{ BTN_THUMBL,		1, NS2_BTN_U2_LS,	},
> +	{ KEY_RECORD,		1, NS2_BTN_U2_CAPTURE,	},
> +	{ BTN_GRIPR,		2, NS2_BTN_U3_SL,	},
> +	{ BTN_GRIPR2,		2, NS2_BTN_U3_SR,	},
> +	{ BTN_GRIPL,		3, NS2_BTN_U4_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,		},
> @@ -3138,6 +3184,23 @@ static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_mappings
>  	{ /* sentinel */ },
>  };
>  
> +static const struct switch2_ctlr_button_mapping ns2_right_joycon_button_unified_mappings[] = {
> +	{ BTN_SOUTH,	0, NS2_BTN_U1_A,	},
> +	{ BTN_EAST,	0, NS2_BTN_U1_B,	},
> +	{ BTN_NORTH,	0, NS2_BTN_U1_X,	},
> +	{ BTN_WEST,	0, NS2_BTN_U1_Y,	},
> +	{ BTN_TR,	0, NS2_BTN_U1_R,	},
> +	{ BTN_TR2,	0, NS2_BTN_U1_ZR	},
> +	{ BTN_START,	1, NS2_BTN_U2_PLUS,	},
> +	{ BTN_THUMBR,	1, NS2_BTN_U2_RS,	},
> +	{ BTN_C,	1, NS2_BTN_U2_C,	},
> +	{ BTN_MODE,	1, NS2_BTN_U2_HOME,	},
> +	{ BTN_GRIPL2,	0, NS2_BTN_U1_SL,	},
> +	{ BTN_GRIPL,	0, NS2_BTN_U1_SR,	},
> +	{ BTN_GRIPR,	3, NS2_BTN_U4_GR,	},
> +	{ /* sentinel */ },
> +};
> +
>  static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
>  	{ BTN_SOUTH,	0, NS2_BTNR_A,		},
>  	{ BTN_EAST,	0, NS2_BTNR_B,		},
> @@ -3159,6 +3222,27 @@ static const struct switch2_ctlr_button_mapping ns2_procon_mappings[] = {
>  	{ /* sentinel */ },
>  };
>  
> +static const struct switch2_ctlr_button_mapping ns2_procon_unified_mappings[] = {
> +	{ BTN_SOUTH,	0, NS2_BTN_U1_A,	},
> +	{ BTN_EAST,	0, NS2_BTN_U1_B,	},
> +	{ BTN_NORTH,	0, NS2_BTN_U1_X,	},
> +	{ BTN_WEST,	0, NS2_BTN_U1_Y,	},
> +	{ BTN_TL,	2, NS2_BTN_U3_L,	},
> +	{ BTN_TR,	0, NS2_BTN_U1_R,	},
> +	{ BTN_TL2,	2, NS2_BTN_U3_ZL,	},
> +	{ BTN_TR2,	0, NS2_BTN_U1_ZR,	},
> +	{ BTN_SELECT,	1, NS2_BTN_U2_MINUS,	},
> +	{ BTN_START,	1, NS2_BTN_U2_PLUS,	},
> +	{ BTN_THUMBL,	1, NS2_BTN_U2_LS,	},
> +	{ BTN_THUMBR,	1, NS2_BTN_U2_RS,	},
> +	{ BTN_MODE,	1, NS2_BTN_U2_HOME	},
> +	{ KEY_RECORD,	1, NS2_BTN_U2_CAPTURE	},
> +	{ BTN_GRIPR,	3, NS2_BTN_U4_GR	},
> +	{ BTN_GRIPL,	3, NS2_BTN_U4_GL	},
> +	{ BTN_C,	1, NS2_BTN_U2_C		},
> +	{ /* sentinel */ },
> +};
> +
>  static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
>  	{ BTN_SOUTH,	0, NS2_BTNR_A,		},
>  	{ BTN_EAST,	0, NS2_BTNR_B,		},
> @@ -3176,6 +3260,23 @@ static const struct switch2_ctlr_button_mapping ns2_gccon_mappings[] = {
>  	{ /* sentinel */ },
>  };
>  
> +static const struct switch2_ctlr_button_mapping ns2_gccon_unified_mappings[] = {
> +	{ BTN_SOUTH,	0, NS2_BTN_U1_A,	},
> +	{ BTN_EAST,	0, NS2_BTN_U1_B,	},
> +	{ BTN_NORTH,	0, NS2_BTN_U1_X,	},
> +	{ BTN_WEST,	0, NS2_BTN_U1_Y,	},
> +	{ BTN_TL2,	2, NS2_BTN_U3_L,	},
> +	{ BTN_TR2,	0, NS2_BTN_U1_R,	},
> +	{ BTN_TL,	2, NS2_BTN_U3_ZL	},
> +	{ BTN_TR,	0, NS2_BTN_U1_ZR	},
> +	{ BTN_SELECT,	1, NS2_BTN_U2_MINUS,	},
> +	{ BTN_START,	1, NS2_BTN_U2_PLUS,	},
> +	{ BTN_MODE,	1, NS2_BTN_U2_HOME	},
> +	{ KEY_RECORD,	1, NS2_BTN_U2_CAPTURE	},
> +	{ BTN_C,	1, NS2_BTN_U2_C		},
> +	{ /* sentinel */ },
> +};
> +
>  static const uint8_t switch2_init_cmd_data[] = {
>  	/*
>  	 * The last 6 bytes of this packet are the MAC address of
> @@ -3802,11 +3903,51 @@ static int switch2_event(struct hid_device *hdev, struct hid_report *report, uin
>  
>  	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.
> -		 */
> +		if (size < 0x3f)
> +			return -EINVAL;
> +
> +		switch (ns2->ctlr_type) {
> +		case NS2_CTLR_TYPE_JCL:
> +			switch2_report_stick(input, &ns2->stick_calib[0],
> +				ABS_X, false, ABS_Y, true, &raw_data[11]);
> +			switch2_report_buttons(input, &raw_data[5],
> +				ns2_left_joycon_button_unified_mappings);
> +			break;
> +		case NS2_CTLR_TYPE_JCR:
> +			switch2_report_stick(input, &ns2->stick_calib[0],
> +				ABS_X, false, ABS_Y, true, &raw_data[14]);
> +			switch2_report_buttons(input, &raw_data[5],
> +				ns2_right_joycon_button_unified_mappings);
> +			break;
> +		case NS2_CTLR_TYPE_GC:
> +			input_report_abs(input, ABS_HAT0X,
> +				!!(raw_data[7] & NS2_BTN_U3_RIGHT) -
> +				!!(raw_data[7] & NS2_BTN_U3_LEFT));
> +			input_report_abs(input, ABS_HAT0Y,
> +				!!(raw_data[7] & NS2_BTN_U3_DOWN) -
> +				!!(raw_data[7] & NS2_BTN_U3_UP));
> +			switch2_report_buttons(input, &raw_data[5], ns2_gccon_unified_mappings);
> +			switch2_report_stick(input, &ns2->stick_calib[0],
> +				ABS_X, false, ABS_Y, true, &raw_data[11]);
> +			switch2_report_stick(input, &ns2->stick_calib[1],
> +				ABS_RX, false, ABS_RY, true, &raw_data[14]);
> +			switch2_report_trigger(input, ns2->lt_zero, ABS_Z, raw_data[0x3d]);
> +			switch2_report_trigger(input, ns2->rt_zero, ABS_RZ, raw_data[0x3e]);
> +			break;
> +		case NS2_CTLR_TYPE_PRO:
> +			input_report_abs(input, ABS_HAT0X,
> +				!!(raw_data[7] & NS2_BTN_U3_RIGHT) -
> +				!!(raw_data[7] & NS2_BTN_U3_LEFT));
> +			input_report_abs(input, ABS_HAT0Y,
> +				!!(raw_data[7] & NS2_BTN_U3_DOWN) -
> +				!!(raw_data[7] & NS2_BTN_U3_UP));
> +			switch2_report_buttons(input, &raw_data[5], ns2_procon_unified_mappings);
> +			switch2_report_stick(input, &ns2->stick_calib[0],
> +				ABS_X, false, ABS_Y, true, &raw_data[11]);
> +			switch2_report_stick(input, &ns2->stick_calib[1],
> +				ABS_RX, false, ABS_RY, true, &raw_data[14]);
> +			break;
> +		}
>  		break;
>  	case NS2_REPORT_JCL:
>  		switch2_report_stick(input, &ns2->stick_calib[0], ABS_X, false,



^ permalink raw reply

* Re: [PATCH v2 3/6] iio: hid-sensors: Use implicit NULL pointer checks
From: Sanjay Chitroda @ 2026-07-05 18:17 UTC (permalink / raw)
  To: Jonathan Cameron, Sanjay Chitroda via B4 Relay
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jiri Kosina,
	Srinivas Pandruvada, linux-iio, linux-kernel, linux-input,
	Maxwell Doose
In-Reply-To: <20260702182205.388efe24@jic23-huawei>



On 2 July 2026 10:52:05 pm IST, Jonathan Cameron <jic23@kernel.org> wrote:
>On Thu, 02 Jul 2026 21:48:00 +0530
>Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
>
>> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
>> 
>> Replace explicit NULL pointer comparisons with implicit checks across
>> HID sensor IIO drivers to follow the preferred kernel coding style.
>Is there anything in the kernel wide style guides about this?
>
>I do prefer this style in IIO but perhaps we should document it
>as local IIO style rather than implying general guidance (unless
>there is some!)
>
Hi Jonathan,

I took reference of existing IIO commit message and followed the same.

There is no kernel coding guideline for NULL pointer. If you think it would be useful, I would be happy to work on documenting the preferred conventions under "Documentation/driver-api/iio/", for example as a new "coding-style.rst" or similar document.

Thanks, Sanjay

>> 
>> Convert 'if (indio_dev == NULL)' -> 'if (!indio_dev)'.
>> 
>> No functional change.
>> 
>> Signed-off-by: Sanjay Chitroda <sanjayembeddedse@gmail.com>
>> Reviewed-by: Maxwell Doose <m32285159@gmail.com>
>> ---
>>  drivers/iio/accel/hid-sensor-accel-3d.c       | 2 +-
>>  drivers/iio/magnetometer/hid-sensor-magn-3d.c | 2 +-
>>  drivers/iio/orientation/hid-sensor-incl-3d.c  | 2 +-
>>  drivers/iio/orientation/hid-sensor-rotation.c | 2 +-
>>  4 files changed, 4 insertions(+), 4 deletions(-)
>> 
>> diff --git a/drivers/iio/accel/hid-sensor-accel-3d.c b/drivers/iio/accel/hid-sensor-accel-3d.c
>> index 9197f3424c0c..225f8dd65ab1 100644
>> --- a/drivers/iio/accel/hid-sensor-accel-3d.c
>> +++ b/drivers/iio/accel/hid-sensor-accel-3d.c
>> @@ -325,7 +325,7 @@ static int hid_accel_3d_probe(struct platform_device *pdev)
>>  
>>  	indio_dev = devm_iio_device_alloc(&pdev->dev,
>>  					  sizeof(struct accel_3d_state));
>> -	if (indio_dev == NULL)
>> +	if (!indio_dev)
>>  		return -ENOMEM;
>>  
>>  	platform_set_drvdata(pdev, indio_dev);
>> diff --git a/drivers/iio/magnetometer/hid-sensor-magn-3d.c b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
>> index ad10fa20fae0..738bad65d74d 100644
>> --- a/drivers/iio/magnetometer/hid-sensor-magn-3d.c
>> +++ b/drivers/iio/magnetometer/hid-sensor-magn-3d.c
>> @@ -463,7 +463,7 @@ static int hid_magn_3d_probe(struct platform_device *pdev)
>>  
>>  	indio_dev = devm_iio_device_alloc(&pdev->dev,
>>  					  sizeof(struct magn_3d_state));
>> -	if (indio_dev == NULL)
>> +	if (!indio_dev)
>>  		return -ENOMEM;
>>  
>>  	platform_set_drvdata(pdev, indio_dev);
>> diff --git a/drivers/iio/orientation/hid-sensor-incl-3d.c b/drivers/iio/orientation/hid-sensor-incl-3d.c
>> index 870c8929491e..c8efb0dab8b6 100644
>> --- a/drivers/iio/orientation/hid-sensor-incl-3d.c
>> +++ b/drivers/iio/orientation/hid-sensor-incl-3d.c
>> @@ -302,7 +302,7 @@ static int hid_incl_3d_probe(struct platform_device *pdev)
>>  
>>  	indio_dev = devm_iio_device_alloc(&pdev->dev,
>>  					  sizeof(struct incl_3d_state));
>> -	if (indio_dev == NULL)
>> +	if (!indio_dev)
>>  		return -ENOMEM;
>>  
>>  	platform_set_drvdata(pdev, indio_dev);
>> diff --git a/drivers/iio/orientation/hid-sensor-rotation.c b/drivers/iio/orientation/hid-sensor-rotation.c
>> index 2dad0453fc67..6db253c1635d 100644
>> --- a/drivers/iio/orientation/hid-sensor-rotation.c
>> +++ b/drivers/iio/orientation/hid-sensor-rotation.c
>> @@ -274,7 +274,7 @@ static int hid_dev_rot_probe(struct platform_device *pdev)
>>  
>>  	indio_dev = devm_iio_device_alloc(&pdev->dev,
>>  					  sizeof(struct dev_rot_state));
>> -	if (indio_dev == NULL)
>> +	if (!indio_dev)
>>  		return -ENOMEM;
>>  
>>  	platform_set_drvdata(pdev, indio_dev);
>> 
>

^ permalink raw reply

* [PATCH] HID: apple: Add Endorfy Thock TKL Wireless to the non-apple keyboard list
From: Michal Slustik @ 2026-07-05 18:50 UTC (permalink / raw)
  To: jikos, bentiss; +Cc: linux-input, linux-kernel, Michal Slustik

The Endorfy Thock TKL Wireless uses the same device ID as an
Apple keyboard (05ac:024f), but its F1-F12 function keys do
not work correctly by default.

Adding two entries to the non-apple keyboard list:
one for Bluetooth mode, identified as "Thock TKL Wireless"
one for 2.4GHz wireless mode, identified as "USB Dongle"

Signed-off-by: Michal Slustik <michal.slustik@gmail.com>
---
 drivers/hid/hid-apple.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index bf7dd0fbf249..0bd1af58d837 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -368,6 +368,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
 	{ "TH87" },			/* EPOMAKER TH87 BT mode */
 	{ "HFD Epomaker TH87" },	/* EPOMAKER TH87 USB mode */
 	{ "2.4G Wireless Receiver" },	/* EPOMAKER TH87 dongle */
+	{ "Thock TKL Wireless" },	/* ENDORFY THOCK TKL BT mode */
+	{ "USB Dongle" },		/* ENDORFY THOCK TKL dongle */
 };
 
 static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH 25/26] Input: maplecontrol - fix style issues
From: Adrian McMenamin @ 2026-07-05 19:28 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
	Yoshinori Sato, Rich Felker, John Paul Adrian Glaubitz,
	Florian Fuchs, linux-kernel, Dmitry Torokhov, linux-input,
	linux-mtd, linux-sh
In-Reply-To: <20260703-b4-maple-cleanup-v1-25-41e424964da5@gmail.com>

On Sat, 4 Jul 2026 at 06:58, Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
>
> Fix coding style and formatting issues reported by checkpatch.pl and
> switch to using BIT(). When reporting D-PAD events avoid conditionals.
>
> Assisted-by: Antigravity:gemini-3.5-flash
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> ---
>  drivers/input/joystick/maplecontrol.c | 25 ++++++++++++-------------
>  1 file changed, 12 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/input/joystick/maplecontrol.c b/drivers/input/joystick/maplecontrol.c
> index 3ef6652d40cb..457a73d91239 100644
> --- a/drivers/input/joystick/maplecontrol.c
> +++ b/drivers/input/joystick/maplecontrol.c
> @@ -35,22 +35,22 @@ static void dc_pad_callback(struct mapleq *mq)
>         buttons = ~le16_to_cpup((__le16 *)(res + 8));
>
>         input_report_abs(dev, ABS_HAT0Y,
> -               (buttons & 0x0010 ? -1 : 0) + (buttons & 0x0020 ? 1 : 0));
> +                        !!(buttons & BIT(5)) - !!(buttons & BIT(4)));
>         input_report_abs(dev, ABS_HAT0X,
> -               (buttons & 0x0040 ? -1 : 0) + (buttons & 0x0080 ? 1 : 0));
> +                        !!(buttons & BIT(7)) - !!(buttons & BIT(6)));


Maybe I have missed something but what is this !! operator?

Adrian

^ permalink raw reply

* Re: [syzbot] [input?] [usb?] INFO: task hung in devres_release_group (2)
From: syzbot @ 2026-07-05 19:30 UTC (permalink / raw)
  To: anna-maria, frederic, linux-input, linux-kernel, linux-usb,
	syzkaller-bugs, tglx
In-Reply-To: <69e31ea7.050a0220.24bfd3.0002.GAE@google.com>

syzbot has found a reproducer for the following issue on:

HEAD commit:    2b763db0c276 Add linux-next specific files for 20260703
git tree:       linux-next
console output: https://syzkaller.appspot.com/x/log.txt?x=129e3b39580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=3c288f117a992ca4
dashboard link: https://syzkaller.appspot.com/bug?extid=d789904ff97c2f3dac88
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
syz repro:      https://syzkaller.appspot.com/x/repro.syz?x=169e3b39580000
C reproducer:   https://syzkaller.appspot.com/x/repro.c?x=119e3b39580000

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/8089f1fba297/disk-2b763db0.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/97090b3387c9/vmlinux-2b763db0.xz
kernel image: https://storage.googleapis.com/syzbot-assets/48e3e98867f8/bzImage-2b763db0.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+d789904ff97c2f3dac88@syzkaller.appspotmail.com

INFO: task kworker/0:2:200 blocked for more than 143 seconds.
      Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:kworker/0:2     state:D stack:21496 pid:200   tgid:200   ppid:2      task_flags:0x4288060 flags:0x00080000
Workqueue: usb_hub_wq hub_event
Workqueue: usb_hub_wq hub_event
Call Trace:
 <TASK>
 context_switch kernel/sched/core.c:5510 [inline]
 __schedule+0x1709/0x5530 kernel/sched/core.c:7231
 __schedule_loop kernel/sched/core.c:7308 [inline]
 schedule+0x164/0x2b0 kernel/sched/core.c:7323
 schedule_timeout+0xc0/0x2c0 kernel/time/sleep_timeout.c:75
 do_wait_for_common kernel/sched/completion.c:100 [inline]
 __wait_for_common kernel/sched/completion.c:121 [inline]
 wait_for_common kernel/sched/completion.c:132 [inline]
 wait_for_completion+0x2ca/0x5e0 kernel/sched/completion.c:153
 i2c_del_adapter+0x281/0x430 drivers/i2c/i2c-core-base.c:1840
 release_nodes drivers/base/devres.c:546 [inline]
 devres_release_group+0x264/0x360 drivers/base/devres.c:739
 hid_device_remove+0x11c/0x1d0 drivers/hid/hid-core.c:2897
 device_remove drivers/base/dd.c:616 [inline]
 __device_release_driver drivers/base/dd.c:1349 [inline]
 device_release_driver_internal+0x48b/0x880 drivers/base/dd.c:1372
 bus_remove_device+0x449/0x560 drivers/base/bus.c:664
 device_del+0x528/0x8f0 drivers/base/core.c:3961
 hid_remove_device drivers/hid/hid-core.c:3078 [inline]
 hid_destroy_device+0x6b/0x1b0 drivers/hid/hid-core.c:3100
 usbhid_disconnect+0x9f/0xc0 drivers/hid/usbhid/hid-core.c:1479
 usb_unbind_interface+0x295/0x9f0 drivers/usb/core/driver.c:458
 device_remove drivers/base/dd.c:618 [inline]
 __device_release_driver drivers/base/dd.c:1349 [inline]
 device_release_driver_internal+0x4f5/0x880 drivers/base/dd.c:1372
 bus_remove_device+0x449/0x560 drivers/base/bus.c:664
 device_del+0x528/0x8f0 drivers/base/core.c:3961
 usb_disable_device+0x3d4/0x8d0 drivers/usb/core/message.c:1478
 usb_disconnect+0x32d/0x990 drivers/usb/core/hub.c:2345
 hub_port_connect drivers/usb/core/hub.c:5407 [inline]
 hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
 port_event drivers/usb/core/hub.c:5871 [inline]
 hub_event+0x1bb7/0x4cf0 drivers/usb/core/hub.c:5953
 process_one_work+0xaaf/0x1480 kernel/workqueue.c:3379
 process_scheduled_works kernel/workqueue.c:3462 [inline]
 worker_thread+0xb05/0x10d0 kernel/workqueue.c:3543
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
INFO: task syz.3.113:6124 blocked for more than 143 seconds.
      Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.3.113       state:D stack:24712 pid:6124  tgid:6124  ppid:5776   task_flags:0x400040 flags:0x00080002
Call Trace:
 <TASK>
 context_switch kernel/sched/core.c:5510 [inline]
 __schedule+0x1709/0x5530 kernel/sched/core.c:7231
 __schedule_loop kernel/sched/core.c:7308 [inline]
 rt_mutex_schedule+0x76/0xf0 kernel/sched/core.c:7604
 rt_mutex_slowlock_block+0x55c/0x680 kernel/locking/rtmutex.c:1670
 __rt_mutex_slowlock kernel/locking/rtmutex.c:1747 [inline]
 __rt_mutex_slowlock_locked kernel/locking/rtmutex.c:1787 [inline]
 rt_mutex_slowlock+0x2d4/0x780 kernel/locking/rtmutex.c:1827
 __rt_mutex_lock kernel/locking/rtmutex.c:1842 [inline]
 __mutex_lock_common kernel/locking/rtmutex_api.c:560 [inline]
 mutex_lock_nested+0x168/0x1d0 kernel/locking/rtmutex_api.c:578
 device_lock include/linux/device.h:1104 [inline]
 usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
 chrdev_open+0x4dc/0x600 fs/char_dev.c:411
 do_dentry_open+0x849/0x1420 fs/open.c:947
 vfs_open+0x3b/0x350 fs/open.c:1052
 do_open fs/namei.c:4694 [inline]
 path_openat+0x2e64/0x3850 fs/namei.c:4857
 do_file_open+0x23e/0x4a0 fs/namei.c:4886
 do_sys_openat2+0x115/0x200 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1385
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f6b1e81e68e
RSP: 002b:00007fff96046768 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 000055558f391500 RCX: 00007f6b1e81e68e
RDX: 000000000000a401 RSI: 00007fff96046840 RDI: ffffffffffffff9c
RBP: 00007fff96046840 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: cccccccccccccccd
R13: 00007f6b1eae5fac R14: 00007f6b1eae5fa0 R15: 00007f6b1eae5fa0
 </TASK>
INFO: task syz.4.114:6125 blocked for more than 143 seconds.
      Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.4.114       state:D stack:24712 pid:6125  tgid:6125  ppid:5777   task_flags:0x400040 flags:0x00080002
Call Trace:
 <TASK>
 context_switch kernel/sched/core.c:5510 [inline]
 __schedule+0x1709/0x5530 kernel/sched/core.c:7231
 __schedule_loop kernel/sched/core.c:7308 [inline]
 rt_mutex_schedule+0x76/0xf0 kernel/sched/core.c:7604
 rt_mutex_slowlock_block+0x55c/0x680 kernel/locking/rtmutex.c:1670
 __rt_mutex_slowlock kernel/locking/rtmutex.c:1747 [inline]
 __rt_mutex_slowlock_locked kernel/locking/rtmutex.c:1787 [inline]
 rt_mutex_slowlock+0x2d4/0x780 kernel/locking/rtmutex.c:1827
 __rt_mutex_lock kernel/locking/rtmutex.c:1842 [inline]
 __mutex_lock_common kernel/locking/rtmutex_api.c:560 [inline]
 mutex_lock_nested+0x168/0x1d0 kernel/locking/rtmutex_api.c:578
 device_lock include/linux/device.h:1104 [inline]
 usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
 chrdev_open+0x4dc/0x600 fs/char_dev.c:411
 do_dentry_open+0x849/0x1420 fs/open.c:947
 vfs_open+0x3b/0x350 fs/open.c:1052
 do_open fs/namei.c:4694 [inline]
 path_openat+0x2e64/0x3850 fs/namei.c:4857
 do_file_open+0x23e/0x4a0 fs/namei.c:4886
 do_sys_openat2+0x115/0x200 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1385
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f23e29fe68e
RSP: 002b:00007ffd83e58858 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 00005555860fe500 RCX: 00007f23e29fe68e
RDX: 000000000000a401 RSI: 00007ffd83e58930 RDI: ffffffffffffff9c
RBP: 00007ffd83e58930 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: cccccccccccccccd
R13: 00007f23e2cc5fac R14: 00007f23e2cc5fa0 R15: 00007f23e2cc5fa0
 </TASK>
INFO: task syz.2.115:6126 blocked for more than 143 seconds.
      Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.2.115       state:D stack:24712 pid:6126  tgid:6126  ppid:5770   task_flags:0x400040 flags:0x00080002
Call Trace:
 <TASK>
 context_switch kernel/sched/core.c:5510 [inline]
 __schedule+0x1709/0x5530 kernel/sched/core.c:7231
 __schedule_loop kernel/sched/core.c:7308 [inline]
 rt_mutex_schedule+0x76/0xf0 kernel/sched/core.c:7604
 rt_mutex_slowlock_block+0x55c/0x680 kernel/locking/rtmutex.c:1670
 __rt_mutex_slowlock kernel/locking/rtmutex.c:1747 [inline]
 __rt_mutex_slowlock_locked kernel/locking/rtmutex.c:1787 [inline]
 rt_mutex_slowlock+0x2d4/0x780 kernel/locking/rtmutex.c:1827
 __rt_mutex_lock kernel/locking/rtmutex.c:1842 [inline]
 __mutex_lock_common kernel/locking/rtmutex_api.c:560 [inline]
 mutex_lock_nested+0x168/0x1d0 kernel/locking/rtmutex_api.c:578
 device_lock include/linux/device.h:1104 [inline]
 usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
 chrdev_open+0x4dc/0x600 fs/char_dev.c:411
 do_dentry_open+0x849/0x1420 fs/open.c:947
 vfs_open+0x3b/0x350 fs/open.c:1052
 do_open fs/namei.c:4694 [inline]
 path_openat+0x2e64/0x3850 fs/namei.c:4857
 do_file_open+0x23e/0x4a0 fs/namei.c:4886
 do_sys_openat2+0x115/0x200 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1385
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f807aa2e68e
RSP: 002b:00007ffe18ae0728 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 0000555582e4d500 RCX: 00007f807aa2e68e
RDX: 000000000000a401 RSI: 00007ffe18ae0800 RDI: ffffffffffffff9c
RBP: 00007ffe18ae0800 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: cccccccccccccccd
R13: 00007f807acf5fac R14: 00007f807acf5fa0 R15: 00007f807acf5fa0
 </TASK>
INFO: task syz.1.116:6127 blocked for more than 143 seconds.
      Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.1.116       state:D stack:24712 pid:6127  tgid:6127  ppid:5774   task_flags:0x400040 flags:0x00080002
Call Trace:
 <TASK>
 context_switch kernel/sched/core.c:5510 [inline]
 __schedule+0x1709/0x5530 kernel/sched/core.c:7231
 __schedule_loop kernel/sched/core.c:7308 [inline]
 rt_mutex_schedule+0x76/0xf0 kernel/sched/core.c:7604
 rt_mutex_slowlock_block+0x55c/0x680 kernel/locking/rtmutex.c:1670
 __rt_mutex_slowlock kernel/locking/rtmutex.c:1747 [inline]
 __rt_mutex_slowlock_locked kernel/locking/rtmutex.c:1787 [inline]
 rt_mutex_slowlock+0x2d4/0x780 kernel/locking/rtmutex.c:1827
 __rt_mutex_lock kernel/locking/rtmutex.c:1842 [inline]
 __mutex_lock_common kernel/locking/rtmutex_api.c:560 [inline]
 mutex_lock_nested+0x168/0x1d0 kernel/locking/rtmutex_api.c:578
 device_lock include/linux/device.h:1104 [inline]
 usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
 chrdev_open+0x4dc/0x600 fs/char_dev.c:411
 do_dentry_open+0x849/0x1420 fs/open.c:947
 vfs_open+0x3b/0x350 fs/open.c:1052
 do_open fs/namei.c:4694 [inline]
 path_openat+0x2e64/0x3850 fs/namei.c:4857
 do_file_open+0x23e/0x4a0 fs/namei.c:4886
 do_sys_openat2+0x115/0x200 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1385
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f0c6723e68e
RSP: 002b:00007ffd2928b0e8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 000055555f817500 RCX: 00007f0c6723e68e
RDX: 000000000000a401 RSI: 00007ffd2928b1c0 RDI: ffffffffffffff9c
RBP: 00007ffd2928b1c0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: cccccccccccccccd
R13: 00007f0c67505fac R14: 00007f0c67505fa0 R15: 00007f0c67505fa0
 </TASK>

Showing all locks held in the system:
1 lock held by khungtaskd/39:
 #0: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x2e/0x180 kernel/locking/lockdep.c:6821
7 locks held by kworker/u8:5/69:
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc9000154fc60 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc9000154fc60 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc9000154fc60 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #2: ffff88806d354310 (&devlink->lock_key#28){+.+.}-{4:4}, at: nsim_dev_trap_report_work+0x57/0xcb0 drivers/net/netdevsim/dev.c:909
 #3: ffff88807ff3f920 (&nsim_trap_data->trap_lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock_rt.h:45 [inline]
 #3: ffff88807ff3f920 (&nsim_trap_data->trap_lock){+.+.}-{3:3}, at: nsim_dev_trap_report drivers/net/netdevsim/dev.c:862 [inline]
 #3: ffff88807ff3f920 (&nsim_trap_data->trap_lock){+.+.}-{3:3}, at: nsim_dev_trap_report_work+0x1e0/0xcb0 drivers/net/netdevsim/dev.c:922
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: __rt_spin_lock kernel/locking/spinlock_rt.c:50 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rt_spin_lock+0x1e0/0x400 kernel/locking/spinlock_rt.c:57
 #5: ffffffff8e0592e0 (local_bh){.+.+}-{1:3}, at: __local_bh_disable_ip+0x3c/0x420 kernel/softirq.c:163
 #6: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: __local_bh_disable_ip+0x3c/0x420 kernel/softirq.c:163
6 locks held by kworker/0:2/200:
 #0: ffff888021ee5538 ((wq_completion)usb_hub_wq){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff888021ee5538 ((wq_completion)usb_hub_wq){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff888021ee5538 ((wq_completion)usb_hub_wq){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc900039dfc60 ((work_completion)(&hub->events)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc900039dfc60 ((work_completion)(&hub->events)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc900039dfc60 ((work_completion)(&hub->events)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #2: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #2: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: hub_event+0x187/0x4cf0 drivers/usb/core/hub.c:5899
 #3: ffff88801f6b4210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #3: ffff88801f6b4210 (&dev->mutex){....}-{4:4}, at: usb_disconnect+0xe3/0x990 drivers/usb/core/hub.c:2336
 #4: ffff888021afb1d8 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #4: ffff888021afb1d8 (&dev->mutex){....}-{4:4}, at: __device_driver_lock drivers/base/dd.c:1171 [inline]
 #4: ffff888021afb1d8 (&dev->mutex){....}-{4:4}, at: device_release_driver_internal+0xb9/0x880 drivers/base/dd.c:1369
 #5: ffff888043dcda90 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #5: ffff888043dcda90 (&dev->mutex){....}-{4:4}, at: __device_driver_lock drivers/base/dd.c:1171 [inline]
 #5: ffff888043dcda90 (&dev->mutex){....}-{4:4}, at: device_release_driver_internal+0xb9/0x880 drivers/base/dd.c:1369
2 locks held by kworker/u8:11/1173:
 #0: ffff888032b53938 ((wq_completion)bat_events){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff888032b53938 ((wq_completion)bat_events){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff888032b53938 ((wq_completion)bat_events){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc9000630fc60 ((work_completion)(&(&bat_priv->tt.work)->work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc9000630fc60 ((work_completion)(&(&bat_priv->tt.work)->work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc9000630fc60 ((work_completion)(&(&bat_priv->tt.work)->work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
6 locks held by kworker/u8:16/4350:
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc9000e78fc60 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc9000e78fc60 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc9000e78fc60 ((work_completion)(&(&nsim_dev->trap_data->trap_report_dw)->work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #2: ffff88802f5da310 (&devlink->lock_key#23){+.+.}-{4:4}, at: nsim_dev_trap_report_work+0x57/0xcb0 drivers/net/netdevsim/dev.c:909
 #3: ffff888079055520 (&nsim_trap_data->trap_lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock_rt.h:45 [inline]
 #3: ffff888079055520 (&nsim_trap_data->trap_lock){+.+.}-{3:3}, at: nsim_dev_trap_report drivers/net/netdevsim/dev.c:862 [inline]
 #3: ffff888079055520 (&nsim_trap_data->trap_lock){+.+.}-{3:3}, at: nsim_dev_trap_report_work+0x1e0/0xcb0 drivers/net/netdevsim/dev.c:922
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: __rt_spin_lock kernel/locking/spinlock_rt.c:50 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rt_spin_lock+0x1e0/0x400 kernel/locking/spinlock_rt.c:57
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: class_rcu_constructor include/linux/rcupdate.h:1183 [inline]
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: unwind_next_frame+0x8f/0x2550 arch/x86/kernel/unwind_orc.c:495
2 locks held by kworker/u8:19/4406:
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff88813fe5c138 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc9000e3cfc60 ((work_completion)(&sub_info->work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc9000e3cfc60 ((work_completion)(&sub_info->work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc9000e3cfc60 ((work_completion)(&sub_info->work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
2 locks held by getty/5359:
 #0: ffff8880327e20a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x25/0x70 drivers/tty/tty_ldisc.c:243
 #1: ffffc90003cc62e0 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x460/0x1360 drivers/tty/n_tty.c:2211
1 lock held by kworker/0:3/5747:
 #0: ffff88808326f938 ((wq_completion)wg-crypt-wg0#28){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff88808326f938 ((wq_completion)wg-crypt-wg0#28){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff88808326f938 ((wq_completion)wg-crypt-wg0#28){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
2 locks held by kworker/1:10/6020:
 #0: ffff888079480d38 ((wq_completion)wg-crypt-wg2#28){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff888079480d38 ((wq_completion)wg-crypt-wg2#28){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff888079480d38 ((wq_completion)wg-crypt-wg2#28){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc900015afc60 ((work_completion)(&peer->transmit_packet_work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc900015afc60 ((work_completion)(&peer->transmit_packet_work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc900015afc60 ((work_completion)(&peer->transmit_packet_work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
9 locks held by kworker/0:8/6023:
 #0: ffff888079480d38 ((wq_completion)wg-crypt-wg2#28){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff888079480d38 ((wq_completion)wg-crypt-wg2#28){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff888079480d38 ((wq_completion)wg-crypt-wg2#28){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc9000157fc60 ((work_completion)(&peer->transmit_packet_work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc9000157fc60 ((work_completion)(&peer->transmit_packet_work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc9000157fc60 ((work_completion)(&peer->transmit_packet_work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #2: ffffffff8e0592e0 (local_bh){.+.+}-{1:3}, at: __local_bh_disable_ip+0x3c/0x420 kernel/softirq.c:163
 #3: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: __local_bh_disable_ip+0x3c/0x420 kernel/softirq.c:163
 #4: ffff88806681b790 (&peer->endpoint_lock){++..}-{3:3}, at: read_lock_bh include/linux/rwlock_rt.h:45 [inline]
 #4: ffff88806681b790 (&peer->endpoint_lock){++..}-{3:3}, at: wg_socket_send_skb_to_peer+0x6e/0x200 drivers/net/wireguard/socket.c:172
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #5: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rt_read_lock+0x277/0x4b0 kernel/locking/spinlock_rt.c:251
 #6: ffffffff8e0592e0 (local_bh){.+.+}-{1:3}, at: __local_bh_disable_ip+0x3c/0x420 kernel/softirq.c:163
 #7: ffffffff8e1c3820 (rcu_read_lock_bh){....}-{1:3}, at: local_bh_disable include/linux/bottom_half.h:20 [inline]
 #7: ffffffff8e1c3820 (rcu_read_lock_bh){....}-{1:3}, at: rcu_read_lock_bh include/linux/rcupdate.h:893 [inline]
 #7: ffffffff8e1c3820 (rcu_read_lock_bh){....}-{1:3}, at: send4+0x217/0xec0 drivers/net/wireguard/socket.c:38
 #8: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #8: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #8: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: __rt_spin_lock kernel/locking/spinlock_rt.c:50 [inline]
 #8: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rt_spin_lock+0x1e0/0x400 kernel/locking/spinlock_rt.c:57
3 locks held by kworker/0:10/6027:
 #0: ffff88813fe1b938 ((wq_completion)events){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffff88813fe1b938 ((wq_completion)events){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffff88813fe1b938 ((wq_completion)events){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #1: ffffc900042bfc60 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #1: ffffc900042bfc60 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #1: ffffc900042bfc60 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: process_one_work+0x7fd/0x1480 kernel/workqueue.c:3344
 #2: ffff88802b13a280 (&data->fib_lock){+.+.}-{4:4}, at: nsim_fib_event_work+0x222/0x3e0 drivers/net/netdevsim/fib.c:1490
1 lock held by syz.3.113/6124:
 #0: ffff88823bf64210 (&dev->mutex
){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.4.114/6125:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.2.115/6126:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.1.116/6127:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.6.123/6301:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.5.122/6302:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.7.125/6306:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.8.126/6307:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.9.135/6427:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.3.136/6442:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.4.138/6470:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.1.139/6472:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.2.147/6530:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.5.149/6598:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.6.151/6636:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.7.153/6640:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.8.159/6689:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.9.162/6754:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.3.164/6825:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.4.165/6832:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.1.169/6851:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.2.173/6887:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.5.177/6981:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.6.178/7018:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.7.181/7044:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
1 lock held by syz.8.183/7061:
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:1104 [inline]
 #0: ffff88823bf64210 (&dev->mutex){....}-{4:4}, at: usbdev_open+0x151/0x790 drivers/usb/core/devio.c:1054
5 locks held by syz-executor/7068:
 #0: ffffffff8f61d3a0 (cb_lock){++++}-{4:4}, at: genl_rcv+0x19/0x40 net/netlink/genetlink.c:1217
 #1: ffffffff8f61d1b8 (genl_mutex){+.+.}-{4:4}, at: genl_lock net/netlink/genetlink.c:35 [inline]
 #1: ffffffff8f61d1b8 (genl_mutex){+.+.}-{4:4}, at: genl_op_lock net/netlink/genetlink.c:60 [inline]
 #1: ffffffff8f61d1b8 (genl_mutex){+.+.}-{4:4}, at: genl_rcv_msg+0x10b/0x7a0 net/netlink/genetlink.c:1208
 #2: ffffffff8f5ac2f8 (rtnl_mutex){+.+.}-{4:4}, at: wiphy_register+0x1faf/0x2ff0 net/wireless/core.c:1154
 #3: ffff8880868c08d8 (&rdev->wiphy.mtx){+.+.}-{4:4}, at: wiphy_lock include/net/cfg80211.h:6868 [inline]
 #3: ffff8880868c08d8 (&rdev->wiphy.mtx){+.+.}-{4:4}, at: wiphy_register+0x1fb9/0x2ff0 net/wireless/core.c:1155
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: class_rcu_constructor include/linux/rcupdate.h:1183 [inline]
 #4: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: unwind_next_frame+0x8f/0x2550 arch/x86/kernel/unwind_orc.c:495
2 locks held by modprobe/7113:
 #0: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:300 [inline]
 #0: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:840 [inline]
 #0: ffffffff8e1c37c0 (rcu_read_lock){....}-{1:3}, at: fast_dput+0x1e/0x670 fs/dcache.c:938
 #1: ffff8880339095b0 (&lockref->lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock_rt.h:45 [inline]
 #1: ffff8880339095b0 (&lockref->lock){+.+.}-{3:3}, at: fast_dput+0x29c/0x670 fs/dcache.c:947

=============================================

NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 39 Comm: khungtaskd Not tainted syzkaller #0 PREEMPT_{RT,(full)} 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 nmi_cpu_backtrace+0x274/0x2d0 lib/nmi_backtrace.c:122
 nmi_trigger_cpumask_backtrace+0x17a/0x380 lib/nmi_backtrace.c:65
 trigger_all_cpu_backtrace include/linux/nmi.h:162 [inline]
 __sys_info lib/sys_info.c:157 [inline]
 sys_info+0x135/0x170 lib/sys_info.c:165
 check_hung_uninterruptible_tasks kernel/hung_task.c:353 [inline]
 watchdog+0xfd7/0x1030 kernel/hung_task.c:561
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
Sending NMI from CPU 0 to CPUs 1:
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 4958 Comm: klogd Not tainted syzkaller #0 PREEMPT_{RT,(full)} 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
RIP: 0010:__list_del include/linux/list.h:227 [inline]
RIP: 0010:__list_del_entry include/linux/list.h:249 [inline]
RIP: 0010:list_move include/linux/list.h:329 [inline]
RIP: 0010:set_next_task_fair+0xce9/0x10b0 kernel/sched/fair.c:15171
Code: c1 e8 03 80 3c 28 00 74 08 4c 89 e7 e8 b0 ff 99 00 4d 89 34 24 4c 89 f0 48 c1 e8 03 80 3c 28 00 74 08 4c 89 f7 e8 97 ff 99 00 <4d> 89 2e 4c 8b 64 24 08 4d 89 fd 49 c1 ed 03 41 80 7c 2d 00 00 74
RSP: 0018:ffffc90003a6f5b0 EFLAGS: 00000046
RAX: 1ffff110170e78a6 RBX: ffff888036f51fd8 RCX: dffffc0000000000
RDX: ffff888036f51fe8 RSI: ffff888036f51f80 RDI: ffff888036f51fe0
RBP: dffffc0000000000 R08: ffffffff8faf37f7 R09: 1ffffffff1f5e6fe
R10: dffffc0000000000 R11: fffffbfff1f5e6ff R12: ffff888034e8bee0
R13: ffff888034e8bed8 R14: ffff8880b873c530 R15: ffff8880b873c530
FS:  00007f76e13e8c80(0000) GS:ffff888125d49000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007effe5be7e9c CR3: 00000000374a8000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 put_prev_set_next_task kernel/sched/sched.h:2787 [inline]
 __pick_next_task+0x215/0x3c0 kernel/sched/core.c:6149
 pick_next_task kernel/sched/core.c:-1 [inline]
 __schedule+0x861/0x5530 kernel/sched/core.c:7146
 __schedule_loop kernel/sched/core.c:7308 [inline]
 schedule+0x164/0x2b0 kernel/sched/core.c:7323
 schedule_timeout+0xc0/0x2c0 kernel/time/sleep_timeout.c:75
 unix_wait_for_peer+0x1f5/0x2f0 net/unix/af_unix.c:1615
 unix_dgram_sendmsg+0xaf4/0x17b0 net/unix/af_unix.c:2251
 sock_sendmsg_nosec+0x13a/0x180 net/socket.c:810
 __sock_sendmsg net/socket.c:825 [inline]
 __sys_sendto+0x41e/0x5d0 net/socket.c:2292
 __do_sys_sendto net/socket.c:2299 [inline]
 __se_sys_sendto net/socket.c:2295 [inline]
 __x64_sys_sendto+0xde/0x100 net/socket.c:2295
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f76e1538407
Code: 48 89 fa 4c 89 df e8 38 aa 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
RSP: 002b:00007ffe12658f20 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
RAX: ffffffffffffffda RBX: 00007f76e13e8c80 RCX: 00007f76e1538407
RDX: 0000000000000048 RSI: 00007ffe12659060 RDI: 0000000000000003
RBP: 00007ffe12659490 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000004000 R11: 0000000000000202 R12: 00007ffe126594a8
R13: 00007ffe12659060 R14: 000000000000002d R15: 00007ffe12659060
 </TASK>


---
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.

^ permalink raw reply

* Re: [PATCH v2 3/6] iio: hid-sensors: Use implicit NULL pointer checks
From: Joshua Crofts @ 2026-07-05 19:31 UTC (permalink / raw)
  To: Sanjay Chitroda
  Cc: Jonathan Cameron, Sanjay Chitroda via B4 Relay, David Lechner,
	Nuno Sá, Andy Shevchenko, Jiri Kosina, Srinivas Pandruvada,
	linux-iio, linux-kernel, linux-input, Maxwell Doose
In-Reply-To: <BF19024B-D73A-4185-A963-516D0574E416@gmail.com>

On Sun, 5 Jul 2026 at 20:17, Sanjay Chitroda <sanjayembeddedse@gmail.com> wrote:
>
>
>
> On 2 July 2026 10:52:05 pm IST, Jonathan Cameron <jic23@kernel.org> wrote:
> >On Thu, 02 Jul 2026 21:48:00 +0530
> >Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
> >
> >> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
> >>
> >> Replace explicit NULL pointer comparisons with implicit checks across
> >> HID sensor IIO drivers to follow the preferred kernel coding style.
> >Is there anything in the kernel wide style guides about this?
> >
> >I do prefer this style in IIO but perhaps we should document it
> >as local IIO style rather than implying general guidance (unless
> >there is some!)
> >
> Hi Jonathan,
>
> I took reference of existing IIO commit message and followed the same.
>
> There is no kernel coding guideline for NULL pointer. If you think it would be useful, I would be happy to work on documenting the preferred conventions under "Documentation/driver-api/iio/", for example as a new "coding-style.rst" or similar document.

Not sure whether any coding style kerneldoc mentions this style,
nevertheless checkpatch prefers (!foo) over (foo == NULL),  see the
COMPARISON_TO_NULL section in the checkpatch kerneldoc.

-- 
Kind regards

CJD

^ permalink raw reply

* Re: [PATCH 25/26] Input: maplecontrol - fix style issues
From: Dmitry Torokhov @ 2026-07-05 20:26 UTC (permalink / raw)
  To: Adrian McMenamin
  Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
	Yoshinori Sato, Rich Felker, John Paul Adrian Glaubitz,
	Florian Fuchs, linux-kernel, linux-input, linux-mtd, linux-sh
In-Reply-To: <CACwZE5RfhwhOoVSPqibM9+XaPJCf-TdnJxi_UT7Dgv71OJPsFQ@mail.gmail.com>

On Sun, Jul 05, 2026 at 08:28:39PM +0100, Adrian McMenamin wrote:
> On Sat, 4 Jul 2026 at 06:58, Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote:
> >
> > Fix coding style and formatting issues reported by checkpatch.pl and
> > switch to using BIT(). When reporting D-PAD events avoid conditionals.
> >
> > Assisted-by: Antigravity:gemini-3.5-flash
> > Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> > ---
> >  drivers/input/joystick/maplecontrol.c | 25 ++++++++++++-------------
> >  1 file changed, 12 insertions(+), 13 deletions(-)
> >
> > diff --git a/drivers/input/joystick/maplecontrol.c b/drivers/input/joystick/maplecontrol.c
> > index 3ef6652d40cb..457a73d91239 100644
> > --- a/drivers/input/joystick/maplecontrol.c
> > +++ b/drivers/input/joystick/maplecontrol.c
> > @@ -35,22 +35,22 @@ static void dc_pad_callback(struct mapleq *mq)
> >         buttons = ~le16_to_cpup((__le16 *)(res + 8));
> >
> >         input_report_abs(dev, ABS_HAT0Y,
> > -               (buttons & 0x0010 ? -1 : 0) + (buttons & 0x0020 ? 1 : 0));
> > +                        !!(buttons & BIT(5)) - !!(buttons & BIT(4)));
> >         input_report_abs(dev, ABS_HAT0X,
> > -               (buttons & 0x0040 ? -1 : 0) + (buttons & 0x0080 ? 1 : 0));
> > +                        !!(buttons & BIT(7)) - !!(buttons & BIT(6)));
> 
> 
> Maybe I have missed something but what is this !! operator?

Double negation to coerce the value to [0, 1] range avoiding
conditional/branching?

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH v2 3/6] iio: hid-sensors: Use implicit NULL pointer checks
From: Jonathan Cameron @ 2026-07-05 22:29 UTC (permalink / raw)
  To: Joshua Crofts
  Cc: Sanjay Chitroda, Sanjay Chitroda via B4 Relay, David Lechner,
	Nuno Sá, Andy Shevchenko, Jiri Kosina, Srinivas Pandruvada,
	linux-iio, linux-kernel, linux-input, Maxwell Doose
In-Reply-To: <CALoEA-wkbbvPA02JFCTuCEQrpGKZfu3NeBScTErTT07U7zJZ_g@mail.gmail.com>

On Sun, 5 Jul 2026 21:31:54 +0200
Joshua Crofts <joshua.crofts1@gmail.com> wrote:

> On Sun, 5 Jul 2026 at 20:17, Sanjay Chitroda <sanjayembeddedse@gmail.com> wrote:
> >
> >
> >
> > On 2 July 2026 10:52:05 pm IST, Jonathan Cameron <jic23@kernel.org> wrote:  
> > >On Thu, 02 Jul 2026 21:48:00 +0530
> > >Sanjay Chitroda via B4 Relay <devnull+sanjayembeddedse.gmail.com@kernel.org> wrote:
> > >  
> > >> From: Sanjay Chitroda <sanjayembeddedse@gmail.com>
> > >>
> > >> Replace explicit NULL pointer comparisons with implicit checks across
> > >> HID sensor IIO drivers to follow the preferred kernel coding style.  
> > >Is there anything in the kernel wide style guides about this?
> > >
> > >I do prefer this style in IIO but perhaps we should document it
> > >as local IIO style rather than implying general guidance (unless
> > >there is some!)
> > >  
> > Hi Jonathan,
> >
> > I took reference of existing IIO commit message and followed the same.
> >
> > There is no kernel coding guideline for NULL pointer. If you think it would be useful, I would be happy to work on documenting the preferred conventions under "Documentation/driver-api/iio/", for example as a new "coding-style.rst" or similar document.  
> 

Probably would go in a subsystem maintainer profile.
https://docs.kernel.org/maintainer/maintainer-entry-profile.html

If you want to take a stab at just a bit on local style preferences
that would be great.  I can probably find time to throw something
together for other standard sections like timelines.


> Not sure whether any coding style kerneldoc mentions this style,
> nevertheless checkpatch prefers (!foo) over (foo == NULL),  see the
> COMPARISON_TO_NULL section in the checkpatch kerneldoc.
> 
Thanks for digging into this.  If it got into checkpatch it was
reviewed and is generally accepted, whether or not anyone updated
the descriptive stuff on kernel style.  Perhaps just refer to that
in the commit message to avoid confusion.

Thanks,

Jonathan



^ permalink raw reply

* [syzbot] [input?] KASAN: slab-use-after-free Read in str_to_user (2)
From: syzbot @ 2026-07-06  1:19 UTC (permalink / raw)
  To: dmitry.torokhov, linux-input, linux-kernel, linux-usb,
	syzkaller-bugs

Hello,

syzbot found the following issue on:

HEAD commit:    dc59e4fea9d8 Linux 7.2-rc1
git tree:       https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
console output: https://syzkaller.appspot.com/x/log.txt?x=105fb11c580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=6ec4d592e55f7960
dashboard link: https://syzkaller.appspot.com/bug?extid=aec1962419768c5caf88
compiler:       gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/23fd2bbd359c/disk-dc59e4fe.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/8ffcad9a17ea/vmlinux-dc59e4fe.xz
kernel image: https://storage.googleapis.com/syzbot-assets/1c2fcc6db866/bzImage-dc59e4fe.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+aec1962419768c5caf88@syzkaller.appspotmail.com

==================================================================
BUG: KASAN: slab-use-after-free in strlen+0x85/0xa0 lib/string.c:402
Read of size 1 at addr ffff888112fec220 by task syz.3.1096/9318

CPU: 1 UID: 0 PID: 9318 Comm: syz.3.1096 Not tainted syzkaller #0 PREEMPT(lazy) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x13d/0x4b0 mm/kasan/report.c:482
 kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
 strlen+0x85/0xa0 lib/string.c:402
 __fortify_strlen include/linux/fortify-string.h:218 [inline]
 str_to_user+0x26/0xa0 drivers/input/evdev.c:769
 evdev_do_ioctl+0x1529/0x1b60 drivers/input/evdev.c:1172
 evdev_ioctl_handler drivers/input/evdev.c:1270 [inline]
 evdev_ioctl+0x16f/0x1a0 drivers/input/evdev.c:1279
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x10b/0x7f0 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f4fe1cdde59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f4fe0737028 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f4fe1f65fa0 RCX: 00007f4fe1cdde59
RDX: 0000200000003d80 RSI: 0000000080404507 RDI: 0000000000000008
RBP: 00007f4fe1d73e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f4fe1f66038 R14: 00007f4fe1f65fa0 R15: 00007ffc2d29e718
 </TASK>

Allocated by task 5308:
 kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
 kasan_save_track+0x14/0x30 mm/kasan/common.c:78
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0x8f/0xa0 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __do_kmalloc_node mm/slub.c:5362 [inline]
 __kmalloc_noprof+0x309/0x7c0 mm/slub.c:5387
 _kmalloc_noprof include/linux/slab.h:973 [inline]
 _kzalloc_noprof include/linux/slab.h:1290 [inline]
 usb_get_configuration+0x1ac/0x4410 drivers/usb/core/config.c:932
 usb_enumerate_device drivers/usb/core/hub.c:2527 [inline]
 usb_new_device+0x580/0x7d0 drivers/usb/core/hub.c:2665
 hub_port_connect drivers/usb/core/hub.c:5567 [inline]
 hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
 port_event drivers/usb/core/hub.c:5871 [inline]
 hub_event+0x314d/0x4af0 drivers/usb/core/hub.c:5953
 process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
 kthread+0x370/0x450 kernel/kthread.c:436
 ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

Freed by task 5418:
 kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
 kasan_save_track+0x14/0x30 mm/kasan/common.c:78
 kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x43/0x70 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2705 [inline]
 slab_free mm/slub.c:6405 [inline]
 kfree+0x20c/0x650 mm/slub.c:6720
 usb_destroy_configuration+0x561/0x700 drivers/usb/core/config.c:896
 usb_release_dev+0x42/0x110 drivers/usb/core/usb.c:494
 device_release+0xd2/0x270 drivers/base/core.c:2636
 kobject_cleanup lib/kobject.c:689 [inline]
 kobject_release lib/kobject.c:720 [inline]
 kref_put include/linux/kref.h:65 [inline]
 kobject_put+0x1f7/0x640 lib/kobject.c:737
 put_device+0x1f/0x30 drivers/base/core.c:3880
 hub_port_connect drivers/usb/core/hub.c:5407 [inline]
 hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
 port_event drivers/usb/core/hub.c:5871 [inline]
 hub_event+0x1d0c/0x4af0 drivers/usb/core/hub.c:5953
 process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
 kthread+0x370/0x450 kernel/kthread.c:436
 ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

The buggy address belongs to the object at ffff888112fec000
 which belongs to the cache kmalloc-1k of size 1024
The buggy address is located 544 bytes inside of
 freed 1024-byte region [ffff888112fec000, ffff888112fec400)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x112fe8
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x200000000000040(head|node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000040 ffff888100041dc0 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
head: 0200000000000040 ffff888100041dc0 dead000000000100 dead000000000122
head: 0000000000000000 0000000800100010 00000000f5000000 0000000000000000
head: 0200000000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd20c0(__GFP_IO|__GFP_FS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 1, tgid 1 (swapper/0), ts 16487694787, free_ts 14540795040
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1859
 prep_new_page mm/page_alloc.c:1867 [inline]
 get_page_from_freelist+0xf25/0x3400 mm/page_alloc.c:3946
 __alloc_frozen_pages_noprof+0x28f/0x2b90 mm/page_alloc.c:5304
 alloc_slab_page mm/slub.c:3294 [inline]
 allocate_slab mm/slub.c:3408 [inline]
 new_slab+0xa2/0x670 mm/slub.c:3454
 refill_objects+0xe3/0x410 mm/slub.c:7338
 refill_sheaf mm/slub.c:2832 [inline]
 __pcs_replace_empty_main+0x376/0x680 mm/slub.c:4703
 alloc_from_pcs mm/slub.c:4801 [inline]
 slab_alloc_node mm/slub.c:4933 [inline]
 __do_kmalloc_node mm/slub.c:5361 [inline]
 __kmalloc_noprof+0x60d/0x7c0 mm/slub.c:5387
 _kmalloc_noprof include/linux/slab.h:973 [inline]
 _kzalloc_noprof include/linux/slab.h:1290 [inline]
 __alloc_workqueue+0x1bd/0x1940 kernel/workqueue.c:5827
 alloc_workqueue_va kernel/workqueue.c:5946 [inline]
 alloc_workqueue_noprof+0xc7/0x130 kernel/workqueue.c:5962
 init_netconsole+0xf3a/0x1150 drivers/net/netconsole.c:2418
 do_one_initcall+0x11d/0x700 init/main.c:1347
 do_initcall_level init/main.c:1409 [inline]
 do_initcalls init/main.c:1425 [inline]
 do_basic_setup init/main.c:1445 [inline]
 kernel_init_freeable+0x6ea/0x7b0 init/main.c:1658
 kernel_init+0x1f/0x1e0 init/main.c:1548
 ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
page last free pid 11 tgid 11 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1406 [inline]
 free_pages_prepare+0x558/0xcb0 mm/page_alloc.c:1451
 __free_contig_range_common.constprop.0+0x12c/0x230 mm/page_alloc.c:6897
 __free_contig_range mm/page_alloc.c:6942 [inline]
 free_pages_bulk+0x126/0x1f0 mm/page_alloc.c:5257
 vm_area_free_pages+0xad/0x2b0 mm/vmalloc.c:3439
 vfree mm/vmalloc.c:3488 [inline]
 vfree+0x107/0x750 mm/vmalloc.c:3462
 delayed_vfree_work+0x56/0x80 mm/vmalloc.c:3392
 process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
 process_scheduled_works kernel/workqueue.c:3405 [inline]
 worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
 kthread+0x370/0x450 kernel/kthread.c:436
 ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

Memory state around the buggy address:
 ffff888112fec100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ffff888112fec180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff888112fec200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
                               ^
 ffff888112fec280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
 ffff888112fec300: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup

^ permalink raw reply

* RE: [PATCH v2 1/3] HID: Intel-thc-hid: Intel-thc: Add API to reset read DMA
From: Xu, Even @ 2026-07-06  2:21 UTC (permalink / raw)
  To: sashiko-reviews@lists.linux.dev
  Cc: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com
In-Reply-To: <20260703081234.A85D41F00A3E@smtp.kernel.org>



> -----Original Message-----
> From: sashiko-bot@kernel.org <sashiko-bot@kernel.org>
> Sent: Friday, July 3, 2026 4:13 PM
> To: Xu, Even <even.xu@intel.com>
> Cc: linux-input@vger.kernel.org; dmitry.torokhov@gmail.com
> Subject: Re: [PATCH v2 1/3] HID: Intel-thc-hid: Intel-thc: Add API to reset read
> DMA
> 
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to
> consider:
> 
> New issues:
> - [High] thc_rxdma_reset() fails to re-initialize the RxDMA PRD tables, leading to
> hardware reading shrunken buffer sizes on restart.
> - [High] thc_rxdma_reset() unsafely clears PRD base addresses for all DMA
> channels while TX or SW DMA engines may still be running.
> - [Medium] thc_rxdma_reset() ignores the return value of
> thc_interrupt_quiesce(dev, false) on the success path.
> 
> Pre-existing issues:
> - [High] Incorrect polling condition and execution order in thc_interrupt_quiesce
> during unquiesce (int_quiesce = false).
> --
> 
> commit b0beafd1bc7a02f7a2277c5a6d69c17c58724f01
> Author: Even Xu <even.xu@intel.com>
> 
> HID: Intel-thc-hid: Intel-thc: Add API to reset read DMA
> 
> Add a helper function thc_rxdma_reset() to do read DMA reset, it can be called
> when fatal DMA error happens.
> 
> > diff --git a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
> > b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
> > index 6ee675e0a7384..7ceb8aeeccd37 100644
> > --- a/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
> > +++ b/drivers/hid/intel-thc-hid/intel-thc/intel-thc-dma.c
> > @@ -561,6 +561,57 @@ static int thc_wait_for_dma_pause(struct
> > thc_device *dev, enum thc_dma_channel c
> [ ... ]
> > +int thc_rxdma_reset(struct thc_device *dev) {
> [ ... ]
> > +	ret = thc_wait_for_dma_pause(dev, THC_RXDMA2);
> > +	if (ret) {
> > +		dev_err(dev->dev, "Wait for RxDMA2 pause failed during RxDMA
> reset\n");
> > +		goto end;
> > +	}
> > +
> > +	thc_dma_unconfigure(dev);
> 
> [Severity: High]
> Could this cause memory corruption or IOMMU faults if TX or SW DMA engines
> are still running?
> 
> thc_rxdma_reset() explicitly pauses RxDMA1 and RxDMA2 but does not pause
> TXDMA or SWDMA. It then calls thc_dma_unconfigure(), which loops over all
> DMA channels (MAX_THC_DMA_CHANNEL) and blindly overwrites their PRD
> base addresses to 0x0.
> 
> If TXDMA was left running (for instance, if a prior thc_dma_write() timed out
> without clearing the TXDMA start bit), setting its PRD base address to 0 while it is
> actively fetching could cause the hardware to fetch descriptors from physical
> address 0.
> 
> > +
> > +	ret = thc_dma_configure(dev);
> 
This isn't a problem, all DMA operations are protected by mutex "thc_bus_lock", that guarantees when do this rxdma_reset, TxDMA and SWDMA are not working.

> [Severity: High]
> Does this leave dirty 'len' descriptors in the PRD memory layout?
> 
> thc_dma_configure() restarts the DMA engine pointers to 0, but it does not re-
> initialize the PRD memory layout. The hardware updates the 'len' field of PRD
> entries to indicate the actual length of received packets.
> 
> Restarting the DMA from index 0 without restoring 'len' to the maximum buffer
> capacity could cause the hardware to use previously received smaller packet sizes
> as the maximum capacity for new transfers, resulting in truncated packets or
> DMA errors.
> 
> > +	if (ret) {
> > +		dev_err(dev->dev, "Re-config DMA failed during RxDMA
> reset\n");
> > +		goto end;
> > +	}
> > +
> > +	thc_interrupt_quiesce(dev, false);
> 
This is expected, once DMA buffers were allocated, in order to avoid memory fragment, this DMA buffers will be reused.
That means the memory address and the max memory length are fixed.
Need pay attention is, this length is just a max memory length, the real length for every DMA data will be stored in every PRD entry and the length will be updated every time once DMA finished. 

> [Severity: Medium]
> Is it safe to ignore the return value of thc_interrupt_quiesce() here?
> 
> Discarding this return value on the success path could silently hide a hardware
> timeout or regmap read failure, potentially leaving interrupts disabled while the
> function reports success.
> 
> [Severity: High]
> This is a pre-existing issue, but is the polling condition and execution order in
> thc_interrupt_quiesce() correct when unquiescing?
> 
> When thc_interrupt_quiesce() is called with int_quiesce = false, it polls the
> HW_STS bit expecting it to be 1 BEFORE it clears the QUIESCE_EN bit:
> 
> drivers/hid/intel-thc-hid/intel-thc/intel-thc-dev.c:thc_interrupt_quiesce() {
>     ...
>     ret = regmap_read_poll_timeout(dev->thc_regmap,
> THC_M_PRT_CONTROL_OFFSET, ctrl,
>                                    ctrl &
> THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_HW_STS,
>                                    THC_REGMAP_POLLING_INTERVAL_US,
> THC_QUIESCE_EN_TIMEOUT_US);
>     ...
>     /* Unquiesce device interrupt - Clear the quiesce bit */
>     if (!int_quiesce)
>         regmap_write_bits(dev->thc_regmap, THC_M_PRT_CONTROL_OFFSET,
>                           THC_M_PRT_CONTROL_THC_DEVINT_QUIESCE_EN, 0); }
> 
> Since the device is already quiesced, HW_STS is already 1, causing the poll to
> return immediately. It then clears the QUIESCE_EN bit and returns without ever
> waiting for HW_STS to become 0 to acknowledge the unquiesce. This could cause
> the driver to proceed before interrupts are actually re-enabled.

That's what this function call works. Called with int_quiesce = false means quiesce the interrupt, if already quiesced, HW_STS should be 0, so clear this bit directly is correct behavior.

> 
> > +
> > +end:
> > +	mutex_unlock(&dev->thc_bus_lock);
> > +	return ret;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(thc_rxdma_reset, "INTEL_THC");
> 
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260703075858.2780398-1-
> even.xu@intel.com?part=1

^ permalink raw reply

* RE: [PATCH v2 2/3] HID: Intel-thc-hid: Intel-quicki2c: Refine recover callback
From: Xu, Even @ 2026-07-06  2:30 UTC (permalink / raw)
  To: sashiko-reviews@lists.linux.dev
  Cc: dmitry.torokhov@gmail.com, linux-input@vger.kernel.org
In-Reply-To: <20260703081055.101CA1F00A3A@smtp.kernel.org>



> -----Original Message-----
> From: sashiko-bot@kernel.org <sashiko-bot@kernel.org>
> Sent: Friday, July 3, 2026 4:11 PM
> To: Xu, Even <even.xu@intel.com>
> Cc: dmitry.torokhov@gmail.com; linux-input@vger.kernel.org
> Subject: Re: [PATCH v2 2/3] HID: Intel-thc-hid: Intel-quicki2c: Refine recover
> callback
> 
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to
> consider:
> 
> New issues:
> - [High] Use-After-Free due to premature `cancel_work_sync` during device
> removal and shutdown.
> 
> Pre-existing issues:
> - [High] Missing `pci_free_irq_vectors()` in probe error paths and device removal
> leaks IRQ resources.
> - [High] The hard IRQ handler accesses hardware registers unconditionally,
> violating PM subsystem guidelines.
> --
> 
> commit e88da9d40562f5d0be755568ae60010a7ba925df
> Author: Even Xu <even.xu@intel.com>
> 
> HID: Intel-thc-hid: Intel-quicki2c: Refine recover callback
> 
> This commit refines the recover flow by offloading it to a workqueue instead of
> processing it within the IRQ handler. It simplifies the operation with a reset API
> and ensures interrupts are disabled during the recovery process.
> 
> > diff --git a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
> > b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
> > index 46d3e9a019995..11e0e129b44c3 100644
> > --- a/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
> > +++ b/drivers/hid/intel-thc-hid/intel-quicki2c/pci-quicki2c.c
> > @@ -245,28 +245,28 @@ static irqreturn_t
> > quicki2c_irq_quick_handler(int irq, void *dev_id)  }
> 
> [Severity: High]
> This is a pre-existing issue, but does quicki2c_irq_quick_handler() access
> hardware registers unconditionally?
> 
> If this device shares an INTx interrupt line, a spurious interrupt from another
> device could cause quicki2c_irq_quick_handler() to execute. It then calls
> thc_interrupt_enable() which writes to memory-mapped registers. If the device is
> in a low-power state, this hardware access can trigger a bus fault.
> 
> Should pm_runtime_get_if_active() be used to check if the device is active before
> touching the hardware registers?
> 
> [ ... ]
> 
> > @@ -771,6 +771,8 @@ static void quicki2c_remove(struct pci_dev *pdev)
> >  	if (!qcdev)
> >  		return;
> >
> > +	cancel_work_sync(&qcdev->recover_work);
> 

THC runtime pm doesn't put THC into D3, THC registers can be accessed all the time.

> [Severity: High]
> Can this sequence lead to a use-after-free?
> 
> cancel_work_sync() is called here, but hardware interrupts are not yet quiesced,
> and the state is still active. If an interrupt fires immediately after
> cancel_work_sync(), quicki2c_irq_thread_handler() can execute and call:
> 
>     schedule_work(&qcdev->recover_work);
> 
> This re-enqueues the work item. After quicki2c_remove() returns, devres frees
> the qcdev structure. When the scheduled work later executes, it will dereference
> the freed qcdev pointer.
> 
> Should the work be cancelled after interrupts are disabled and the device state is
> set to QUICKI2C_DISABLED?
> 
> >  	quicki2c_hid_remove(qcdev);
> >  	quicki2c_dma_deinit(qcdev);
> >
> >  	pm_runtime_get_noresume(qcdev->dev);
> >
> >  	quicki2c_dev_deinit(qcdev);
> >
> >  	pci_clear_master(pdev);
> >  }
> 

Good finding!
In fact, I have another patch for remove() and shutdown(), if so, let me combine them into this patch.

> [Severity: High]
> This is a pre-existing issue, but does quicki2c_remove() leak IRQ resources?
> 
> The remove path unbinds the device but doesn't appear to call
> pci_free_irq_vectors(). Similarly, if quicki2c_probe() fails after
> pci_alloc_irq_vectors(), the error path (disable_pci_device) does not free the
> vectors.
> 
> Repeated unbind/bind operations or probe deferrals will continually leak these
> vectors, which can eventually exhaust system IRQ capacity.
> 
This same as above.

> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260703075858.2780398-1-
> even.xu@intel.com?part=2

^ permalink raw reply

* Re: [PATCH 05/13] mfd: replace linux/gpio.h inclusions
From: Charles Keepax @ 2026-07-06  8:59 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: linux-gpio, Arnd Bergmann, Bartosz Golaszewski, Andrew Lunn,
	Sebastian Hesselbarth, Gregory Clement, Frank Li, Robert Jarzmik,
	Krzysztof Kozlowski, Greg Ungerer, Thomas Bogendoerfer,
	Hauke Mehrtens, Rafał Miłecki, Yoshinori Sato,
	John Paul Adrian Glaubitz, Linus Walleij, Dmitry Torokhov,
	Jakub Kicinski, Paolo Abeni, Dominik Brodowski, linux-kernel,
	linux-arm-kernel, linux-samsung-soc, patches, linux-m68k,
	linux-mips, linux-sh, linux-input, linux-media, netdev,
	linux-sunxi, linux-phy, linux-rockchip, linux-sound
In-Reply-To: <20260629132633.1300009-6-arnd@kernel.org>

On Mon, Jun 29, 2026 at 03:26:25PM +0200, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
> 
> linux/gpio.h should no longer be used, convert these instead to
> either linux/gpio/consumer.h or linux/gpio/legacy.h as needed.
> 
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
>  drivers/mfd/arizona-irq.c         | 2 +-
>  drivers/mfd/wm8994-irq.c          | 2 +-

For the wolfson bits:

Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>

Thanks,
Charles

^ permalink raw reply

* Re: [PATCH 07/13] ASoC: replace linux/gpio.h inclusions
From: Charles Keepax @ 2026-07-06  9:01 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: linux-gpio, Arnd Bergmann, Bartosz Golaszewski, Andrew Lunn,
	Sebastian Hesselbarth, Gregory Clement, Frank Li, Robert Jarzmik,
	Krzysztof Kozlowski, Greg Ungerer, Thomas Bogendoerfer,
	Hauke Mehrtens, Rafał Miłecki, Yoshinori Sato,
	John Paul Adrian Glaubitz, Linus Walleij, Dmitry Torokhov,
	Jakub Kicinski, Paolo Abeni, Dominik Brodowski, linux-kernel,
	linux-arm-kernel, linux-samsung-soc, patches, linux-m68k,
	linux-mips, linux-sh, linux-input, linux-media, netdev,
	linux-sunxi, linux-phy, linux-rockchip, linux-sound
In-Reply-To: <20260629132633.1300009-8-arnd@kernel.org>

On Mon, Jun 29, 2026 at 03:26:27PM +0200, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
> 
> linux/gpio.h is going away,s o use linux/gpio/consumer.h instead.
> 
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
>  sound/soc/codecs/cs42l84.c | 2 +-
>  sound/soc/codecs/cx2072x.c | 2 +-

For the Cirrus bits:

Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>

Thanks,
Charles

^ permalink raw reply

* [PATCH] HID: mcp2200: validate input report length
From: Pengpeng Hou @ 2026-07-06  9:09 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: Pengpeng, Benjamin Tissoires, linux-input, linux-kernel

From: Pengpeng <pengpeng@iscas.ac.cn>

mcp2200_raw_event() dispatches on the first byte of the HID input
report and overlays the READ_ALL response structure on the same buffer.
The HID core passes the actual report size to the callback, so validate
that the command byte and READ_ALL response fields are present before
reading them.

Reject truncated reports for the pending command instead of reading
beyond the current input report payload.

Signed-off-by: Pengpeng <pengpeng@iscas.ac.cn>
---
 drivers/hid/hid-mcp2200.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/hid/hid-mcp2200.c b/drivers/hid/hid-mcp2200.c
index dafdd5b4a079..d2f45892dc22 100644
--- a/drivers/hid/hid-mcp2200.c
+++ b/drivers/hid/hid-mcp2200.c
@@ -299,8 +299,19 @@ static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report,
 	struct mcp2200 *mcp = hid_get_drvdata(hdev);
 	struct mcp_read_all_resp *all_resp;
 
+	if (size < 1) {
+		mcp->status = -EINVAL;
+		complete(&mcp->wait_in_report);
+		return 0;
+	}
+
 	switch (data[0]) {
 	case READ_ALL:
+		if (size < sizeof(*all_resp)) {
+			mcp->status = -EINVAL;
+			break;
+		}
+
 		all_resp = (struct mcp_read_all_resp *) data;
 		mcp->status = 0;
 		mcp->gpio_inval = all_resp->io_port_val_bmap;
-- 
2.43.0


^ permalink raw reply related

* [PATCH] Input: atmel_mxt_ts - validate firmware frame bounds
From: Pengpeng Hou @ 2026-07-06  9:11 UTC (permalink / raw)
  To: Nick Dyer; +Cc: Pengpeng, Dmitry Torokhov, linux-input, linux-kernel

From: Pengpeng <pengpeng@iscas.ac.cn>

mxt_flash_fw() walks a length-prefixed firmware image. The loop only
checks that the cursor is below fw->size before reading the two-byte
frame length and passing the declared frame to the bootloader write path.

Check that the length field and the complete frame, including CRC bytes,
fit in the remaining firmware image before reading or writing the frame.

Signed-off-by: Pengpeng <pengpeng@iscas.ac.cn>
---
 drivers/input/touchscreen/atmel_mxt_ts.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 2c0e5a67135b..2aa52b4e5ff5 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -2927,10 +2927,19 @@ static int mxt_flash_fw(struct mxt_data *data, const struct firmware *fw)
 		if (error)
 			return error;
 
+		if (fw->size - pos < 2) {
+			dev_err(dev, "Truncated frame size in firmware\n");
+			return -EINVAL;
+		}
+
 		frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));
 
 		/* Take account of CRC bytes */
 		frame_size += 2;
+		if (frame_size > fw->size - pos) {
+			dev_err(dev, "Truncated frame in firmware\n");
+			return -EINVAL;
+		}
 
 		/* Write one frame to device */
 		error = mxt_bootloader_write(data, fw->data + pos, frame_size);
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH] HID: mcp2200: validate input report length
From: sashiko-bot @ 2026-07-06  9:19 UTC (permalink / raw)
  To: Pengpeng Hou; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260706090932.75673-1-pengpeng@iscas.ac.cn>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] A raw event arriving immediately after `hid_hw_open` will cause a NULL pointer dereference because driver data and synchronization primitives are not yet initialized.
- [High] Concurrent calls to `mcp_cmd_read_all` can lead to corrupted state, lost wakeups, and data races because `wait_in_report` and shared response fields are not protected by locks during command execution.
- [High] The GPIO chip is managed via devres and unregistered after the HID hardware is stopped, leading to potential use-after-free or invalid hardware accesses.
--

commit d11b6ebfc1ea2a8baf37113e30af821787d261be
Author: Pengpeng <pengpeng@iscas.ac.cn>
Subject: HID: mcp2200: validate input report length

This commit updates mcp2200_raw_event() to validate that the command byte
and response fields are present in the HID input report before accessing
them. It rejects truncated reports instead of reading past the end of the
payload.

> diff --git a/drivers/hid/hid-mcp2200.c b/drivers/hid/hid-mcp2200.c
> index dafdd5b4a0797..d2f45892dc22a 100644
> --- a/drivers/hid/hid-mcp2200.c
> +++ b/drivers/hid/hid-mcp2200.c
> @@ -299,8 +299,19 @@ static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report,
>  	struct mcp2200 *mcp = hid_get_drvdata(hdev);
>  	struct mcp_read_all_resp *all_resp;
>  
> +	if (size < 1) {
> +		mcp->status = -EINVAL;

[Severity: High]
This is a pre-existing issue, but is it possible for an interrupt to fire
immediately after hid_hw_open() in mcp2200_probe() and trigger a NULL
pointer dereference here?

If mcp2200_raw_event() is called before hid_set_drvdata() and lock
initialization complete:

drivers/hid/hid-mcp2200.c:mcp2200_probe() {
        ...
        ret = hid_hw_open(hdev);
        ...
        mutex_init(&mcp->lock);
        init_completion(&mcp->wait_in_report);
        hid_set_drvdata(hdev, mcp);
        ...
}

Could mcp be NULL here, leading to a crash when accessing mcp->status?

> +		complete(&mcp->wait_in_report);
> +		return 0;
> +	}
> +
>  	switch (data[0]) {
>  	case READ_ALL:
> +		if (size < sizeof(*all_resp)) {
> +			mcp->status = -EINVAL;
> +			break;
> +		}
> +
>  		all_resp = (struct mcp_read_all_resp *) data;
>  		mcp->status = 0;
>  		mcp->gpio_inval = all_resp->io_port_val_bmap;

[Severity: High]
This isn't a bug introduced by this patch, but can concurrent calls to
mcp_cmd_read_all() lead to corrupted state or lost wakeups?

In mcp_cmd_read_all(), the mutex is released before waiting for the
response:

drivers/hid/hid-mcp2200.c:mcp_cmd_read_all() {
        ...
        mutex_unlock(&mcp->lock);
        t = wait_for_completion_timeout(&mcp->wait_in_report, ...);
        ...
}

Does modifying mcp->status and mcp->gpio_inval locklessly here in
mcp2200_raw_event() create a data race if multiple threads are reading or
writing GPIO state concurrently?

[ ... ]

[Severity: High]
This is a pre-existing issue, but does using devres for the GPIO chip
create a use-after-free risk during device removal?

In mcp2200_probe(), the chip is registered with devres:

drivers/hid/hid-mcp2200.c:mcp2200_probe() {
        ...
        ret = devm_gpiochip_add_data(&hdev->dev, &mcp->gc, mcp);
        ...
}

Then in mcp2200_remove(), the hardware is stopped before the chip is
unregistered:

drivers/hid/hid-mcp2200.c:mcp2200_remove() {
        ...
        hid_hw_close(hdev);
        hid_hw_stop(hdev);
        ...
}

Could a user-space process continue interacting with the GPIO device while
the USB device is physically removed, causing invalid hardware accesses
before devres cleans up the GPIO chip?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706090932.75673-1-pengpeng@iscas.ac.cn?part=1

^ permalink raw reply

* [PATCH] Input: iforce - validate packet lengths
From: Pengpeng Hou @ 2026-07-06  9:20 UTC (permalink / raw)
  To: Dmitry Torokhov; +Cc: Pengpeng Hou, Bryam Vargas, linux-input, linux-kernel

iforce_process_packet() decodes several packet formats from a
variable-length input buffer.

Add minimum length checks for joystick, wheel and status packets before
reading their fixed fields.

Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
 drivers/input/joystick/iforce/iforce-packets.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c
index effa76bfd8f9..54697b252b84 100644
--- a/drivers/input/joystick/iforce/iforce-packets.c
+++ b/drivers/input/joystick/iforce/iforce-packets.c
@@ -155,6 +155,9 @@ void iforce_process_packet(struct iforce *iforce,
 	switch (packet_id) {
 
 	case 0x01:	/* joystick position data */
+		if (len < 5)
+			break;
+
 		input_report_abs(dev, ABS_X,
 				 (__s16) get_unaligned_le16(data));
 		input_report_abs(dev, ABS_Y,
@@ -170,6 +173,9 @@ void iforce_process_packet(struct iforce *iforce,
 		break;
 
 	case 0x03:	/* wheel position data */
+		if (len < 4)
+			break;
+
 		input_report_abs(dev, ABS_WHEEL,
 				 (__s16) get_unaligned_le16(data));
 		input_report_abs(dev, ABS_GAS,   255 - data[2]);
@@ -181,6 +187,9 @@ void iforce_process_packet(struct iforce *iforce,
 		break;
 
 	case 0x02:	/* status report */
+		if (len < 2)
+			break;
+
 		input_report_key(dev, BTN_DEAD, data[0] & 0x02);
 		input_sync(dev);
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH v1] HID: wacom: #include <linux/device-id/hid.h> instead of <linux/mod_devicetable.h>
From: Uwe Kleine-König (The Capable Hub) @ 2026-07-06  9:21 UTC (permalink / raw)
  To: Ping Cheng, Jason Gerecke, Jiri Kosina, Benjamin Tissoires
  Cc: linux-input, linux-kernel

The single device-id structure that is used by drivers/hid/wacom.h and
its users (drivers/hid/wacom_sys.c + drivers/hid/wacom_wac.c) is struct
hid_device_id. So #include the much more lightweight
<linux/device-id/hid.h> instead the full <linux/mod_devicetable.h>.

Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>
---
Hello,

there is no coordination needed, just make sure to apply it to v7.2-rc2 or
later. So please take it via the hid tree for the next merge window.

Best regards
Uwe

 drivers/hid/wacom.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h
index 1deacb4568cb..d2398b1cdbb2 100644
--- a/drivers/hid/wacom.h
+++ b/drivers/hid/wacom.h
@@ -82,7 +82,7 @@
 #include <linux/kernel.h>
 #include <linux/slab.h>
 #include <linux/module.h>
-#include <linux/mod_devicetable.h>
+#include <linux/device-id/hid.h>
 #include <linux/hid.h>
 #include <linux/kfifo.h>
 #include <linux/leds.h>

base-commit: 8cdeaa50eae8dad34885515f62559ee83e7e8dda
-- 
2.55.0.11.g153666a7d9bb


^ permalink raw reply related

* Re: [PATCH] Input: atmel_mxt_ts - validate firmware frame bounds
From: sashiko-bot @ 2026-07-06  9:24 UTC (permalink / raw)
  To: Pengpeng Hou; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260706091144.76132-1-pengpeng@iscas.ac.cn>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but missing serialization in the sysfs `update_fw` attribute leads to double-free and use-after-free vulnerabilities.
- [High] This problem wasn't introduced by this patch, but direct use of a vmapped firmware buffer for I2C transfers causes DMA mapping failures or memory corruption.
--

commit 4f9a71983f301889709f29b76263c84f5ab07dfc
Author: Pengpeng <pengpeng@iscas.ac.cn>

Input: atmel_mxt_ts - validate firmware frame bounds

This commit adds bounds checking to the firmware update loop in
mxt_flash_fw() to ensure that the declared frame size and its CRC bytes fit
within the remaining firmware image. This prevents reading past the end of
the firmware buffer.

> diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
> index 2c0e5a67135b6..2aa52b4e5ff50 100644
> --- a/drivers/input/touchscreen/atmel_mxt_ts.c
> +++ b/drivers/input/touchscreen/atmel_mxt_ts.c
[ ... ]
> @@ -2927,10 +2927,19 @@ static int mxt_flash_fw(struct mxt_data *data, const struct firmware *fw)
>  		if (error)
>  			return error;
>  
> +		if (fw->size - pos < 2) {
> +			dev_err(dev, "Truncated frame size in firmware\n");
> +			return -EINVAL;
> +		}
> +
>  		frame_size = ((*(fw->data + pos) << 8) | *(fw->data + pos + 1));
>  
>  		/* Take account of CRC bytes */
>  		frame_size += 2;
> +		if (frame_size > fw->size - pos) {
> +			dev_err(dev, "Truncated frame in firmware\n");
> +			return -EINVAL;
> +		}
>  
>  		/* Write one frame to device */
>  		error = mxt_bootloader_write(data, fw->data + pos, frame_size);

[Severity: High]
This isn't a bug introduced by this patch, but does this direct use of the
firmware buffer for I2C transfers risk DMA mapping failures?

The request_firmware() function can map the firmware buffer using vmap()
for files larger than PAGE_SIZE.

When mxt_bootloader_write() passes this potentially vmapped buffer directly
into i2c_transfer(), an underlying I2C adapter driver using DMA will
attempt to map a vmapped address, which yields an invalid physical address.

Should we bounce this buffer into memory allocated with kmalloc() before 
passing it to the I2C subsystem?

[Severity: High]
This isn't a bug introduced by this patch, but does the firmware update path
lack serialization?

Concurrent writes to the sysfs update_fw attribute can execute mxt_load_fw()
in parallel:

mxt_update_fw_store() {
    ...
    error = mxt_load_fw(dev, MXT_FW_NAME);
}

If both threads evaluate the bootloader state as true simultaneously,
they will both execute the cleanup routines:

mxt_load_fw() {
    if (!data->in_bootloader) {
        mxt_free_input_device(data);
        mxt_free_object_table(data);
    ...
}

Could this result in a double-free on pointers like data->raw_info_block
and a double-unregister of the input device?

>  		if (error)
>  			return error;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706091144.76132-1-pengpeng@iscas.ac.cn?part=1

^ permalink raw reply

* Re: [PATCH] Input: iforce - validate packet lengths
From: sashiko-bot @ 2026-07-06  9:34 UTC (permalink / raw)
  To: Pengpeng Hou; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260706092016.78176-1-pengpeng@iscas.ac.cn>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Zero-length USB packets cause an integer underflow that bypasses length checks, triggering a massive out-of-bounds kernel memory read and panic.
- [High] Incomplete bounds checks for joystick and wheel packets allow logical out-of-bounds reads.
- [High] Out-of-bounds read in status report packet processing loop.
--

commit ebc2b4788d350db685692da195ae9d9cd27098a0
Author: Pengpeng Hou <pengpeng@iscas.ac.cn>

Input: iforce - validate packet lengths

iforce_process_packet() decodes several packet formats from a
variable-length input buffer. Add minimum length checks for joystick,
wheel and status packets before reading their fixed fields.

> diff --git a/drivers/input/joystick/iforce/iforce-packets.c b/drivers/input/joystick/iforce/iforce-packets.c
> index effa76bfd8f9a..54697b252b84d 100644
> --- a/drivers/input/joystick/iforce/iforce-packets.c
> +++ b/drivers/input/joystick/iforce/iforce-packets.c
> @@ -155,6 +155,9 @@ void iforce_process_packet(struct iforce *iforce,
>  	switch (packet_id) {
>  
>  	case 0x01:	/* joystick position data */
> +		if (len < 5)
> +			break;
> +

[Severity: Critical]
This isn't a bug introduced by this patch, but does len underflow when a
zero-length USB packet is received?

Looking at iforce_usb_irq() in drivers/input/joystick/iforce/iforce-usb.c, it
calls iforce_process_packet() with urb->actual_length - 1:

    iforce_process_packet(iforce, iforce_usb->data_in[0],
                          iforce_usb->data_in + 1, urb->actual_length - 1);

If urb->actual_length is 0, this underflows to SIZE_MAX. The new bounds check
here will be bypassed, and subsequent loops can read linearly out of bounds
into unmapped memory, potentially causing a panic.

[Severity: High]
This is a pre-existing issue, but is the len < 5 check sufficient to prevent
out-of-bounds reads when processing hats and buttons?

When iforce_process_packet() calls iforce_report_hats_buttons(), it
unconditionally accesses data[6]:

    input_report_abs(dev, ABS_HAT0X, iforce_hat_to_axis[data[6] >> 4].x);

If a short packet arrives (e.g., actual length 6, leading to len = 5), the
check passes, but we still read stale memory when accessing data[6],
potentially reporting phantom hat movements or button presses.

[ ... ]

>  	case 0x03:	/* wheel position data */
> +		if (len < 4)
> +			break;
> +

[Severity: High]
This is also a pre-existing issue, but this check is similarly insufficient
because iforce_report_hats_buttons() requires at least 7 bytes of data to
safely access data[6].

[ ... ]

>  	case 0x02:	/* status report */
> +		if (len < 2)
> +			break;
> +

[Severity: High]
This isn't a bug introduced by this patch, but could the status report
processing loop read out of bounds?

Downstream in this case block, there is a loop:

    for (j = 3; j < len; j += 2)
        mark_core_as_ready(iforce, get_unaligned_le16(data + j));

If a packet arrives with an even length (e.g., len = 4), the loop condition
j < len allows get_unaligned_le16() to read two bytes starting at offset 3.
This extends beyond the valid payload length of 4, inadvertently reading stale
data that could match and corrupt force-feedback state tracking for unrelated
effects.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260706092016.78176-1-pengpeng@iscas.ac.cn?part=1

^ permalink raw reply

* Re: [PATCH v2 1/7] Input: elan_i2c - Wait for initialization after enabling regulator supply
From: Chen-Yu Tsai @ 2026-07-06 10:08 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Matthias Brugger, AngeloGioacchino Del Regno, Benson Leung,
	Tzung-Bi Shih, Jiri Kosina, Andi Shyti, linux-mediatek,
	devicetree, linux-arm-kernel, chrome-platform, linux-input,
	linux-i2c, linux-kernel
In-Reply-To: <akmZOC_J2ZTl4zLX@google.com>

On Sun, Jul 5, 2026 at 7:44 AM Dmitry Torokhov
<dmitry.torokhov@gmail.com> wrote:
>
> Hi Chen-Yu,
>
> On Fri, Jul 03, 2026 at 07:55:54PM +0800, Chen-Yu Tsai wrote:
> > Elan trackpad controllers require some delay after enabling power to
> > the controller for the hardware and firmware to initialize:
> >
> >   - 2ms for hardware initialization
> >   - 100ms for firmware initialization
> >
> > Until then, the hardware will not respond to I2C transfers. This was
> > observed on the MT8173 Chromebooks after the regulator supply for the
> > trackpad was changed to "not always on".
> >
> > Add proper delays after regulator_enable() calls. To avoid impacting
> > the boot time of existing devices that have the power rails always on,
> > skip the delay if the regulator supply was already enabled. In this
> > case the regulator is either always on, was on by default at power up,
> > or was left on by some other driver, such as the I2C OF component
> > prober. Either way the controller has had ample time to initialize.
>
> Unfortunately we do not know that [it had ample time]. For this code be
> reliable we need to record the time at which given regulator was turned
> on and then execute/adjust the delay as needed. Until we have it we need
> to assume the regulator was enabled at the time of regulator_enable()
> call.
>
> I am not concerned with increased boot time too much given the driver is
> set up for asynchronous probing.

I'm a bit confused since you raised the issue for ACPI platforms that don't
go through the regulator API. Now you're saying it's not a big issue?

Either way I will add some new regulator consumer APIs to handle this.
This needs to be in the regulator core to prevent race conditions, such
as when one thread deems the delay is enough just when another disables
the regulator.


ChenYu

^ permalink raw reply

* Re: [PATCH 02/13] m68k/coldfire: replace linux/gpio.h inclusions
From: Greg Ungerer @ 2026-07-06 11:22 UTC (permalink / raw)
  To: Arnd Bergmann, linux-gpio
  Cc: Arnd Bergmann, Bartosz Golaszewski, Andrew Lunn,
	Sebastian Hesselbarth, Gregory Clement, Frank Li, Robert Jarzmik,
	Krzysztof Kozlowski, Thomas Bogendoerfer, Hauke Mehrtens,
	Rafał Miłecki, Yoshinori Sato,
	John Paul Adrian Glaubitz, Linus Walleij, Dmitry Torokhov,
	Jakub Kicinski, Paolo Abeni, Dominik Brodowski, linux-kernel,
	linux-arm-kernel, linux-samsung-soc, patches, linux-m68k,
	linux-mips, linux-sh, linux-input, linux-media, netdev,
	linux-sunxi, linux-phy, linux-rockchip, linux-sound
In-Reply-To: <20260629132633.1300009-3-arnd@kernel.org>

Hi Arnd,

On 29/6/26 23:26, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
> 
> linux/gpio.h should no longer be used, convert these instead to
> linux/gpio/legacy.h for coldfire.
> 
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>

For the ColdFire changes, LGTM:

Reviewed-by: Greg Ungerer <gerg@linux-m68k.com>

Regards
Greg



> ---
>   arch/m68k/coldfire/device.c     | 2 +-
>   arch/m68k/include/asm/mcfgpio.h | 2 +-
>   2 files changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/m68k/coldfire/device.c b/arch/m68k/coldfire/device.c
> index 1420bae0964f..9a0258acd998 100644
> --- a/arch/m68k/coldfire/device.c
> +++ b/arch/m68k/coldfire/device.c
> @@ -12,7 +12,7 @@
>   #include <linux/init.h>
>   #include <linux/io.h>
>   #include <linux/spi/spi.h>
> -#include <linux/gpio.h>
> +#include <linux/gpio/legacy.h>
>   #include <linux/fec.h>
>   #include <linux/dmaengine.h>
>   #include <asm/traps.h>
> diff --git a/arch/m68k/include/asm/mcfgpio.h b/arch/m68k/include/asm/mcfgpio.h
> index 7103cfa4edb6..29726aa40eb6 100644
> --- a/arch/m68k/include/asm/mcfgpio.h
> +++ b/arch/m68k/include/asm/mcfgpio.h
> @@ -16,7 +16,7 @@ int __mcfgpio_request(unsigned gpio);
>   void __mcfgpio_free(unsigned gpio);
>   
>   #ifdef CONFIG_GPIOLIB
> -#include <linux/gpio.h>
> +#include <linux/gpio/legacy.h>
>   #else
>   
>   /* our alternate 'gpiolib' functions */


^ permalink raw reply

* [PATCH] HID: i2c-hid: Fix "(null)" device name error logs in i2c_hid_parse
From: Ai Chao @ 2026-07-06 12:29 UTC (permalink / raw)
  To: jikos, bentiss, dianders, treapking, kenkinming2002, raoxu,
	superm1
  Cc: linux-input, linux-kernel, Ai Chao

When i2c-hid fails to read the HID report descriptor during device
initialization, the error message prints as:

  hid (null): reading report descriptor failed

The HID device name is set in hid_add_device() after calling
hdev->ll_driver->parse(), so when i2c_hid_parse() fails and calls
hid_err(), the device name has not been set yet, resulting in "(null)"
output.

Use dev_err(&client->dev, ...) instead of hid_err(hid, ...) because
the I2C client device is fully initialized with a proper name, providing
meaningful error messages for debugging.

Before: hid (null): reading report descriptor failed
After:  i2c_hid i2c-TPD0001:00: reading report descriptor failed

Signed-off-by: Ai Chao <aichao@kylinos.cn>
---
 drivers/hid/i2c-hid/i2c-hid-core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 3adb16366e93..0e725a0f0abe 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -792,7 +792,7 @@ static int i2c_hid_parse(struct hid_device *hid)
 					    ihid->hdesc.wReportDescRegister,
 					    rdesc, rsize);
 		if (ret) {
-			hid_err(hid, "reading report descriptor failed\n");
+			dev_err(&client->dev, "reading report descriptor failed\n");
 			goto out;
 		}
 	}
-- 
2.34.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox