Writing a Linux User Space Driver for Densha de Go! Ryojōhen Controller

Part 1 - USB Descriptor and libusb

July 28, 2024

Background

A few days ago, I was able to get one of the Densha de Go! Ryojōhen Controller (電車でGO! 旅情編コントローラ) similar to the picture shown below from an online auction on eBay. The model number is TCPP-20014. It was released by TAITO in 2002 and was designed for the Densha de Go! Ryojōhen game on Sony PlayStation 2 accoring to the controller documentation by @marcriera on GitHub .

tcpp20014

電車でGO! 旅情編コントローラ (TCPP-20014)

Surprisingly, the controller does have a USB 1.1 connector. However, upon connecting to my computer, the operating systems (Windows / Ubuntu) can't read data from it as it claims to be a Human Interface Device while failed to provide the needed HID report descriptor.

If you are using Windows, some work already has been done to support this controller. You can try to use the controller convertor tool (電車でGo!コントローラー変換器) provided by @autotraintas to test your controller and play games with it. More details can be found here .

However, as a gamer on Linux using PCSX2 emulator, I would need to write a custom user space driver to connect my controller with the Densha de Go! game running on the emulator.

USB Descriptor

As an effort to familar myself with the controller, I first dumped its USB descriptor using the lsusb tool.

Bus 003 Device 007: ID 0ae4:0007 Taito Corp. TAITO_DENSYA_CON_T03
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass          255 Vendor Specific Class
  bDeviceSubClass       255 Vendor Specific Subclass
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0ae4 Taito Corp.
  idProduct          0x0007 
  bcdDevice            1.00
  iManufacturer           1 TAITO
  iProduct                2 TAITO_DENSYA_CON_T03
  iSerial                 3 TCPP20014
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0019
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              500mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 
      bInterfaceProtocol      0 
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              20
Device Status:     0x0002
  (Bus Powered)
  Remote Wakeup Enabled

C++ and libusb

Based on the USB descritor, we can see that the controller data has a maximum of 8 bytes and can be accessed through endpoint address 0x81 via interrupt transfer. With these data in mind, I wrote a C++ program to retrive these data using libusb.

#include <array>
#include <cstdio>
#include <signal.h>
#include "libusb-1.0/libusb.h"

#define BUFFER_SIZE 8

bool quit = false;

void quit_handler(int s);

int main()
{
    signal(SIGINT, quit_handler);

    libusb_context *ctx = nullptr;
    libusb_init(&ctx);

    libusb_device_handle *deviceHandle = libusb_open_device_with_vid_pid(ctx, 0x0ae4, 0x0007);

    libusb_set_configuration(deviceHandle, 1);
    libusb_claim_interface(deviceHandle, 0);

    while (!quit)
    {
        std::array<unsigned char, BUFFER_SIZE> buffer;

        // Get controller data
        libusb_interrupt_transfer(deviceHandle, 0x81, buffer.data(), BUFFER_SIZE, NULL, 100);

        for (const unsigned char &data : buffer)
        {
            std::printf("%02X ", data);
        }
        std::printf("\n");

        usleep(20 * 1000);
    }

    libusb_release_interface(deviceHandle, 0);
    libusb_close(deviceHandle);
    libusb_exit(ctx);
}

void quit_handler(int s)
{
    quit = true;
}

Demo

We can now read the controller data in Linux after compiling and running the above program.

demo

Controller Data Meaning
Byte Number Data Items Meaning
1 0x24 Brake Released
2 0x00 Power Neutral
3 0xFF Pedal Released
4 0x08 DPad Center
5 0x00 Buttons Released
6 - 8 0x000000 N/A N/A

Future Steps

  • Emulate this HID controller as a standard gamepad using the Linux kernel module called uinput.

Reference