hid: tmff2: add Thrustmaster T500RS wheel base driver - #186
Conversation
2ae18a3 to
bd43bb9
Compare
|
Hi @Kimplul , Here is a new version of the driver, fully rebuilt from scratch. I've been trying to do my best to get it done the right way, steering its content the best I could (with my knowledge). I'll discard the previous PR that is not optimal. This one should be supporting more effects, and should be better designed. I've been also working on documentation to make it more comprehensive and based on SDL2 real examples. Again, I am fully open to your feedback (and also @MmAaXx500 ones) that were really valuable by the other PR, so when you will have time, I'd really happy to get to your review. Thanks |
Kimplul
left a comment
There was a problem hiding this comment.
Thanks for continuing on this project, I was getting worried that we scared you off :)
I did a quick overview. The documentation in particular looks a lot better, having clear examples of what each packet does and how they build up to a full effect upload helped greatly. I haven't done any cross-referencing between the docs and implementation, as I still found quite a lot of smaller issues I'd like taken care of first. I'll do a more thorough review after that.
Not entirely sure what you mean by 'from scratch', for instance the comment explaining the set_gain rationale is identical between this and the previous PR.
|
Got the code and the PR updated based on all your feedback. All were valid, the only ones I haven't worked on are the SQUARE effect I plan to work on later and the git version (that I can easily remove). |
9e6319b to
b7d188d
Compare
|
Hi @Kimplul , I've been working again on the PR, responding to all your points (almost all -- if not all -- were relevant), and introducing some missing features (FF_SQUARE missing and 0x05 conditional effect that was incorrectly done). If you mind having another pass, I'd be really happy. |
|
Sorry about the delay, bit busy before the end of the year. I had a look through the resolved comments, most looked good but there were a few that I decided to unresolve, please have a second look at them. I still haven't cross-referenced documentation to the code, I'll try and get that done by the end of next week, but based on a cursory look the code looks a lot better now. Seems you managed to shave off ~400 lines of code, well done. Please also add a copyright statement to the start of the files you created, something like https://elixir.bootlin.com/linux/v6.18.1/source/drivers/hid/hid-retrode.c for example. I really should do that as well to the bits I wrote, but this is the first 'major' new addition to the driver so I've never really had any reason to think about it before. |
|
No worries, thank you for the feedback you provided. Take the necessary time for whatever need review. I am not in the hurry, and this driver does work for me already so It can last as long as necessary from your end. I'm happy we trend to get somewhat a stable mergeable version. Cheers |
408f5bf to
1c5af2c
Compare
That's the responsibility of |
It is indeed not working with the current code. I'll revert this. Not sure how I should update the init driver yet. Maybe just specifying the boot mode, will test. EDIT: I just open a PR against hid-tminit for TSCP branch (even though it's t500rs code update): Kimplul/hid-tminit#2 I tested and it appears to work fine with that code and my driver (and the mode switch reverted from within the driver) |
Yeah, I intended to merge |
|
There we are @Kimplul , I've been addressing all the points I guess, and introduced some fix also here or there (you can check latest commits, small scoped). Take your time for the documentation cross-check and let me know, in parallel I'm trying to improve the bits where I can |
Kimplul
left a comment
There was a problem hiding this comment.
Sorry about the delay, I've been busier than expected. I mainly focused on the docs and found some inaccuracies and points of improvement, please have a look at those.
I applied some style fixes myself, please cherry-pick them from https://github.com/Kimplul/hid-tmff2/tree/cazzoo-hardening/t500rs-driver. I don't have push access to your branch and opening a PR for a PR seemed a bit silly, but the changes were trivial enough that I didn't really want to request them explicitly. Kind of a clumsy part of the PR flow, but eh.
Regarding style, I've tried to follow the Linux Kernel style guide in this repo. Most significantly, it dictates a tab width of 8 spaces, whereas this has a width of 2, please fix that. You might also want to try running your code through clang-format with the kernel's style configuration, see https://www.kernel.org/doc/html/next/dev-tools/clang-format.html
1bf936c to
5c31c0e
Compare
|
Thank you @Kimplul for all the feedback. No worries for the delay, I also had a lot of parallel work to take care of in the meantime. Now I believe the code and much more ready, and the documentation updated appropriately and more accurately. Please have another pass and I'd be happy to change anything that's not fine by your POV. |
5015d21 to
3e8c027
Compare
Kimplul
left a comment
There was a problem hiding this comment.
At least the documentation still needs a bit of work. I would suggest maybe to read through everything a couple of times from a perspective of someone who doesn't know anything about the wheel, reading from top to bottom to get a good overall picture of what the wheel does and how. Present things in the order that they're needed, lots of examples, try to maintain a consistent way to present information, stuff like that.
| 41 00 41 01 - START command | ||
| ``` | ||
| - **Packet Type:** 0x41 (Command) | ||
| - **Effect ID:** 0x00 (always 0x00 for T500RS) |
There was a problem hiding this comment.
I believe we've already established that the effect ID varies depending on what effects are loaded, no?
| 41 00 00 01 - STOP command | ||
| ``` | ||
| - **Packet Type:** 0x41 (Command) | ||
| - **Effect ID:** 0x00 (always 0x00 for T500RS) |
|
|
||
| **Important Notes:** | ||
| - **Envelope Support:** Envelope packets (0x02) have limited support. Non-zero envelope values cause EPROTO errors on periodic and constant effects. Always send zeros for envelope parameters on these effect types. | ||
| - **Runtime Updates:** Effect updates (via `update_effect` callback) only modify parameter-specific packets (0x03, 0x04, 0x05). Duration and delay changes require re-uploading the entire effect. |
There was a problem hiding this comment.
Please clarify that this is only a limitation of this driver, and not of the FFB subsystem in general. Just in case someone reference this documentation when trying to figure out how FFB works.
| 4 | 2 | duration_ms | Duration in milliseconds, little-endian | ||
| 6 | 2 | delay_ms | Delay before start, little-endian | ||
| 8 | 1 | reserved1 | 0x00 | ||
| 9 | 2 | packet_code_1 | Code for subsequent packet type (variable!) |
There was a problem hiding this comment.
packet_code_1 and packet_code_2 are the same as param_sub and envelope_sub, are they not? I think param_sub and envelope_sub are more descriptive terms, the code uses them as well.
| | 0x40 | Spring | Windows driver captures | | ||
| | 0x41 | Damper/Friction/Inertia | Windows driver + FFEdit captures | | ||
|
|
||
| **Note:** Square wave (0x20) was discovered in FFEdit captures. The Windows driver may not expose this effect type through the standard API. |
There was a problem hiding this comment.
The windows driver most definitely exposes the square wave. FFEdit uses the Windows API, so I'm not entirely sure what this is referencing anyway?
|
|
||
| **Parameter Details:** | ||
| - Magnitude: 0-127 (scaled from Linux 0-32767) | ||
| - Offset: s8 (-128 to +127, DC bias (Direct Current bias - a constant force offset), scaled from Linux -32768 to +32767) |
There was a problem hiding this comment.
Direct Current bias - a constant force offset I mean I know what you're trying to say, but you're still not saying very well and I'm not sure a first-time reader would really get what you're trying to say.
A graphical example would probably be pretty good here, maybe you could try for some ASCII art? At the very least, I would like to see it explained out in full, something like A constant offset means that the periodic effect is stronger when twisting the wheel to one side and weaker to the other. Feel free to come with your own suggestions, I'm not much of a wordsmith :)
| - Magnitude: 0-127 (scaled from Linux 0-32767) | ||
| - Offset: s8 (-128 to +127, DC bias (Direct Current bias - a constant force offset), scaled from Linux -32768 to +32767) | ||
| - Phase: 0-255 (256 steps for 360 degrees, scaled from Linux 0-35999) | ||
| - Period: Direct milliseconds (no Hz conversion!) |
| - effect_type = 0x21 (triangle wave) | ||
| - Same envelope and periodic packet structure as sine wave | ||
|
|
||
| **Note:** Waveform type determined by effect_type in main packet, not in periodic packet parameters. |
There was a problem hiding this comment.
Would it be correct to say that each waveform type is its own effect_type?
| | 6 | 0x00b6 | 0x00c4 | | ||
|
|
||
| ### Driver Implementation Notes | ||
| - **Effect ID Handling:** The driver uses hardware IDs 1-15 to avoid quirky behavior with hardware index 0 (only valid for constant effects). |
There was a problem hiding this comment.
This is already mentioned above. Sometimes repetition can be useful, but there's only like 15 lines between each occurence so one or the other should probably be removed.
| - **Device Format:** 16-bit little-endian (0-35999 in 0.01 degree units) | ||
| - **Conversion:** `device_dir = (os_ffb_dir * 36000) / 65536` | ||
| - **Examples:** | ||
| - 0degrees = 0x0000 |
There was a problem hiding this comment.
I believe it's customary to put a space between the number and degrees, presumably because degree is a whole word. Short forms, such as ms for millisecond, can be written without a space between.
|
I have been following this development with some excitement and just tried to build and run the current PR. I do not know how to properly mention files in a comment, but in the hid-tmt500rs-usb.c file there is a capital i after a semicolon. |
|
Good catch! Thank you very much for the report |
|
@SeitzB I think you have the wrong branch checked out, |
Kimplul
left a comment
There was a problem hiding this comment.
I added some comments about the most recent stylistic changes. Looks like clang-format did something weird with the whitespacing in multi-line comments, or maybe they were weird to begin with and clang-format just choked. Dunno.
| u8 phase, u16 period_ms) | ||
| { | ||
| /* Byte order per Windows USB captures (example: 04 2a 00 06 00 3f 0a 00): | ||
| * b0=T500RS_PKT_PERIODIC, b1=code, b2=reserved1, b3=mag, b4=offset, |
There was a problem hiding this comment.
Quick nitpick: Something funky with the comments, seems to use mixed spaces and tabs?
| /* Wheel handles positive magnitudes only */ | ||
| projected = -projected; | ||
|
|
||
| /* Add 180 degrees to phase to maintain correct force direction. |
| offset = (s8)((end_level - start_level) / 512); | ||
|
|
||
| /* | ||
| * Phase encodes ramp direction per FFEdit captures: |
| */ | ||
| phase = (start_level < end_level) ? 0x7f : 0x00; | ||
|
|
||
| /* Byte order per USB captures: b0=id, b1=code, b2=reserved1, b3=mag, |
| p->id = 0x02; | ||
| p->subtype = subtype; | ||
|
|
||
| /* |
|
|
||
| T500RS_DBG(t500rs, "Sending initialization sequence...\n"); | ||
|
|
||
| /* Report 0x42 - Init/status commands (2 bytes each) |
| hid_warn(t500rs->hdev, "Init command 0x42 0x00 failed: %d\n", | ||
| ret); | ||
|
|
||
| /* Report 0x40 - Enable FFB (4 bytes) |
| hid_warn(t500rs->hdev, | ||
| "Init command 3 (0x40 config) failed: %d\n", ret); | ||
|
|
||
| /* Report 0x43 - Set global gain (2 bytes) |
| u8 right_sat = (cond->right_saturation * 100) / 65535; | ||
| u8 left_sat = (cond->left_saturation * 100) / 65535; | ||
|
|
||
| struct t500rs_pkt_r05_condition *p = |
There was a problem hiding this comment.
Not required to get merged, but the high level of indent here makes the code a bit annoying to read, some kind of refactoring could be useful here, such as instead of t500rs_build_r05_condition and t500rs_send_hid you could instead just have t500rs_send_condition.
| break; | ||
| } | ||
|
|
||
| case T500RS_SEQ_CONDITION_Y: { |
There was a problem hiding this comment.
Does anyone use the second axis? The T300 only uses effect->u.condition[0], and I don't remember seeing kernel drivers use condition[1].
|
Commit c6d27c3 says:
That's not true. Most of the issues I pointed out still remain. Did you push the commit by accident or what's the deal here? |
|
That was indeed not intended to be pushed straight away, I'm still having a few time to work on it and probably pressed the wrong button. Sorry for the inconvenience |
|
Hi folks, I used the git command above to follow the hardened branch in the hid-tmff2 directory, recompiled the modules (which worked) and installed them using sudo make install and install-udev. I still have to use the "tmdrv" command or "oversteer" will complain about "no device found". Once I run tmdrv to init the wheel, the driver gets loaded according to "dmesg". FFB is back in AC Rally and Automobilista 2. I had to re-assign the controls in AC Rally but a major patch was released today (0.3, it's still in early access) so that may have been the cause. AM2 did not require any reassignments. in rfactor2, I selected "disable SteamInput" and checked the Proton version I'm using (9.0) with this title. I'm happy to report that this time, rfactor2 had FFB until I returned from 3d on-track to the 2d UI. Re-entering the track a 2nd time, FFB was gone again. I have not rebooted the machine in between game launches, but I'll try that next (hopefully not losing my comment here for the umpteenth time :-)) and report back. All the best, Uwe @cazzoo It's very kind of you the shell out real money in order to test rfactor2. If you'd like to be re-imbursed for your troubles I'm happy to contribute :-) |
|
I nailed the issue a bit down and found some more clues. So confirming the issue happens for both games, I found that the game (when doing alt+tab or get back to pits) is stopping effects. When entering back in race, somehow, the effects are not started back.. So I tested adding an extra re-init script (manually triggered) : Stop work handler, Reset all effect states and then Reinitialize wheel (tmff->open), and that worked! So I don't exactly know if there's a bug to fix in Wine/Proton, but that workaround worked. I'm trying to get it added to the base driver but I'm not entirely convinced this is the right place, especially knowing this is shared code with other wheel bases... |
I don't think that's a good approach. From the logs it looks like effects 0-3 are all requesting a start, which the driver should be able to handle just fine. Not sure why the game seems to be sending multiple stop signals, but that shouldn't matter. Looks like all the messages are printed within ~50 milliseconds. Is it maybe possible that the driver handles the STOP requests, and then ignores the START requests because they arrived 'too soon'? |
|
Thanks for putting in the work debugging this folks. rfactor2 supports plugins. Is this something that could be handled on a plugin level maybe? rfactor2 even has an FFB reset function (I wonder why) but sadly this does nothing to fix the problem in this case (I've tried :-)) Sorry in advance if this is a stupid idea and / or won't work, but maybe a few lines in an rf2 / RR plugin is all that is needed... All the best, Uwe |
|
@hoover67 As far as I can tell, the game itself (+Proton+SDL etc.) is behaving as it should and is following the FFB API, but the driver is not responding correctly, therefore the driver should be fixed. I'm sure there are all kinds of workarounds that could be implemented, but I don't want to accept clearly incorrect code into this repository. I hope there's no rush here and we can take the time to do things properly :) |
|
Maybe it could be helpful if we looked at what a working driver does? I have a Logitech G27 which has relatively mature drivers afaik. I could buy rfactor and try it with the g27 and provide you with whatever logs might be helpful to you. And if it also doesn't work with the G27 we would have confirmation that it may not be a driver issue after all? Just an offer |
That's an interesting offer, if you mind that could definitely help. Rfactor2 is not overpriced, and you can get refund if you manage to capture information rather quick |
|
@SeitzB Appreciate the offer, can't hurt to compare against another implementation but at least my T300 works with RRRE and rFactor2. Or, at the very least has worked, haven't played either in a while but either way the games themselves are likely working as intended. |
|
Just to let you know, I figured that if I restrict the driver to use only 1 effect slot, I don't have the issue. |
|
@Kimplul, I managed to understand what was going wrong, and it's not particularly due to t500RS code, but due to the fact I am sending interrupts and not HID commands, more particularly it's how the base driver (tmff2) is handling multiple stop/start events while focusing out and in the game. I tested multiple times and this completely fixed the issue. The rationale is: Because this is impacting the base driver, I decided not to commit directly into my branch, but created dedicated branch and a PR from my branch towards my branch, with the fix included: cazzoo@83bbd95 Would you mind checking the code and telling me if you see anything anti-pattern or against the base code, that may break the other wheels. |
That doesn't make sense to me. Setting and clearing the flags is done within a spinlock, where exactly would the race condition be? If you mean that the worker sees a STOP, and while handling the STOP, a START arrives, I could see that the START is not picked up. However, since the worker is running, How is the interrupt/hid messaging stuff related? Is the interupt just slower, meaning a greater chance of mismatched start/stop? |
I don't really know why this happens, but as soon as we have multiple effects handled by the driver, it does fail to get all started effects to be stopped, and new effects started. We can see the sequence is slightly different between focuses-in that works and the ones that doesn't work (the pattern is always the same when failing or succeeding). From my attempts, getting all the effects handled with the 3 phases sequence ensures the STOPS are all processed and then the STARTS can start. I will definitely check the work handler, see if it stops or not when focus out.
I don't really know, but the game send the exact same command in the same order to T500 and T300, and T300 handles it properly. T300 uses HID layer AFAIK whereas T500 uses USB interrupts, so my assumption is that HID layer is handling this. |
|
So, I figured there's no issue with the work handler, it's still processing after game started, no matter an ALT+TAB has been pressed or whatsoever (getting back to game's pits menu). After some more analysis, the T500RS hardware requires all effects to be uploaded before any are started. This is a device protocol requirement, not a software bug: When doing Sequential, it failed: Upload effect0 → Start effect0 → Upload effect1 → Start effect1 → ... When we interleave uploads and starts, the device gets confused and FFB doesn't work. The three-pass approach ensures the device is in a consistent state before starting effects. This corroborate with my previous finding: T500RS only upload and play once, then it's continuously sending updates. I am happy to revisit the way the t500rs driver works, but I think we're getting to an edge-case that need to be dealt in a particular way, and I'm getting to the end of my knowledge to troubleshoot this case. @Kimplul, If you have any other suggestion, my ears are open and I'm fine to work back on the logic, just let me know. From all my attempts, without that three-pass it was randomly failing (unless I forced only 1 effect slot for the driver), and at the moment I am putting in place that workaround, it's working all the time long (100% success rate). |
|
EDIT: I tried the "git checkout" command mentioned above and I receive an error that the branch named already exists. Should I just delete everything and repeat the process? Hey @cazzoo, thanks for the update. Is there a way for me to test your "workaround" & maybe provide (hopefully helpful) feedback? All the best, Uwe |
|
@hoover67 you have to do git fetch cazzoo origin (commands are not correct), then checkout branch "hardening/fix-multi-stop-start-race-condition" from cazzoo origin as well, and then build&install driver. |
|
EDIT: Never mind, managed to (hopefully) checkout the correct version, will try it later on. Sorry for the noise! :-) Uwe |
|
@cazzoo Looking great! Just tried rfactor2 with the latest version of your driver including the race condition workaround and I was able to enter the track several times without the wheel losing FFB effects... thanks so much guys for all your hard work. |
I guess that makes more sense to me, would at least explain why I haven't seen anything similar with my T300. But what happens if a game deliberately uploads an effect, starts it, then uploads another effect a bit later? That should be allowed by the FFB API, but how should the driver handle it? If the driver sends out a STOP to all effects and then later starts all effects again, that would throw off all effect timings and periodic effects would have a jarring jump. I can't remember which game it was but I think I've seen something like that, each time the car crashed into something a custom one-off effect would be uploaded and played a single time, with the strength of the crash etc. calculated separately for each upload. |
|
Hey @cazzoo just a quick word that I did my first online league race in rf2 yesterday evening on Linux Mint 22. I'm happy to report that FFB on the T500RS worked without any problems and felt "just like on win10" throughout the event. I still have to run the tmdrv script above though before the wheel starts working (after plugging it in of course), but that may well be a misconfiguration on my system rather than an issue with the driver itself. Any ideas what I could look at / into in order to solve this? Thanks again for all your work @Kimplul and @cazzoo, it's much appreciated! Uwe |
|
Hi guys, just a quick note to let you know that FFB also works with the RallySimFans Richard Burns Rally mod. Also, after updating to Linux Mint 22.3 (was running .0 before), I no longer have to run the tmdrv script above after plugging in the wheel in order for oversteer to detect it correctly (I still have to launch oversteer as root though). All the best, Uwe |
|
Hi folks, I just tried the june-hardening branch and lost all ffb effects on my wheel (in rfactor2, haven't tried other sims yet) except for a rather strong centering force. I installed the new version as per instructions in the readme after a full "clone" of the repo. i then reverted back to the old driver (thankfully I had a backup) and everything worked as before. All the best, Uwe |
|
Hi all, I just tested the driver with /hardening/fix-multi-stop-start-race-condition branch (as @hoover67 said the /hardening/June-pass to not work for him) and force feedback seems to be working, however the force feedback itself feels a bit off, I don't know how to descibe it other than it has very exaggerated force near center yet almost nothing but centering spring when turning. Tried changing settings via oversteer but also doesn't seem to make much of a difference. The game I use to test is Assetto Corsa and Assetto Corsa Competizione. I'm also unable to validate pedal functionality as I am using Thrustmaster T-LCM pedal directly via USB. I will try the /hardening/June-pass branch next just to feel the difference. |
|
Hello both, Thank you for your interest, I am currently working to update the driver and make it work more alike the T300RS and other variants, having effects that lasts length with driver terminating effect. |
|
Hey cazzoo, thanks for the info. I still have a win10 partition on my gaming rig somewhere, I have no idea if it still boots up though. What exactly would you be interested in when you're asking us to capture a session? Cheers, Uwe |
|
@hoover67 , I am looking at any game session captures, so I can verify protocol is working as implemented. Regarding new changes, if you are interested into the latest changes, you can checkout branch session/agent_a361d90b-d010-4399-8b16-7988ad63f2f8 pulling at it, building and replacing the system driver. Happy to get your feedback on it |
|
hi @cazzoo, would a Wireshark capture in bare metal Windows via USBPcap be sufficient? I'm planning to capture a practice session in Assetto Corsa, preparing for a club league endurance event. |
|
Hi @cazzoo, following is capture from my T500 RS playing Assetto Corsa in Windows: Google Drive link |
|
I checked this morning and my win10 install is no longer operational, so I won't be able to provide a USB capture of an rf2 session on short notice... sorry. All the best, Uwe |
|
Hi all, I managed to revive my win10 partition from the dead and did a little rfactor2 session at Kyalami 1976 in the Brabham BT24 by ChiefWiggum on our practise server (all content is freely available from the steam workshop). The pcap file can be found here: I did not do any specific filtering as the t500 wheel is the only device connected to this physical port. (front-facing USB3 port). I had to disable steam input for rfactor2 as the title would not detect any input from my wheel. I left all values (ffb, multipliers etc) at default. The session includes plugging in the wheel right at the beginning of the capture (initial rotation of the hardware wheel over the entire range by the firmware). Please let me know if any other info is required from my end @cazzoo All the best & thanks again for your work on this project! Uwe |




This driver has been built from scratch based the previous dirty driver (see #175) and on captures from ffbsdl tool ran in windows for all possible effects (through SDL2 library).