Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFB not work as expect when direction changed #43

Closed
y761823 opened this issue Jul 12, 2022 · 25 comments · Fixed by #44
Closed

FFB not work as expect when direction changed #43

y761823 opened this issue Jul 12, 2022 · 25 comments · Fixed by #44

Comments

@y761823
Copy link

y761823 commented Jul 12, 2022

When I nagitive the direction of ffb effect, there is a high probability that force feedback not work. Unless I remove the effect and upload a new effect. Is it my misuse?

system: Ubuntu 16.04/20.04
my test code with compile command gcc -std=c++14 t300rs.cc :

#include <algorithm>
#include <cmath>
#include <cstdio>

#include <fcntl.h>
#include <libevdev-1.0/libevdev/libevdev.h>
#include <unistd.h>

int main() {
  int event_fd_ =
      open("/dev/input/by-id/"
           "usb-Thrustmaster_Thrustmaster_T300RS_Racing_wheel-event-joystick",
           O_RDWR | O_NONBLOCK);

  ff_effect effect_;
  effect_.id = -1;
  effect_.type = FF_CONSTANT;
  effect_.u.constant.level = 19722;
  effect_.direction = 35389;
  effect_.u.constant.envelope.attack_length = 0;
  effect_.u.constant.envelope.attack_level = 0;
  effect_.u.constant.envelope.fade_length = 0;
  effect_.u.constant.envelope.fade_level = 0;
  effect_.trigger.button = 0;
  effect_.trigger.interval = 0;
  effect_.replay.length = 0xffff;
  effect_.replay.delay = 0;
  bool ffb_need_restart_ = false;
  bool has_play_effect_ = false;
  int cnt = 0;

  while (true) {
    // There is a high probability that force feedback will fail when change
    // direction without rewrite
    if (++cnt % 1000 == 0) {
      effect_.direction = -effect_.direction;
    //   ffb_need_restart_ = true;  // not restart
    }

    printf("ffb_level %d\n", effect_.u.constant.level);
    printf("direction %d\n", effect_.direction);

    if (ffb_need_restart_ || ioctl(event_fd_, EVIOCSFF, &effect_) < 0) {
      printf("restart force feed back\n");
      if (effect_.id >= 0 && ioctl(event_fd_, EVIOCRMFF, effect_.id) < 0) {
        printf("ioctl erase feedback failed\n");
      }
      effect_.id = -1;
      has_play_effect_ = false;
      ffb_need_restart_ = false;
      continue;
    }

    /* If first time, start to play the effect_ */
    if (!has_play_effect_) {
      struct input_event play;
      play.type = EV_FF;
      play.code = effect_.id;
      play.value = 1;

      if (write(event_fd_, (const void *)&play, sizeof(play)) == -1) {
        printf("Play effect failed\n");
      }
      has_play_effect_ = true;
    }
    usleep(8000);
  }
}
@Kimplul
Copy link
Owner

Kimplul commented Jul 12, 2022

Hi, could you clarify what you mean by 'fails'? Is there an error in dmesg, or does ioctl return an error code or do you just mean that the effect stops playing?

Other than that, I think one issue might be with

effect_.replay.length = 0xffff;

The kernel docs in https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input.h#L302 say

/*

  • All duration values are expressed in ms. Values above 32767 ms (0x7fff)
  • should not be used and have unspecified results.
    */

Assuming that 0xffff works as expected, that's still at most ~60 seconds, could you be running out of effect duration? Try using 0 to make the effect infinite.

Also a sidenote, in https://www.kernel.org/doc/html/latest/input/ff.html#dynamic-update-of-an-effect changing the direction of an effect dynamically is not recommended. I'm pretty sure my driver should handle the situation correctly, but might not work as expected on other wheels and from what I can remember doesn't work on Windows.

@y761823
Copy link
Author

y761823 commented Jul 12, 2022

when I set effect_.replay.length = 0xffff, ffb works as expected but will lose after almost 60s. I'll change it to 0, thanks for the suggestion.
"fails" mean that, for example, my ffb make the steering wheel turns clockwise at first. But when I change direction, the steering wheel still turns clockwise instead of counterclockwise. If it's by design, I think I should upload a new effect for it.

@Kimplul
Copy link
Owner

Kimplul commented Jul 12, 2022

Right, yeah, my driver doesn't update the effect if only the direction changes, similar to how the Windows driver works. However, if you change the level of the FFB as well, the direction will be updated at the same time. Although don't rely on this, as stated earlier changing the direction of an effect dynamically isn't a well defined operation.

@berarma
Copy link

berarma commented Jul 12, 2022

Right, yeah, my driver doesn't update the effect if only the direction changes, similar to how the Windows driver works. However, if you change the level of the FFB as well, the direction will be updated at the same time. Although don't rely on this, as stated earlier changing the direction of an effect dynamically isn't a well defined operation.

That's not how it works on Linux. I don't see the point in making a Linux driver behave like the Windows one. It should work as defined by the Linux API or we'll see inconsistent behavior between different hardware.

If you're worried about the driver working well on Wine/Proton is equally important to follow the Linux API because that's what Wine/Proton expects and will translate Windows calls accordingly.

The Linux API doc doesn't say you shouldn't change direction dynamically. It says you should expect that some drivers stop and restart the effect when a dynamic change of direction is requested. So it's completely fine to change direction dynamically. The hardware has to support this or restart the effect with the new direction.

@Kimplul
Copy link
Owner

Kimplul commented Jul 12, 2022

That's not how it works on Linux. I don't see the point in making a Linux driver behave like the Windows one. It should work as defined by the Linux API or we'll see inconsistent behavior between different hardware.

Out of curiosity, is there more documentation about Linux FFB conventions than https://www.kernel.org/doc/html/latest/input/ff.html? I remember you having to correct me on effect.duration as well.

The Linux API doc doesn't say you shouldn't change direction dynamically. It says you should expect that some drivers stop and restart the effect when a dynamic change of direction is requested. So it's completely fine to change direction dynamically. The hardware has to support this or restart the effect with the new direction

Lazy reading by me, sorry. The context of the restart is not well defined, not the act of changing direction then? Fair enough, should be a reasonably quick fix.

@berarma
Copy link

berarma commented Jul 12, 2022

Besides the API docs there's the headers file that defines the structs that has some comments. Nothing else that I know.

The driver has to be able to change direction, either dynamically as requested in case the device supports it or by restarting the effect as a fallback.

The docs say that some drivers might do a effect restart when requesting a dynamic direction change and the developer should be OK with that. The developer could also check that the devices they want to support do the right thing for their case.

@Kimplul
Copy link
Owner

Kimplul commented Jul 12, 2022

I pushed a test commit to https://github.com/Kimplul/hid-tmff2/tree/direction

Don't have access to my wheel at the moment, please feel free to test the branch out.

@Raboebie could you also check if it works?

@y761823
Copy link
Author

y761823 commented Jul 14, 2022

I try to install https://github.com/Kimplul/hid-tmff2/tree/direction but it still not working as expected.
Even if I modify both level and direction, my steering wheel still always turns clockwise.

    if (++cnt % 1000 == 0) {
      effect_.u.constant.level++;
      effect_.direction = -effect_.direction;
      // ffb_need_restart_ = true;  // not restart
    }

Is it that the steering wheel firmware does not support this modification?

@Kimplul
Copy link
Owner

Kimplul commented Jul 14, 2022

it that the steering wheel firmware does not support this modification?

No, the firmware doesn't actually have any concept of 'direction', my driver is just responsible for translating it down into something the wheel should understand. Apparently my driver is not working the way I expected it to, weird.

Did you check that the newer driver is being loaded? Linux pretty often caches modules and you might still be running the old module. You could try adding something like printk("test"); in tmff2_wheel_init to make it more obvious. Although the old driver should still react to level changes, you could try also flipping the level as a sanity check.

And not to be condescending, but also check that you have the correct branch checked out in git.

@y761823
Copy link
Author

y761823 commented Jul 15, 2022

I'm sure I've check out the branch to direction. But I'm not sure if the new driver is used in the kernel (I don't know where I can see the output of printk? I didn't see it).
Other than that, I try to increase the level by 3000 every time, and there is a high probability that it work, but occasionally it does not work, I don't know why.

@Kimplul
Copy link
Owner

Kimplul commented Jul 15, 2022

I don't know where I can see the output of printk?

dmesg -w

Other than that, I try to increase the level by 3000 every time, and there is a high probability that it work, but occasionally it does not work, I don't know why.

That's pretty weird, might be a timing issue but I'll have to look into it.

@berarma
Copy link

berarma commented Jul 15, 2022

I'm sure I've check out the branch to direction. But I'm not sure if the new driver is used in the kernel (I don't know where I can see the output of printk? I didn't see it). Other than that, I try to increase the level by 3000 every time, and there is a high probability that it work, but occasionally it does not work, I don't know why.

I hadn't looked at your code before but now that I did I've seen some things that I think should be commented.

The direction parameter is a uint16 and for wheels should be 0x4000 (left) or 0xC000 (right). Other directions towards the vertical axis should fade out the effect since there's no vertical FFB.

Changing the sign of direction has the effect of mirroring the effect to the other side. Even if it's unsigned it would work.

You're sending effect updates every 8ms even if the effect changes only every 8s·

I've tried the code on my G29 and it works only if I change the direction to 0x4000 or 0xC000.

@gou4shi1
Copy link

The direction parameter is a uint16 and for wheels should be 0x4000 (left) or 0xC000 (right). Other directions towards the vertical axis should fade out the effect since there's no vertical FFB.

Could you share more about direction? I only see that

	level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff;

in this driver.

@berarma
Copy link

berarma commented Jul 19, 2022

The direction parameter is a uint16 and for wheels should be 0x4000 (left) or 0xC000 (right). Other directions towards the vertical axis should fade out the effect since there's no vertical FFB.

Could you share more about direction? I only see that

	level = (constant.level * fixp_sin16(effect.direction * 360 / 0x10000)) / 0x7fff;

in this driver.

The direction parameter is an angle expressed as an unsigned 16 bit integer with direction 0 pointing up and direction 0x8000 (180°) pointing down, running counter-clockwise. This is the direction of the force that will be applied and it's exactly how it works on joysticks that have FFB on both axes, X and Y.

Since wheels have only one axis of movement, only the horizontal component of the force is used. With wheels, it doesn't make much sense using values other than 0x4000 or 0xC000 for the direction. They will work but only have the effect of reducing the total force applied.

That line of code takes the horizontal component of the force requested in the parameters and applies it to the wheel. If the direction is up the horizontal component will be 0, if the direction is top/left (45°) the horizontal component will be half the total force, and if the direction is left (90°) the horizontal component will be 100% of the force.

@gou4shi1
Copy link

That line of code takes the horizontal component of the force requested in the parameters and applies it to the wheel. If the direction is up the horizontal component will be 0, if the direction is top/left (45°) the horizontal component will be half the total force, and if the direction is left (90°) the horizontal component will be 100% of the force.

Thanks for your reply.

@gou4shi1
Copy link

gou4shi1 commented Jul 25, 2022

I found something suspicious:
The input old of tmff2_upload() is maintained in https://github.com/torvalds/linux/blob/master/drivers/input/ff-core.c#L145, while tmff2_upload() is async, the actual processing logic happens in t300rs_update_constant().
So if the upload frequency is so high, e.g. 8ms in this issue, state->old may be updated to the new value before t300rs_update_constant() processes the new value, thus if (constant.level != constant_old.level) failed.

@Kimplul
Copy link
Owner

Kimplul commented Jul 25, 2022

Good point, I'll add a slight twist to that: The old parameter in tmff2_upload() is the previous effect uploaded by the user, whereas state->old is intended to be the previous effect uploaded to the device. In theory, as long as an effect has the flag FF_EFFECT_QUEUE_UPLOAD state->old should stay the same, as a newer effect hasn't been uploaded to the device yet, and as such all comparisons should be to what the device is currently (supposedly) doing.

Looking at the code now though, it looks like I'm missing a negation and instead the effect is being updated on every user upload. Oops.

At work right now so I can't test, but adding a ! to

if (test_bit(FF_EFFECT_QUEUE_UPDATE, &state->flags))
might fix things.

It might also be worth making this relationship more obvious, for example changing state->old in tmff2_work_handler() somewhere instead of relying on the FFB susbystem's old, but that's probably a secondary task.

@gou4shi1
Copy link

At work right now so I can't test, but adding a ! to

It works like a charm!

@berarma
Copy link

berarma commented Jul 25, 2022

Are you sure all the comparisons of the current values with the old values are necessary? I remember avoiding them in new-lg4ff, I don't know if that would be possible here but it would sure make things simpler.

For example, I see there's a comparison for the duration. Updating an effect with the same duration is still an update and it should make the effect last longer since the length counter is reset again to its initial value.

With the level, I've seen the parameters are compared but not the final actual values. So the level that needs to apply might be different even though the level parameter is the same on the last and the current updates.

Forgive if I make any mistake, I might have forgotten some details.

@Kimplul
Copy link
Owner

Kimplul commented Jul 25, 2022

Are you sure all the comparisons of the current values with the old values are necessary?

No, not really, as previously mentioned I've mostly just been copying the Windows driver, which for example seems to not update the duration when the effect is modified (at least when done through ffedit.exe.)

Updating an effect with the same duration is still an update and it should make the effect last longer since the length counter is reset again to its initial value.

Seems reasonable, and if you're doing it I probably should too.

Not to be rude, but where are you getting this information from? As I already mentioned, I know my driver is working like the Windows counterpart when it should be more like a Linux driver, but I honestly have no idea what behaviour is expected on Linux. I couldn't find a mention of this vs. the current behavour in the FFB docs nor the source code.

With the level, I've seen the parameters are compared but not the final actual values. So the level that needs to apply might be different even though the level parameter is the same on the last and the current updates.

The final actual values (for level) are only affected by the direction and the level parameter, as far as I'm aware, and the driver checks if either of these parameters have changed since the last iteration. Or am I misunderstanding something?

Anycase, if you have more insights, please feel free to update me on what else this driver might be doing differently from yours. Getting this driver to (finally?) behave consistently with other ones would be nice.

Thanks for your help.

@berarma
Copy link

berarma commented Jul 25, 2022

The Linux docs don't explicitly talk about the duration parameter but defines dynamically updating an effect as loading a new one without needing to stop the current one. In addition, I based new-lg4ff on the existing hid-lg4ff driver and used it as reference. I think I managed to have the same behavior.

I'm not 100% sure about everything so discussion is welcome. I should write some tests for ffbtools so we can see how the different drivers behave in these special situations.

@gou4shi1
Copy link

@Kimplul
Copy link
Owner

Kimplul commented Jul 26, 2022

@gou4shi1 Yes, thanks for reminding me, already forgot.

@berarma
Copy link

berarma commented Jul 26, 2022

I remember reading the ff-memless driver code since hid-lg4ff was based on it. It's a simple implementation of the Linux FFB API without hardware details. This base driver is intended for devices that only support one effect of each type played simultaneously. It keeps track of all the effects and mixes them so that the drivers based on it don't have to duplicate the code. I think it's a good reference for details that the Linux docs don't explicitly mention.

In the function at https://github.com/torvalds/linux/blob/e0dccc3b76fb35bb257b4118367a883073d7390e/drivers/input/ff-memless.c#L465 we can see that a dynamical update does use the new duration and even the delay. So if an effect is updated with a delay of 1s and a duration of 2s the effect would pause for 1s then play for 2s independently of the older values.

@Kimplul
Copy link
Owner

Kimplul commented Jul 29, 2022

This is not strictly related to this ticket, but I'm my defence I apparently hadn't forgotten to apply the direction branch to master.When #44 was merged it seems to have overwritten my direction changes for some reason.

Compare

if ((constant.level != constant_old.level) || (effect.direction != old.direction)) {

and

if (constant.level != constant_old.level) {

Not entirely sure what went wrong, but no harm done.

Kimplul added a commit that referenced this issue Sep 2, 2022
+ According to discussion in #43 this is more along the lines of what
  other wheels do.
@MmAaXx500 MmAaXx500 mentioned this issue Jun 30, 2024
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants