KeyboardioHID icon indicating copy to clipboard operation
KeyboardioHID copied to clipboard

Need a way to default to boot protocol

Open algernon opened this issue 6 years ago • 38 comments

Currently, KeyboardioHID follows the HID spec, and defaults to report protocol, with an option to go back to boot. We should have a way to default to boot, without having to detach/attach first.

algernon avatar Nov 25 '19 12:11 algernon

One simplistic way to do this would be to use KEYBOARDIOHID_DEFAULT_KEYBOARD_REPORT_PROTOCOL for the default, and it would be set to HID_REPORT_PROTOCOL if unset. This would allow vendors to set a default in boards.txt, for example.

algernon avatar Nov 25 '19 13:11 algernon

Another option, which I personally like better, would be to not instantiate BootKeyboard from within KeyboardioHID, but leave it up to the device plugins. This would allow us to have a constructor that takes a default_protocol argument, and each device could use whichever they want.

Even better, we could turn KeyboardioHID into kaleidoscope::driver::hid::keyboardio::, in which case we'd be able to make the whole HID facade nicer, move it all to the Kaleidoscope repo, and allow an even easier choice for devices: in Props! The default ::hid::keyboardio::BaseProps would default to report, but boards could easily override that.

@obra, what's your take on an attempt to try and turn this into a HID driver?

algernon avatar Nov 25 '19 16:11 algernon

For summary, as far as I see, we have the following options to have a device that defaults to boot protocol rather than report:

  1. Switch protocols in setup(). This requires no changes to KeyboardioHID, and can be achieved within the firmware sketch alone. The downside is that this requires a detach-attach dance on every connect. It's also an ugly workaround.
  2. Use an optional KEYBOARDIOHID_DEFAULT_KEYBOARD_REPORT_PROTOCOL macro that boards can define in boards.txt, and would default to report if unspecified. This is simple to implement, doesn't require existing device plugins to change, but it's kind of a hack. It also makes it harder for the end-user change the default if need be.
  3. We could stop instantiating BootKeyboard from KeyboardioHID, and leave it up to the device plugins. This would delegate it to the right place (device props), would allow changing the default in a reasonably easy way. But still feels like a hack, especially because we instantiate the rest of the stuff.
  4. We could merge KeyboardioHID into kaleidoscope::driver::hid::keyboardio::*. Like the previous option, this would allow one to select the default via Props, but everything would be instantiated there, not just BootKeyboard.

For the very short term, I can go with option 1 for the Raise firmware (because I'd like to not fork KeyboardioHID for option 2 or 3). In the long run, I think option 4 is the best.

algernon avatar Nov 25 '19 16:11 algernon

I am fine with doing #4 ᐧ

On Mon, Nov 25, 2019 at 8:55 AM Gergely Nagy [email protected] wrote:

For summary, as far as I see, we have the following options to have a device that defaults to boot protocol rather than report:

  1. Switch protocols in setup(). This requires no changes to KeyboardioHID, and can be achieved within the firmware sketch alone. The downside is that this requires a detach-attach dance on every connect. It's also an ugly workaround.
  2. Use an optional KEYBOARDIOHID_DEFAULT_KEYBOARD_REPORT_PROTOCOL macro that boards can define in boards.txt, and would default to report if unspecified. This is simple to implement, doesn't require existing device plugins to change, but it's kind of a hack. It also makes it harder for the end-user change the default if need be.
  3. We could stop instantiating BootKeyboard from KeyboardioHID, and leave it up to the device plugins. This would delegate it to the right place (device props), would allow changing the default in a reasonably easy way. But still feels like a hack, especially because we instantiate the rest of the stuff.
  4. We could merge KeyboardioHID into kaleidoscope::driver::hid::keyboardio::*. Like the previous option, this would allow one to select the default via Props, but everything would be instantiated there, not just BootKeyboard.

For the very short term, I can go with option 1 for the Raise firmware (because I'd like to not fork KeyboardioHID for option 2 or 3). In the long run, I think option 4 is the best.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/keyboardio/KeyboardioHID/issues/59?email_source=notifications&email_token=AAALC2HFGNVVDPJUUG5EKETQVP7PZA5CNFSM4JRID7L2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFDCF6Q#issuecomment-558244602, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAALC2HKI2R4X3M26MLIQMTQVP7PZANCNFSM4JRID7LQ .

obra avatar Nov 26 '19 19:11 obra

I made some progress on this: Kaleidoscope@driver/hid & KeyboardioHID@hid-driver. It works, and a lot of things are simpler to do now.

The downside is that Mouse and AbsoluteMouse never get optimized out by the linker. They don't get dropped, because the linker sees more now, and sees possible references to them, while it did not before, when they were in the adaptor library. If I use the no-op mouse/absolutemouse drivers, then we're a few bytes PROGMEM and RAM better than the status quo.

The solution here is to have the ::driver::hid::Base base class in Kaleidoscope, but ::driver::hid::Keyboardio in KeyboardioHID, on top of the stuff we already have there. This should allow the linker to do the right thing. At least, I think that is the solution. Will need to actually test it.

In any case, with the new system, a board can easily set their default protocol in their setup(), so all is well! I can probably make it so that we set the protocol in Props. I'm kinda liking what we have so far, though it does need considerable amounts of cleanup.

If the idea works, we can get rid of Kaleidoscope-HIDAdaptor-KeyboardioHID, and will only need a HID library like KeyboardioHID along with Kaleidoscope for a minimal bundle.

algernon avatar Nov 27 '19 18:11 algernon

Sadly, just moving ::driver::hid::Keyboardio to KeyboardioHID did not help.

algernon avatar Nov 28 '19 08:11 algernon

They don't get dropped, because the linker sees more now, and sees possible references to them, while it did not before, when they were in the adaptor library.

That must be due to the fact that the symbols were moved to another compilation unit. Mind you, what linkers do is highly dependent on the order of object files being passed at the linker command line.

Maybe you could get back to the old behavior by somehow forcing the linker command line. Not sure, though, what rules the order depend upon, maybe alphabetical, maybe something else. But it might be worth experimenting with by intercepting the linker command line with VERBOSE=1 and then experimenting a bit.

noseglasses avatar Nov 28 '19 09:11 noseglasses

That must be due to the fact that the symbols were moved to another compilation unit.

...and this is probably it. With the new setup, Mouse, Keyboard, BootKeyboard & friends do not get instantiated by KeyboardioHID, but by Kaleidoscope. Where the classes live is then irrelevant, unless I can move instantiation back to KeyboardioHID.

Thanks, I have a new route to explore now!

algernon avatar Nov 28 '19 09:11 algernon

Having an xWrapper class that just wraps the global object provided by KeyboardioHID helps, and allows the linker to optimize out the Mouse/AbsoluteMouse objects as needed. With that, we're at +56 PROGMEM and +87 RAM, which is much better than before, but still more than I'm happy with.

algernon avatar Nov 28 '19 10:11 algernon

And with a few more tricks: -42 PROGMEM, -15 RAM, with functional equivalence, and 0 changes to KeyboardioHID, and with Kaleidoscope-HIDAdaptor-KeyboardioHID dropped.

algernon avatar Nov 28 '19 10:11 algernon

Hmmhmm. The above numbers were for the Basic sketch. For Devices/Keyboardio/Model01, we're at +386/+11, which is a tad too much. Looking into it.

algernon avatar Nov 28 '19 13:11 algernon

Most examples saw -500/-29 PROGMEM/DATA, with only a few seeing an increase in size:

  • Model01: +386/+11
  • Planck: +256/-34
  • Atreus: +254/-34
  • Atreus2: +708/+6

The Atreus2 is most interesting.

algernon avatar Nov 28 '19 14:11 algernon

make size-map will likely be instructive

On Nov 28, 2019, at 9:59 AM, Gergely Nagy [email protected] wrote:

 Most examples saw -500/-29 PROGMEM/DATA, with only a few seeing an increase in size:

Model01: +386/+11 Planck: +256/-34 Atreus: +254/-34 Atreus2: +708/+6 The Atreus2 is most interesting.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

obra avatar Nov 28 '19 15:11 obra

I'm using elf_diff, even more instructive. I now see which functions increased in size considerably, but couldn't figure out why yet.

FWIW, the Atreus2 case looks something like this:

  1. HID_::setup(): +136
  2. HID_::getDescriptor(): +134
  3. HID_::getInterface(): +130
  4. MouseKeys::onKeyswitchEvent(): +104
  5. HID_::sendReport(): +36
  6. MouseWrapper::begin(): +24
  7. HID_::HID_(): +16

The rest are smaller, and make reasonable sense. These... these feel like we might be initializing something more than once. Or perhaps AbsoluteMouse not getting optimized out... At least in some of the cases. The Planck example, for example, doesn't use MouseKeys.

algernon avatar Nov 28 '19 16:11 algernon

Looking at the disassembly, we have two copies of many keyboard stuff. Now to find out where we do that!

algernon avatar Nov 28 '19 16:11 algernon

struct KeyboardioProps: public BaseProps {
  typedef keyboardio::KeyboardProps KeyboardProps;
  typedef keyboardio::Keyboard<KeyboardProps> Keyboard;
  typedef keyboardio::MouseProps MouseProps;
  typedef keyboardio::Mouse<MouseProps> Mouse;
  typedef keyboardio::AbsoluteMouseProps AbsoluteMouseProps;
  typedef keyboardio::AbsoluteMouse<AbsoluteMouseProps> AbsoluteMouse;
};

If I drop the Keyboard typedef, then we do not have duplication. But then we don't have a single instance of Keyboard_ either. So it is something around that place that's fishy.

algernon avatar Nov 29 '19 08:11 algernon

I give up. I have no clue why we have two constructor calls, or why the other functions grow so much.

I'll keep the branches around, and may revisit it some other time, but for now, I surrender. Thankfully, we can set the default protocol on a per-board basis, we just have to set BootKeyboard.default_protocol and call BootKeyboard.setProtocol() before Kaleidoscope.setup().

algernon avatar Nov 29 '19 11:11 algernon

Could you possibly make the elf_diff stuff available? I could take a glimpse and maybe I have an idea.

noseglasses avatar Nov 29 '19 14:11 noseglasses

@algernon, I think the reasons for your troubles is that the recently I introduced stuff in Kaleidoscope/src/kaleidoscope/device/virtual/ in the course of working on the virtual builds. @obra cherry picked that stuff recently to master.

Unfortunately not all files in Kaleidoscope/src/kaleidoscope/device/virtual/ are properly protected by #ifdef KALEIDOSCOPE_VIRTUAL_BUILD, including Kaleidoscope/src/kaleidoscope/device/virtual/HID.cpp. That file contains a no-op HID implementation that is accidentlally also compiled in the non-virtual builds. I guess that due to link order dependencies its symbols are sometimes caught up and used instead of the actual HID implementation. I totally forgot that Arduino is greedy and builds whatever it can find. Also, when testing this before submitting the virtual build PR, that problem did not show up :(

That's why elf_diff shows the strange increase in symbol sizes. It compares the virtual HID symbols with the non-virtual ones.

I think that issue is somewhat urgent as it potentially can make the entire firmware dysfunctional. Apologies for not being able to fix this within the next two days. Hope you can.

The fix is to add the proper #ifdef KALEIDOSCOPE_VIRTUAL_BUILD clauses to everything that lives in Kaleidoscope/src/kaleidoscope/device/virtual.

noseglasses avatar Nov 30 '19 09:11 noseglasses

I already added an ifdef guard to that HID.cpp, and still saw double Keyboard_ constructors. Though, I have not added guards to the rest of stuff in device/virtual. I'll see if guarding the rest has any effect.

algernon avatar Nov 30 '19 10:11 algernon

Just checked, only Logging.cpp was missing the ifdef guard, and adding it there made no difference :|

algernon avatar Nov 30 '19 10:11 algernon

But the elf_diff document you made available definitely showed a comparison with a build that linked the virtual hid implementation.

noseglasses avatar Nov 30 '19 13:11 noseglasses

Damn, I just concentratet on the HID stuff in the elf_diff doc. Cause that's where the big size increase came from. I missed the Hardware thing. Can't chech now on the phone. What do you mean by duplicate. Identical signatures are not accepted by the linker so I suppose there must be a difference.

noseglasses avatar Nov 30 '19 13:11 noseglasses

@algernon, I'm still curious, could you post an elf_diff output of a comparison after you added the ifdef guards in virtual/hid.cpp?

noseglasses avatar Nov 30 '19 20:11 noseglasses

Having rebased my branch on top of master, the Model01 example is substantially better: +64 PROGMEM & +11 RAM compared to master, which is much better than +386/+11, but I'd still like to understand why. There's no virtual hid involved (size-map | grep -i virtual only shows non-virtual thunk to SingleAbsoluteMouse_::sendReport(void*, int)).

Main takeaways from Model01-master-vs-hid-driver.html.txt:

  • MouseKeys_::onKeyswitchEvent grew 122 bytes for no good reason. Might need some noinline love there?
  • MouseKeys_::onSetup() and MouseKeys_::warp_jump are also suspiciously bigger for no good reason.
  • sendKeyboardReport() grew from 126 bytes to 178, that's 52 bytes in and of itself!
  • Looking at the disappeared & new symbol list, there's a suspicious lack of kaleidoscope::driver::hid::base::Mouse and ::AbsoluteMouse.

So it is increasingly looking like things are getting inlined, which is probably good for performance, but bad for size.

algernon avatar Dec 11 '19 11:12 algernon

Hrm, no. Adding __attribute__((noinline)) anywhere near the mouse stuff increases code size.

algernon avatar Dec 11 '19 11:12 algernon

A'ight, down to +32/+11, which is getting closer to being acceptable.

algernon avatar Dec 11 '19 11:12 algernon

Atreus2 is down to +20/+6.

algernon avatar Dec 11 '19 11:12 algernon

I think we can stomach a +32/+11, but will check the rest of the examples to see if there any other outliers.

algernon avatar Dec 11 '19 11:12 algernon