Skip to content

Protocol Force Feedback

mescon edited this page Jul 20, 2026 · 1 revision

Protocol: Force Feedback

How force reaches the motor: the dedicated interface-2 endpoint stream, the packet encoding, and the driver's software effect engine. Part of the Protocol Specification; the audio-haptic layer that shares this endpoint is in TrueForce Protocol.

One packet family, two roles

Endpoint 0x03 OUT (interface 2) carries one 64-byte type-0x01 packet family. Byte 10, the "new samples this packet" count, demultiplexes it:

  • 0: a pure constant-force ("KF") update, documented here.
  • 4: a unified force+audio ("TF") packet whose bytes 12-63 carry a rolling haptic sample window on top of the same force field; see TrueForce Protocol for the full framing.

Constant-force report (64 bytes, byte 10 = 0)

Offset  Size  Type    Description
------  ----  ----    -----------
0       1     u8      Report ID (0x01)
1-3     3     -       Reserved (0x00 0x00 0x00)
4       1     u8      Command type (0x01 = stream/sample packet)
5       1     u8      Sequence counter (0x00-0xFF, wraps)
6-7     2     u16 LE  Force value / motor torque target ("cur")
8-9     2     u16 LE  Duplicate (must match bytes 6-7)
10      1     u8      New-sample count (0 here; 4 = TF audio packet)
11-63   53    -       Zero for a constant-force packet

Bytes 6-9 are the motor torque target ("cur" in TrueForce terms) and are honoured whether or not the packet also carries audio: a constant-force packet is a TF stream packet with an empty sample window. The sequence counter is a single byte wrapping at 255 (unlike the two-byte HID++ sequence) and is shared across all interface-2 packet types.

Force encoding (offset binary)

Value Force
0x0000 maximum LEFT
0x8000 neutral
0xFFFF maximum RIGHT
uint16_t offset_binary = (int16_t)signed_value + 0x8000;

Rates

Games stream at 1000 Hz on Windows (median inter-packet gap ~1.0 ms in every gameplay capture); the kernel driver's own force stream runs at 500 Hz. There is no idle keepalive: the endpoint is silent when no effect plays. (An earlier revision documented an 05 07 "refresh" packet; capture re-analysis proved that was a DualShock 4 lightbar report to a different device that was plugged in at capture time, and the driver does not send it.)

The driver's effect engine

The wheel firmware only understands raw constant forces on this endpoint, so the driver emulates the complete Linux FF effect set on top of it:

  • FF_CONSTANT, FF_RAMP, FF_PERIODIC (sine, square, triangle, saw up, saw down), and the four condition effects FF_SPRING, FF_DAMPER, FF_FRICTION, FF_INERTIA.
  • A 500 Hz timer walks the active-effect slots, sums each effect's instantaneous contribution, applies FF_GAIN, and sends one net force value. The timer keeps running whenever any effect is playing, even if the current net force is zero (a spring at exact centre must push the moment the wheel moves).
  • Condition effects read the live wheel state (position from the interface-0 report, velocity and acceleration derived at the tick) and apply the standard ff_condition_effect formula; waveform and envelope semantics match Documentation/input/ff.rst.
  • Emulated springs get synthetic damping (wheel_spring_damping) because an undamped host-side spring on a low-friction direct-drive motor rings; see the Sysfs API Reference.
  • Vibration-class effects (rumble, periodics at 20 Hz or faster) can route to the TrueForce audio channel instead of the steering sum (wheel_texture_route), matching the Windows KF/TF split.
  • FFB must be sent from process context (workqueue); the driver uses hid_hw_output_report() on interface 2.

Architecture note: two force transports

The direct-drive wheels expose two independent force paths, and which one a host uses is a choice, not a hardware constraint:

  • HID++ feature 0x8123 fn2: the path Logitech's own Windows runtime uses for normal game FFB on these wheels. Report 0x11/0x12, device index 0xFF, signed int16 BE motor target at payload offset 10-11, sent at the game's rate (~140-333 Hz observed). Set-and-hold: the wheel maintains the last commanded force indefinitely with no keepalive.
  • Endpoint 0x03 (this page): the TrueForce session channel, used by SDK-native games and by this Linux driver for ALL force output. While a TrueForce session is active, the packet's "cur" field OVERRIDES the 0x8123 path, and the sample window plays additively on top.

The G920/G923 comparison in the Protocol Specification therefore describes defaults, not capabilities: the older wheels have only 0x8123, while the DD wheels have both. A minimal 0x8123-fn2 sender remains an untested alternative transport for this driver (potentially relevant if the wheel's FFB-filter smoothing turns out to apply only to that path); the G920-class slot-based 0x8123 engine, however, saturates the queue on these wheels and cannot be reused as-is.

Reference C structure

#define HIDPP_DD_FF_REPORT_ID       0x01
#define HIDPP_DD_FF_REPORT_SIZE     64

struct hidpp_dd_ff_report {
    u8 report_id;       /* 0x01 */
    u8 reserved[3];     /* 0x00, 0x00, 0x00 */
    u8 effect_type;     /* 0x01 = constant force */
    u8 sequence;        /* 0x00-0xFF, wraps */
    __le16 force;       /* 0x0000=left, 0x8000=center, 0xFFFF=right */
    __le16 force_dup;   /* duplicate of force value */
    u8 padding[54];     /* zeros */
} __packed;

Autocenter

These wheels have no hardware autocenter setting (G HUB exposes none); the driver provides a real host-side centring spring instead, driven by the autocenter sysfs attribute or the standard evdev FF_AUTOCENTER control, summed in the same 500 Hz loop.

Clone this wiki locally