Skip to content
This repository has been archived by the owner on Dec 14, 2023. It is now read-only.

Need a way to default to boot protocol #59

Open
algernon opened this issue Nov 25, 2019 · 38 comments
Open

Need a way to default to boot protocol #59

algernon opened this issue Nov 25, 2019 · 38 comments

Comments

@algernon
Copy link

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
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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.

@obra
Copy link
Member

obra commented Nov 26, 2019 via email

@algernon
Copy link
Author

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
Copy link
Author

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

@noseglasses
Copy link
Collaborator

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.

@algernon
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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
Copy link
Author

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.

@obra
Copy link
Member

obra commented Nov 28, 2019 via email

@algernon
Copy link
Author

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
Copy link
Author

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

@algernon
Copy link
Author

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
Copy link
Author

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().

@noseglasses
Copy link
Collaborator

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

@algernon
Copy link
Author

@noseglasses
Copy link
Collaborator

@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.

@algernon
Copy link
Author

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
Copy link
Author

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

@noseglasses
Copy link
Collaborator

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

@noseglasses
Copy link
Collaborator

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
Copy link
Collaborator

@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?

@algernon
Copy link
Author

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
Copy link
Author

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

@algernon
Copy link
Author

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

@algernon
Copy link
Author

Atreus2 is down to +20/+6.

@algernon
Copy link
Author

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

@algernon
Copy link
Author

hmm... might need to noinline some ConsumerControl things, perhaps... (trying to figure out the asm diffs). But lets compare all examples first.

@algernon
Copy link
Author

OH!

I found something interesting: switching from arduino 1.8.9 (gcc 5.4) to arduino 1.8.10 (gcc 7), we win ~270/4 bytes on both master and hid/driver. So if we switch to arduino 1.8.10, then the size increase doesn't matter.

@algernon
Copy link
Author

With arduino 1.8.9, the Model01 diff is +150 PROGMEM, Atreus2 is +250, most others are either negligible, or the diff is in the negatives.

@algernon
Copy link
Author

Checked with arduino 1.8.10, the biggest increase is the Model01's +32/11, followed by +20/+6 Atreus2. The rest is noticably smaller. (And even with that +32, the firmware is still smaller than with arduino 1.8.9 + master!)

If we can up our recommended arduino version to 1.8.10, then I think this is a reasonable trade-off.

@obra
Copy link
Member

obra commented Dec 11, 2019 via email

@algernon
Copy link
Author

Well, if we don't upgrade the compiler, then turning the HID facade into a driver has serious PROGMEM ramifications, which will be noticeable to existing users, while defaulting to boot proto is something only new users, on a new keyboard will see (and even there, if it turns out to be a bad thing, can be reverted in the next firmware update). One thing I can try, is having the driver in Kaleidoscope-HIDAdaptor-KeyboardioHID, so that it is a separate compilation unit, and see if that improves the situation. I've tried that before, and it didn't help much, but we're at a better state right now than back then, so it makes sense to revisit.

Mind you, being able to change the default protocol is not the only reason for this PR. Turning the facade into a driver allows us to have all kinds of Props, which will be useful down the road, when we have different HID drivers where more props make sense. It also paves the way of being able to disable some of the HID (like BootKeyboard) completely to save space, without having to #ifdef around the code and pass in special compiler flags during build - instead, we will be able to do it from a user sketch. (That needs a bit more work than turning the facade into a driver, but that's the first step towards it)

@algernon
Copy link
Author

Moving the ::driver::hid::Keyboardio stuff to the HIDAdaptor doesn't make a difference.

@algernon
Copy link
Author

Well. This is weird. I just recompiled both master and the driver/hid branch with arduino 1.8.9, and don't see such a big bloat anymore. I get similar sizes as what is on Travis, which is in the few dozen bytes extra range, which I think is fine.

Gonna turn this into a few PRs so it'll be easier to verify my results.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants