commit 55ad020b045625dde864cc6c91700dce84cbfd74
Author: Jake Koroman <jakekoroman@proton.me>
Date: Fri, 6 Dec 2024 09:05:50 -0500
Ready. Set. Go!
Diffstat:
A | Makefile | | | 6 | ++++++ |
A | README | | | 19 | +++++++++++++++++++ |
A | shifter.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");