-
-
Notifications
You must be signed in to change notification settings - Fork 7k
[PluggableHID] 2nd round of API improvement (WIP) #3896
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
Conversation
You were right. Your commits all look good to me. I will compare with my version and look if this can be used like this. But its looking very good to me. Sorry that i missed that first. Have you tested this with real hardware? afk for some time, excuse me. |
I haven't tested yet (all those changes should be "equivalent" but... only testing will tell).
np, I'm going away too, will continue tomorrow. |
|
great, thanks for testing!
I'm going to apply this change.
I'll leave this one as the last task
Yes
Yes
because I've just moved code around and they was defined like that. I'll check if everything can be changed to unsigned.
Well the reasoning is that IF and EP are assigned after the module has been plugged, they are not structure-like that are assigned directly from the user. Moreover if we make the other fields constant and initialized through constructor we are going to remove this difference. Let's see how it looks I'm going to add more commits in the next few hours. |
I forgot: You can get rid of the static functions and variables now. Since the Class is now a singleton there is no need for static functions and variables. I tried to fix that in a local branch, but before editing twice (you and me) I suggest you just add this. So I wait for further commits from you. Let me know on any update. |
Ok, just pushed another 4 commits, this should solve 90% of the problems so far. Still to be done:
|
static bool plug(PUSBListNode *node); | ||
static int getInterface(uint8_t* interfaceNum); | ||
static int getDescriptor(int8_t t); | ||
static bool setup(USBSetup& setup, uint8_t i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those static functions can be removed as well I think.
you could make those int8_t and use the interface as a check if the modul exists. Maybe this check can be done in the pluggable class so we have less code duplication/error with bad library coding. (set to -1 if all modules are used) And we also need to change the modules used limit to the endpoint number, not the modules itself. On a 16u2 there is only one more free endpoint. If your interface (midi usb I suspect) uses 2 endpoints it is not able to work correctly. Thatswhy the check should be done via endpoint count and set the interface to -1 if it doesnt work correct. If we implement the -1 inside the pluggable class we can remove the bool return value of plug() otherwise we could use this to set the interface to -1. The 16u2 needs to be patched with the modul/ep limit anyways, but later. |
Pushed other 2 commits.
this means that the check: bool PluggableUSB_::plug(PUSBListNode *node)
{
if (modulesCount >= MAX_MODULES) {
return false;
} should really be: bool PluggableUSB_::plug(PUSBListNode *node)
{
if ((lastEp + node->numEndpoints) >= MAX_MODULES) {
return false;
} and |
Yes. We can get rid of the modules count variable and overhead with this check. You need to define the value then with 6 (since 3 are used by the CDC which is initialized to For the u2 Series please add 4 enpoints. You also need to edit this file: Or (maybe even better) you can use To initialize Please use Edit: last 2 commits are looking goood. Getting more and more excited now :) Edit2: Does |
Wait wait wait, the From USBCore: u8 _initEndpoints[] =
{
0,
EP_TYPE_INTERRUPT_IN, // CDC_ENDPOINT_ACM
EP_TYPE_BULK_OUT, // CDC_ENDPOINT_OUT
EP_TYPE_BULK_IN, // CDC_ENDPOINT_IN
#ifdef PLUGGABLE_USB_ENABLED
//allocate 3 endpoints and remove const so they can be changed by the user
0,
0,
0,
#endif
}; in PluggableUSB lastEp is set to
so lastEp is |
@matthijskooijman HID will be initialzed multiple times. At least if I need to do a fork. This is needed to create multiple gamepads on multiple endpoints (and other cool stuff). I am just wondering about what variable you are talking. Whatever variable this is, if the first adding works (enough endpoints) and the 2nd doesnt, with a shared variable this can cause problems. Pluggable USB probably does not need to be a singleton/class at all. So we could maybe change this back. As you said the size should be compared. @cmaglie good catch with the 1 offset. sizeof is still the best solution i guess. Please conside to commend the first entry of |
I looked casually through all commits, looking good to me!
No need - global variables are zeroed by default (the attachInterrupt stuff need to initialize to
I was going to say that you still need the count to interate the modules, but you can of course just loop until
I haven't looked at the code for this, but perhaps it would be worth considering to convert the CDC class to a pluggable class as well? This might simplify some of the code? Even if it is always enabled, the code structure might become more regular? OTOH it could add size to the compiled code, of course. |
Pushed other 3 commits. Please have a closer review and also test if possible, because those commits have some code change too. I'm done for today :-), be back tomorrow. |
I see only 2 commits? Those look good to me. |
Github orders the history of the pull request based on the commit timestamp (not the push timestamp), so you can find the commit if you reload the page and scroll back in the discussion, or maybe it's more simple to change to the "Commits" tab, btw this is the "missing" one: |
Yeah but we only want to initialize some of them. I just want to be sure the endpoint is not (wrong) configured if it shouldnt be at all.
We need this check anyways so the modul count is really not needed (like in pluggable HID we also do not count the devices)
I was thinking of this as well. I also would like to see a whole deactivation of the USB-Core at all. This removes a lot of overhead for the 32u4 which can be used for non usb stuff very good. The u2 Series profits even more here. TODOs from above:
|
One more thing I realized wrt the static/non-static thing: AFAIU constructors for global objects are run in arbitrary order. This means that inside a constructor, other objects might not be fully initialized yet. This applies in particular to non-static members, which are always initialized in the constructor, whereas static members and global variables can be initialized by zeroing or copying the .data section (both of which happen before any constructors are called). In this case, this can cause problems if a module is initialized before the PluggableUSB class, or a HID "submodule" is initialized before the main HID object. Not sure what the exact problems or solutions are, just wanted to throw this out there. |
omg @matthijskooijman you're right, we hit exactly in the static initialization order fiasco, totally missed that :-( https://isocpp.org/wiki/faq/ctors#static-init-order The FAQ propose also doing the initialization using lazy construction through an helper function, for example: HID_& HID()
{
static HID_* obj = new HID_();
return *obj;
} and replace every occurrence of HID with the call to HID() so: HID.SendReport(.....); becomes HID().SendReport(....); Very ugly :-( don't know if there are other easier solutions. |
I got the idea mainly, but not the connection to this API. Is it more a problem to have static members or to not have static members. This is above my c++ knowledge at the moment. You mean the problem is, if the HID.cpp tried to call the PUSB.plug() function if it does not exist yet? Wouldnt this give a compile error or so? May you give me a real example related to our API? |
Exactly that, and the compiler doesn't give any warning. The problem is that static objects are constructed at the very beginning of the sketch (even before main()!) in an undefined order. So the solution is to use a lazy construction like the one I've posted above, or to not call other (static) object's method from the constructor. |
A singleton HID_ HID is also a static object? |
yes |
And what if we just add the hid this pointer to the root node linled list and call the init endpoint functions *that are now inside plug() with the pluggable constructor? Or there the same problem, whether all notes are added or not? I have no real idea how to fix this. Is there no compiler attribute we can add? |
The field is now built on-the-fly on the stack and sent over USB. This change increase Flash usage and decrease SRAM usage: before: 6114 / 241 after: 6152 / 216 (removed HIDDescriptor field) delta: +38 / -25 SRAM is a much more scarse resource and this change free up to about 10% of the "base" usage.
Thanks @matthijskooijman, interesting read, I'll give it a try. In the meantime I've pushed a solution with the lazy-construction trick, it doesn't look so bad in the end. |
@matthijskooijman, I've read about I've tried to revert back the various |
I'd vote against the static HID class, since I want to inherit from this and creat multiple HID devices. (with a single module). This is required since not all HID features can be merged in a multireport and needs more than a single endpoint to work properly. I have no idea to solve it though. But I will test the new build now. |
Hi Nico, you can specify multiple endpoints in an alternative implementation of HID library; I am still convinced that that class should remain as simple as possible. |
Then the solution sounds reasonable. I looked at all commits of the PR anyways, looking good so far. This weekend I hope I can test what I can do this these updates now. Anything that is still on the todo list? Is this static thing now 100% fixed? You can remove this public statement: |
I found a new problem: Originally I was trying to remove the interface check to the Pluggable USB instead of the HID class to avoid code duplication, passing a variable and coding errors. |
Next problem I found is that the API is currently not supporting multiple interfaces per plug. I am not sure if this is really needed to have but there are two options:
It seems that the CDC Serial uses 2 interfaces. Not sure if this can be realized with 2 plugs or better with a real multiinterface API. We should defenitely consider to make the CDC class pluggable as well. This would save us some bytes. Not sure if we still have the static problem here as well. Once again to the static problem: I also made some changes to the API (on top of this PR). Please have a look (each commit pls): |
This is an interesting one, looking at the sequence of calls starting from the ISR we have: // Endpoint 0 interrupt
ISR(USB_COM_vect)
{
[...]
USBSetup setup;
Recv((u8*)&setup,8);
[...]
u8 requestType = setup.bmRequestType;
[...]
if (REQUEST_STANDARD == (requestType & REQUEST_TYPE))
{
u8 r = setup.bRequest;
u16 wValue = setup.wValueL | (setup.wValueH << 8);
[...]
else if (GET_DESCRIPTOR == r)
{
ok = SendDescriptor(setup); <---- enters here
}
[...]
} static bool SendDescriptor(USBSetup& setup)
{
int ret;
u8 t = setup.wValueH;
if (USB_CONFIGURATION_DESCRIPTOR_TYPE == t)
return SendConfiguration(setup.wLength);
InitControl(setup.wLength);
#ifdef PLUGGABLE_USB_ENABLED
ret = PluggableUSB().getDescriptor(t); <--- enters here
if (ret != 0) {
return (ret > 0 ? true : false);
}
#endif int PluggableUSB_::getDescriptor(int8_t type)
{
PUSBListNode* node;
for (node = rootNode; node; node = node->next) {
int ret = node->getDescriptor(type);
// ret!=0 -> request has been processed
if (ret)
return ret;
}
return 0;
} so we don't have the interface number information anywhere in the chain.
The pluggable should already handle multiple interfaces, you just need to instantiate the PlugNode with the value of 2 on |
Yes you can set numIFs o 2. But how can we actually call a methode of interface 2? This was too long ago since I looked at the code, but I think there were some parts where not all interfaces can be used. The implementation seemed not 100% completed/compatible to me. Well I have to look again later. |
Thanks for looking into this. In the meantime I'm merging this one so we can align the other implementations (for SAM and SAMD) as well, and eventually add the latest bit coming from your analysis. Looking at the CDC implementation:
So, in the end, the USB-CDC seems to not suffer the problem above. |
Meh you deleted the branch. Now I cannot compare my changes real quick... The multiinterface would be "disabled" with this commit I made: So its better to check the value in the HID class itself. And yes we should rather use the wIndex value to check the Interface number. So we a) dont need to pass a 2nd value and b) we may remove the interfacetype and use this instead. I also got a new idea (pretending the multi interface thing works) is to make the pluggable HID to also plug different Interface or endpoints. Depending if you want to use a multireport device or now. I will try this out myself. TODO:
I guess you go ahead and fix this yourself since I currently have less time and want to avoid doing the work twice. Your commits look really good and I am really happy to see the change :) Oh and I would not apply this to SAM right now. Give it some more time, just a tiny bit before we change/revert stuff. I guess we could have some problems with Midi USB. Maybe @facchinm should try to fix this first and see if the PUSB is flexible enough for this. (regarding the static problem especially) |
Meow. To rename the functions is up to you. |
May somebody explain me the static fix once again? I think you currently cannot add multiple instances of HID like that. Originally I wanted to create a main HID class and inherit from this multiple interfaces. I think this is impossible now. However I think I can solve this with a multiinterface HID class. You then can plug the HIDDevice to any interface number and give them unique interfaces if desired. But then it is required to create a flexible endpoint type array and interface number etc which is generated at runtime. The Keyboard/Mouse constructor needs to be called first and then it adds them to the HID class. However this requires those variables to be non const:
Is this correct? Can anyone follow? Then we can have (all pluggable):
|
This is a follow up to #3840, an attempt to make the PluggableHID API more flexible before the release.
@NicoHood, please, try to find 10 minutes to look at the commits one at a time. From what I can see they "match" your latest attempts very closely.
This is implemented on AVR only for now (SAM and SAMD will follow).