blob: 8b3b6841035d065ceda1a82e84b7eba4aad6e445 [file]
// 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-ids.h"
#include "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:
dev_dbg(&ns2_usb->udev->dev, "shutting down input urb: %d\n", urb->status);
return;
default:
dev_dbg(&ns2_usb->udev->dev, "unknown input urb status: %d\n", urb->status);
break;
}
spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
if (ns2_usb->shutdown)
schedule = false;
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 {
ns2_usb->bulk_in[i].state = NS2_URB_FREE;
}
}
if (ns2_usb->bulk_in[i].state != NS2_URB_FREE)
continue;
if (ns2_usb->shutdown)
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))
continue;
ns2_urb = &ns2_usb->bulk_in[i];
if (!ns2_urb)
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:
dev_dbg(&ns2_usb->udev->dev, "shutting down output urb: %d\n", urb->status);
return;
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_free_in_urbs(struct switch2_usb *ns2_usb)
{
struct urb *urb;
uint8_t *data;
int i;
unsigned long flags;
spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
ns2_usb->shutdown = true;
spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
cancel_work_sync(&ns2_usb->message_in_work);
usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
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_kill_urb(urb);
usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
usb_free_urb(urb);
}
}
static void switch2_usb_free_out_urbs(struct switch2_usb *ns2_usb)
{
struct urb *urb;
uint8_t *data;
int i;
unsigned long flags;
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_kill_urb(urb);
usb_free_coherent(ns2_usb->udev, NS2_BULK_SIZE, data, urb->transfer_dma);
usb_free_urb(urb);
}
}
static void switch2_usb_shutdown(struct switch2_cfg_intf *intf)
{
struct switch2_usb *ns2_usb = (struct switch2_usb *)intf;
int i;
unsigned long flags;
spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
ns2_usb->shutdown = true;
spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
cancel_work_sync(&ns2_usb->message_in_work);
usb_kill_anchored_urbs(&ns2_usb->bulk_in_anchor);
spin_lock_irqsave(&ns2_usb->bulk_in_lock, flags);
for (i = 0; i < NS2_IN_URBS; i++)
ns2_usb->bulk_in[i].state = NS2_URB_FREE;
spin_unlock_irqrestore(&ns2_usb->bulk_in_lock, flags);
}
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);
if (!urb->urb)
/* We're shutting down and this URB was freed */
break;
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))
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;
ns2_usb->cfg.shutdown = switch2_usb_shutdown;
ret = switch2_controller_attach_cfg(phys, &ns2_usb->cfg);
if (ret < 0)
goto err_free_out;
return 0;
err_free_out:
switch2_usb_free_out_urbs(ns2_usb);
err_free_in:
switch2_usb_free_in_urbs(ns2_usb);
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);
switch2_usb_free_in_urbs(ns2_usb);
/*
* 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.
*/
if (ns2_usb->cfg.parent)
switch2_controller_detach_cfg(ns2_usb->cfg.parent);
switch2_usb_free_out_urbs(ns2_usb);
}
#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");