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

USB: Add CDC support, update capsule stack #1902

Merged
merged 44 commits into from Jun 15, 2020
Merged

USB: Add CDC support, update capsule stack #1902

merged 44 commits into from Jun 15, 2020

Conversation

bradjc
Copy link
Contributor

@bradjc bradjc commented Jun 2, 2020

Pull Request Overview

This pull request is part of #1048, #1893.

This adds CDC support which implements kernel::hil::Uart. This allows us to run console over USB without having to write our own host-side kernel drivers.

On that path, this also updates the capsules USB stack to allow for the larger descriptors we use with the CDC device. This is also the first time we have needed multiple interface descriptors, so it adds support for that as well.

The bug fixes needed along the way are in separate PRs.

Testing Strategy

This pull request was tested by running apps that use the console on imix, and setting up the console to use UART over CDC rather than the UART driver.

TODO or Help Wanted

This does not work on the nRF52 (at least on my computer) because that driver does not support CTRL WRITE messages. FYI.

I had to add a platform-specific variable, since the nRF only works if the device descriptor says the packet size is 64, and the SAM4L driver only works if the device descriptor says the packet size is 8. This also makes the USB stack work on the SAM4L again.

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

Formatting

  • Ran make format.
  • Fixed errors surfaced by make clippy.

bradjc and others added 30 commits June 2, 2020 11:32
This was split off from `Ok`, but not added to the return code handler
for the SAM4L. Kinda feel like this one is on Rust, since it shouldn't
compile without handling this case :(.
As far as I can tell, we only ever use the pointer address for these
buffers, and not their length, so it should be fine if the buffers are
too long (say 64 bytes rather than 8, as the capsule uses).
Buffer size increased from 8 to 64.
Per multiple resources, GET_DESCRIPTOR cannot be used to access
interfaces or endpoints separately from configurations.
https://www.beyondlogic.org/usbnutshell/usb6.shtml
Really, descriptors.rs should be handling all of this and the control
endpoint handler should just be working with buffers of data. That
wasn't totally accomplished here, but it was a start. Additionally, this
means that descriptors created by the upper layers can just be made on
the stack and passed in with no need for persistant storage.
I don't think I did it right
call enable/attach directly
no idea why this was written this way
capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/cdc.rs Show resolved Hide resolved
capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/usbc_client_ctrl.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@brghena brghena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a fantastic addition to Tock. Incredibly useful for a number of boards. A couple of small comments for you, but I otherwise approve.

capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/cdc.rs Outdated Show resolved Hide resolved
capsules/src/usb/cdc.rs Show resolved Hide resolved
capsules/src/usb/usbc_client.rs Outdated Show resolved Hide resolved
capsules/src/usb/usbc_client_ctrl.rs Show resolved Hide resolved
Comment on lines 61 to 63
/// Buffer containing the byte-packed representation of the configuration
/// descriptor and all other descriptors for this device.
other_descriptor_buffer: DescriptorBuffer,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think "other" is a meaningful name. Which descriptors are exactly in there? Maybe configuration_descriptors_buffer would make more sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well it isn't just configuration descriptors, it really is all other descriptors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but there is some structure in these descriptors, I assume they are not stored there in arbitrary order. Maybe "configuration descriptor tree" would match the concept more closely?

capsules/src/usb/usbc_client_ctrl.rs Outdated Show resolved Hide resolved
capsules/src/usb/usbc_client_ctrl.rs Outdated Show resolved Hide resolved
capsules/src/usb/usbc_client_ctrl.rs Outdated Show resolved Hide resolved
_ => hil::usb::CtrlSetupResult::ErrInvalidConfigurationIndex,
}
}
DescriptorType::Interface => match descriptor_index {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested it. But does anything break if a host sends us a query for an Interface descriptor anyway?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interface descriptor requests are not possible. Per multiple resources, GET_DESCRIPTOR cannot be used to access interfaces or endpoints separately from configurations. https://www.beyondlogic.org/usbnutshell/usb6.shtml So it should be fine to hit the default match case.

Comment on lines +395 to +400
/// Buffer for holding the configuration, interface(s), and endpoint(s)
/// descriptors. Also includes class-specific functional descriptors.
pub struct DescriptorBuffer {
pub buf: [Cell<u8>; 128],
pub len: usize,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a memory usage point of view, I find it unfortunate to store some descriptors (e.g. HID) twice: one time already serialized here, and another time in the ClientCtrl object. Can't we serialize everything on-demand in the handle_standard_device_request function, as before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be willing to add a commit? This code works, and gets us closer to a long standing item on our wishlist. The USB stack is by no means final, and I'm sure there are optimizations possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be willing to add a commit?

As far as I'm aware, I'm not able to push commits in this branch, as I'm not a "reviewer with write access".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think serializing everything immediately is actually the way to go. The ClientCtrl doesn't actually need to know much about the descriptors other than just hang on to several buffers of memory and provide them when desired. So it doesn't need a full struct sitting around. A way to stop the duplication would be to have separate buffers for endpoints, interfaces, and other stuff (like the HID) and then combine buffers when things are requested by the host. That would allow you to have only one copy of the HID. We didn't go that full route because the HID descriptor isn't very large so just having two copies lying around doesn't seem to matter much.

The other reason to go with a serialized approach is that it was getting difficult to make lifetimes work for the descriptors. The ClientCtrl can't have structs that it copies fields into because it doesn't know which descriptors are going to exist or how many. It's way easier for the ClientCtrl to have an array of bytes with a fixed size that it can just copy descriptors into than having pointers to arrays of structs that you have to reason about the lifetimes of. I'm sure there's some way to get things working that way, but I couldn't figure it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think this is really important to change before this PR goes in and you have some ideas about how to do so, I think you could pull this branch into your own Tock repo, make changes, and then make a PR against this branch rather than against master.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you think this is really important to change before this PR goes in and you have some ideas about how to do so, I think you could pull this branch into your own Tock repo, make changes, and then make a PR against this branch rather than against master.

It's not really practical to make a PR against another PR to be honest...

Match other implementations, and not all settings actually matter in practice.

Co-authored-by: Branden Ghena <brghena@berkeley.edu>
bradjc and others added 3 commits June 5, 2020 21:01
Co-authored-by: Branden Ghena <brghena@berkeley.edu>
Co-authored-by: Branden Ghena <brghena@berkeley.edu>
Co-authored-by: Branden Ghena <brghena@berkeley.edu>
Comment on lines +421 to +422
interface_descriptor: &mut [InterfaceDescriptor],
endpoint_descriptors: &[&[EndpointDescriptor]],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it weird to separate the arrays for interface and endpoint descriptors in this way. The assumption that isn't mentioned in this API is that interface_descriptor.len() == endpoint_descriptors.len().

Could it be possible to have some InterfaceHierarchy struct:

struct InterfaceHierarchy {
    interface: InterfaceDescriptor,
    endpoints: &[EndpointDescriptor],
}

Then, this function would take an interface_hierarchies: &mut [InterfaceHierarchy] parameter which would logically tie the interfaces to their endpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course, but USB is a WIP stack, so I don't think we should block this change (with a tracking issue dating back years) on the most preferred design, instead we should continue our policy of forward progress is good and iterate quickly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point. My point was mostly that in my experience working with USB, a lot of things can be brittle and therefore overlooking design could fire back in terms of long hours of subtle debugging. But sure this can be improved in a separate pull request.

Comment on lines +499 to +500
// TODO should we be erroring here if len > 128? Otherwise we'll probably
// buffer overrun and panic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should definitely be handled explicitly, e.g. panic!("Configuration descriptor too large"). This shouldn't be a problem given that all of the configuration is defined at compile time, so even if this panic happens at runtime, it's reliably triggered depending on the compile-time descriptors.

This is also IMO a reason why these descriptors should rather be serialized on-demand following get-descriptor requests (which then doesn't impose a limit on the total buffer size), rather than eagerly serialized in a temporary buffer here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the best way to handle this? We don't want panic! in capsules generally, but if this overruns we do want a fast, obvious error.

Just leaving a note for now, is honestly probably sufficient for the foreseeable future (at least until a USB capsule cleanup and rewrite) since 128 bytes fits CDC, OpenSK stuff, and also would fit USB Mass Storage or additional HID stuff if we want to expand to that in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I think even serializing on demand doesn't actually fix this simply. You would just end up with structs that, when serialized sometime at runtime, end up either overrunning the buffer or more likely getting truncated. You could handle this at runtime, but doing so would require you to account for partial progress in serializing some struct or another and picking up serializing part-way through that struct for the next packet. (You can't just stop at the last struct that fits because I believe you have to send the full 64 byte packet in order to fragment.) At least with the descriptors serialized at initialization time, the error occurs immediately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the best way to handle this? We don't want panic! in capsules generally, but if this overruns we do want a fast, obvious error.

I don't see any unsafe in this file. So if the buffer overruns, the current behavior is that it will panic at one of the array indexing operations (e.g. within a write_to where the target buffer is smaller than the source). So the current code would yield a panic (which we want to avoid apparently), a fast error (in this function), but not so obvious (array indexing out of bounds somewhere).

What I suggest is an explicit panic with a clear message here, which improves over the current state by making the error obvious.

To avoid a panic altogether, the best would probably be to put all of this code in a const fn so that the contents of the descriptor buffer is pre-computed at compile time, and that any failure would prevent it from compiling. However, I'm not sure what the current status of const fn is in terms of supporting things like loops (although I remember there has been some improvements lately).

capsules/src/usb/usbc_client_ctrl.rs Show resolved Hide resolved
@bradjc bradjc requested a review from alistair23 June 8, 2020 16:04
@bradjc
Copy link
Contributor Author

bradjc commented Jun 12, 2020

Are there any major blocking issues for this?

Copy link
Contributor

@hudson-ayers hudson-ayers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not particularly familiar with USB, but everything looks reasonable from my cursory review, and I think this is important to merge quickly so work can continue on first-class support for the nano board

@bradjc
Copy link
Contributor Author

bradjc commented Jun 12, 2020

@tock/core-wg

@brghena brghena mentioned this pull request Jun 13, 2020
2 tasks
Copy link
Member

@ppannuto ppannuto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only blockers I see are @gendx's comments:

@gendx
Copy link
Contributor

gendx commented Jun 15, 2020

I think the only blockers I see are @gendx's comments:

+1 to having (a) tracking issue(s) for the comments I left, so that they are not forgotten in the long term once USB stabilizes, but without unnecessarily blocking progress of useful features in the short term.

@bradjc bradjc mentioned this pull request Jun 15, 2020
10 tasks
@bradjc
Copy link
Contributor Author

bradjc commented Jun 15, 2020

I created #1940.

@bradjc bradjc requested a review from ppannuto June 15, 2020 16:08
Copy link
Member

@ppannuto ppannuto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bors r+

I'd have preferred if the first of the three checkboxes had just been implemented, but given that this thing is big enough already and the USB stack is generally alpha-quality anyway, leaving as a tracked TODO is good enough.

@bors bors bot merged commit 126d711 into master Jun 15, 2020
@bors bors bot deleted the usb-cdc branch June 15, 2020 17:45
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 this pull request may close these issues.

None yet

6 participants