odor-truckshift-driver

a simple usb driver for the ODOR-TRUCKSHIFT
git clone git://git.jakekoroman.com/odor-truckshift-driver
Log | Files | Refs | README

commit 55ad020b045625dde864cc6c91700dce84cbfd74
Author: Jake Koroman <jakekoroman@proton.me>
Date:   Fri,  6 Dec 2024 09:05:50 -0500

Ready. Set. Go!

Diffstat:
AMakefile | 6++++++
AREADME | 19+++++++++++++++++++
Ashifter.c | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 205 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,6 @@ +obj-m += shifter.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean diff --git a/README b/README @@ -0,0 +1,19 @@ +ODOR-TRUCKSHIFT linux driver +============================ + +simple usb driver for this shifter https://www.amazon.com/American-Simulator-Gearshift-PC-Compatible-Thrustmaster/dp/B093WN8H8N + +the hid_generic driver doesn't properly detect this device, i imagine because the device is using a weird non-standard hid implementation. +luckily for us the device is extremely simple with just 3 buttons. + +usage +----- + +simply build it and load it. will require kernel-headers to build. + +# make +# insmod ./shifter.ko + +now it should be recognized as ODOR-TRUCKSHIFT. you can test it with evtest or within a game. + +enjoy :) diff --git a/shifter.c b/shifter.c @@ -0,0 +1,180 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/usb.h> +#include <linux/hid.h> +#include <linux/input.h> +#include <linux/slab.h> // For kmalloc +#include <linux/interrupt.h> + +#define DEVICE_VENDOR_ID 0x1020 +#define DEVICE_PRODUCT_ID 0x8863 +#define DEVICE_NAME "ODOR-TRUCKSHIFT" + +static struct usb_device_id usb_device_table[] = { + { USB_DEVICE(DEVICE_VENDOR_ID, DEVICE_PRODUCT_ID) }, + {} +}; +MODULE_DEVICE_TABLE(usb, usb_device_table); + +struct truckshifter { + struct usb_device *udev; + struct input_dev *input_device; + unsigned char *input_buffer; +}; + +static void parse_hid_report(struct truckshifter *truckshifter, unsigned char *data) { + // the magic byte + unsigned char button_data = data[0]; + + if (button_data & 0x01) { + input_event(truckshifter->input_device, EV_KEY, BTN_0, 1); + } else { + input_event(truckshifter->input_device, EV_KEY, BTN_0, 0); + } + + if (button_data & 0x02) { + input_event(truckshifter->input_device, EV_KEY, BTN_1, 1); + } else { + input_event(truckshifter->input_device, EV_KEY, BTN_1, 0); + } + + if (button_data & 0x04) { + input_event(truckshifter->input_device, EV_KEY, BTN_2, 1); + } else { + input_event(truckshifter->input_device, EV_KEY, BTN_2, 0); + } + + input_sync(truckshifter->input_device); +} + +static void read_hid_report(struct urb *urb) { + struct truckshifter *truckshifter = urb->context; + + if (urb->status) { + printk(KERN_ERR "USB HID report error: %d\n", urb->status); + return; + } + + parse_hid_report(truckshifter, urb->transfer_buffer); + + int retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + printk(KERN_ERR "Failed to resubmit URB\n"); + } +} + +static int start_reading_hid_report(struct truckshifter *truckshifter) { + struct usb_interface *interface = truckshifter->udev->actconfig->interface[0]; + struct usb_endpoint_descriptor *endpoint; + struct urb *urb; + int pipe; + int max_packet_size; + + endpoint = &interface->cur_altsetting->endpoint[0].desc; + pipe = usb_rcvintpipe(truckshifter->udev, endpoint->bEndpointAddress); + max_packet_size = usb_endpoint_maxp(endpoint); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + printk(KERN_ERR "Failed to allocate URB\n"); + return -ENOMEM; + } + + truckshifter->input_buffer = kmalloc(max_packet_size, GFP_KERNEL); + if (!truckshifter->input_buffer) { + printk(KERN_ERR "Failed to allocate buffer\n"); + usb_free_urb(urb); + return -ENOMEM; + } + + usb_fill_int_urb(urb, truckshifter->udev, pipe, truckshifter->input_buffer, max_packet_size, + read_hid_report, truckshifter, endpoint->bInterval); + + return usb_submit_urb(urb, GFP_KERNEL); +} + +static int usb_driver_probe(struct usb_interface *interface, const struct usb_device_id *id) { + struct truckshifter *hid_device; + struct usb_device *dev = interface_to_usbdev(interface); + struct input_dev *input_device; + int retval; + + hid_device = kzalloc(sizeof(struct truckshifter), GFP_KERNEL); + if (!hid_device) { + printk(KERN_ERR "Failed to allocate memory for HID device\n"); + return -ENOMEM; + } + + hid_device->udev = dev; + + input_device = input_allocate_device(); + if (!input_device) { + printk(KERN_ERR "Failed to allocate input device\n"); + kfree(hid_device); + return -ENOMEM; + } + + hid_device->input_device = input_device; + + input_device->name = DEVICE_NAME; + input_device->evbit[0] = BIT_MASK(EV_KEY); + input_device->keybit[BIT_WORD(BTN_0)] |= BIT_MASK(BTN_0); + input_device->keybit[BIT_WORD(BTN_1)] |= BIT_MASK(BTN_1); + input_device->keybit[BIT_WORD(BTN_2)] |= BIT_MASK(BTN_2); + + retval = input_register_device(input_device); + if (retval) { + printk(KERN_ERR "Failed to register input device\n"); + input_free_device(input_device); + kfree(hid_device); + return retval; + } + + retval = start_reading_hid_report(hid_device); + if (retval) { + printk(KERN_ERR "Failed to start reading HID report\n"); + input_unregister_device(input_device); + kfree(hid_device); + return retval; + } + + usb_set_intfdata(interface, hid_device); + + printk(KERN_INFO "ODOR-Truckshift connected\n"); + return 0; +} + +static void usb_driver_disconnect(struct usb_interface *interface) { + struct truckshifter *hid_device = usb_get_intfdata(interface); + + if (hid_device) { + if (hid_device->input_buffer) + kfree(hid_device->input_buffer); + input_unregister_device(hid_device->input_device); + kfree(hid_device); + } + + printk(KERN_INFO "ODOR-Truckshift disconnected\n"); +} + +static struct usb_driver custom_hid_driver = { + .name = "custom_hid_gamepad", + .id_table = usb_device_table, + .probe = usb_driver_probe, + .disconnect = usb_driver_disconnect, +}; + +static int __init usb_driver_init(void) { + return usb_register(&custom_hid_driver); +} + +static void __exit usb_driver_exit(void) { + usb_deregister(&custom_hid_driver); +} + +module_init(usb_driver_init); +module_exit(usb_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jake Koroman <jakekoroman@proton.me>"); +MODULE_DESCRIPTION("Driver for the Labtec ODDOR-TRUCKSHIFT usb truck shifter");