Force feedback wheel support – status 4

I made the FFB support generic in GNU/Linux. The following wheels should be supported:

  • G27
  • Driving Force GT
  • G25
  • Momo Racing Force Feedback (tested)
  • Momo Force
  • Driving Force Pro
  • Logitech Speed Force Wireless
  • Formula Force GP (tested, the only working effect is vibration)

My current task is to make the code generic on Windows.

Joystick calibration in GNU/Linux

Note: joystick correction parameters can now be set in the GIMX configurations, so there’s no need anymore to do any calibration at the operating system level.

The reference tool to tweak joysticks from userland in GNU/Linux is jscal. It is a command line tool that can be used to calibrate or remap joystick controls. Its calibration mode (jscal -c /dev/input/jsX) sadly does not work for calibrating the pedals from my Logitech Momo Racing wheel. Surprisingly, the manual is very vague about how the correction works:

 -s, --set-correction <nb_axes,type,precision,coefficients,...>
Sets correction to specified values. For each axis, specify the
correction type (0 for none, 1 for "broken line"), the precision,
and if necessary the correction coefficients ("broken line" 
corrections take four coefficients).
-p, --print-correction
Prints the current correction settings. The format of the output
is a jscal command line.

What’s a “broken-line” correction? What are the coefficients used for? I only found the answer in the kernel source code:
static int joydev_correct(int value, struct js_corr *corr)
{
  switch (corr->type) {
  case JS_CORR_NONE:
    break;

  case JS_CORR_BROKEN:
    value = value > corr->coef[0] ? (value < corr->coef[1] ? 0 : 
            ((corr->coef[3] * (value - corr->coef[1])) >> 14)) : 
            ((corr->coef[2] * (value - corr->coef[0])) >> 14);
    break;

  default:
    return 0;
  }

  return value < -32767 ? -32767 : (value > 32767 ? 32767 : value);
}

The value calculation can be expanded like this:
if (value > corr->coef[0])
{
  if (value < corr->coef[1])
  {
    value = 0;
  }
  else
  {
    value = corr->coef[3] * (value - corr->coef[1])) >> 14;
  }
}
else
{
  value = corr->coef[2] * (value - corr->coef[0])) >> 14;
}

This leads to the following deductions:

  • coef[0] and coef[1] are two axis values
  • coef[2] and coef[3] are the correction factors that apply respectively below and above these axis values
  • coef[0] < coef[1] means there is a dead zone between these two axis values
  • coef[1] = coef[0] means there is no dead zone
  • coef[0] > coef[1] means coef[1] is ignored: it behaves as if coef[1] = coef[0]
  • the resulting value is divided by 16384

Let’s see what are the default correction settings for a Logitech Momo Racing wheel:

matlo@matlo-desktop ~ $ jscal -p /dev/input/js1
jscal -s 3,1,0,511,511,1050628,1050628,1,0,127,127,4227330,4227330,1,0,127,127,4227330,4227330 /dev/input/js1

Both gas and brake pedals have the same correction: 1,0,127,127,4227330,4227330
This maps a value from the [0,255] range to the [-32767,32767] range. The rest value 255 is mapped to 32767 when I expect it to be mapped to 0, and the “fully pressed” value 0 is mapped to -32767 when I expect it to be mapped to 32767…
To fix this, it’s possible to set coef[0] to 255 (which means coef[1] and coef[3] are not used and can be set to 0), and to set coef[2] to 32767 x 16384 / -255 = -2105312. The resulting jscal command is:
matlo@matlo-desktop ~ $ jscal -s 3,1,0,511,511,1050628,1050628,1,0,255,0,-2105312,0,1,0,255,0,-2105312,0 /dev/input/js1

Finally, it’s possible to save the calibration values:
matlo@matlo-desktop ~ $ sudo jscal-store /dev/input/js1
matlo@matlo-desktop ~ $ cat /var/lib/joystick/joystick.state 
NAME="Logitech  Logitech MOMO Racing "
VENDOR="046d"
PRODUCT="ca03"
jscal -u 3,0,1,2,10,288,289,290,291,292,293,294,295,296,297
jscal -s 3,1,0,511,511,1050628,1050628,1,0,255,0,-2105312,0,1,0,255,0,-2105312,0

Force feedback wheel support – status 3

As explained in status 1, GIMX has to convert the FFB report stream so that it can be transmitted to a wheel that cannot handle reports as fast as a G29 (my Momo racing wheel can handle one report each 8ms, and the G29 one report each 4ms). I wrote some code that handles this task the following way:

  1. on reception, decode the report according to the protocol specification (published by Logitech here), and only handle “download and play” and “stop” commands
  2. store the status of the effect into a slot (there are up to 4 effects), and push the effect id into a fifo
  3. if no write is pending:
    1. if multiple effects have to be stopped, build a report to stop them, and remove the effects from the fifo
    2. otherwise, get the next effect to update from the fifo (if any), and build a report
    3. finally, if a report has to be written, write it (this is non-blocking)
  4. when writing is complete (this is notified asynchronously), execute step 3

And that’s it, I made my Momo Racing wheel work with my PS4, including force-feedback!

My last task will be to make the code more generic so that other Logitech wheels can be used.

Force feedback wheel support – status 2

While searching for some HID parser code to parse HID input reports from Logitech wheels, I found an interesting driver that can do this job: UHID. It was written to allow connecting a userspace-managed device to the HID core driver of Linux. My first idea was to create a device with UHID, logically connected to the USB bus, with the same name and USB ids, and the fixed HID report from the Linux kernel. But using the same bus and the same USB ids makes the HID core driver load specific drivers (hid-lg, hid-lg4ff). The userspace-managed device I want to create is an input device only, since I’m already managing the HID OUT interrupt endpoint with libusb. The solution is to connect the device to the virtual bus. The last thing to care about is to set the same calibration parameters as the kernel does.

Force feedback wheel support – status 1

Some people may have noticed the presence of two new firmwares since GIMX 4.0: EMUG27PS3 and EMUT300RSPS4. There is also another new firmware in the git repository: EMUG29PS4. These firmwares can be used with the DIY USB adapter. Many thanks to tps and InhexSTER for helping me implement these firmwares!

As it may not be obvious, these firmwares have the following purposes:

  • EMUG27PS3: emulate a Logitech G27 wheel to use with a PS3
  • EMUT300RSPS4: emulate a Thrustmaster T300RS wheel to use with a PS4
  • EMUG29PS4: emulate a Logitech G29 wheel to use with a PS4.

For now, only controls can be used. But force feedback support is coming! This is possible  thanks to the following findings:

  • the DS4 can be used as an authentication source for PS4 wheels
  • the PS3 and the PS4 send FFB data to the Logitech wheel using the FFB protocol that Logitech published here

This means that it is possible to forward FFB data to almost any Logitech wheel including my old Momo racing wheel 🙂

My current work is to make a communication layer to manage Logitech wheels, that are standard HID devices. One key feature of this communication layer is to support asynchronous transfers, which allows to initiate transfers without waiting (blocking) for the results. This is the only way to efficiently process inputs from multiple devices at the same time (one thread per device is not efficient to me). This communication layer also has to be cross-platform (Linux and Windows). GIMX already talks to HID devices using the hidapi library, but this library does not support asynchronous transfers (this is the reason why GIMX does not support rumble with GPP/Cronus/Titan devices). After realizing that no library was providing this, I decided to write my own one, that uses overlapped IO in Windows, and the hidraw interface in Linux. It turned out that the hidraw interface does not support asynchronous writes, which forced me to use the libusb library instead. When using the libusb library, the kernel drivers are detached (unloaded), including the driver that parses HID input packets and translates them into input events (i.e. joystick buttons and axes). This means that GIMX has to do this job, which may be a time consuming development considering each Logitech wheel can have a different HID input format.

There is another non-trivial task that I’ll have to work on. I was thinking that the bInterval value for a USB interrupt out endpoint would restrict the USB host from sending more than one transfer each bInterval period on this endpoint. But in fact a USB device can accept more than that: the G29 has a bInterval of 5ms for its interrupt out endpoint, but it can take at least one out report each 4ms; the Momo racing has a bInterval of 10ms for its interrupt out endpoint, but it can take at least one out report each 8ms. Therefore, changing the bInterval for the interrupt out endpoint of the EMUG29PS4 firmware would not allow to work-around the mismatch of the out report period (I tried it before realizing that fact, and it made my PS4 randomly crash!). This means that I can’t forward all interrupt out transfers to my Momo racing wheel: I have to drop or combine some transfers without altering the FFB effects.