// 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 #include #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 "); MODULE_DESCRIPTION("Driver for Nintendo Switch 2 Controllers");