rust-embedded-community/usb-device

Alternate setting support

kiffie opened this issue · 5 comments

Hi all,

I did not find support for Alternate Settings for Interfaces. Any progress here? As I needed this for one of my projects (USB audio), I use a quite pragmatic solution. Maybe, you would like to integrate it or something similar.

A function like interface_with_alternate_setting could be added to descriptor.rs, which must be called multiple times to create multiple interface descriptors for the respective alternate settings. The SET_INTERFACE request can then be handled in an impl<B: UsbBus> UsbClass<B>. Later, the request handler in device.rs could be extended to avoid boilerplate code in the UsbClass impl.

In case you are interested in this approach of adding alternate settings, I could create a PR.

Here is the code of the the function I added to my own copy of your code during my experiments:

    /// Writes a interface descriptor with a specific alternate setting.
    ///
    /// # Arguments
    ///
    /// * `number` - Interface number previously allocated with
    ///   [`UsbBusAllocator::interface`](crate::bus::UsbBusAllocator::interface).
    /// * `alternate_setting` - number of the alternate setting
    /// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
    ///   that do not conform to any class.
    /// * `interface_sub_class` - Sub-class code. Depends on class.
    /// * `interface_protocol` - Protocol code. Depends on class and sub-class.
    pub fn interface_with_alternate_setting(&mut self, number: InterfaceNumber,
        alternate_setting: u8,
        interface_class: u8, interface_sub_class: u8, interface_protocol: u8) -> Result<()>
    {
        if alternate_setting == device::DEFAULT_ALTERNATE_SETTING {
            match self.num_interfaces_mark {
                Some(mark) => self.buf[mark] += 1,
                None => return Err(UsbError::InvalidState),
            };
        }

        self.num_endpoints_mark = Some(self.position + 4);

        self.write(
            descriptor_type::INTERFACE,
            &[
                number.into(), // bInterfaceNumber
                alternate_setting, // bAlternateSetting (how to even handle these...)
                0, // bNumEndpoints
                interface_class, // bInterfaceClass
                interface_sub_class, // bInterfaceSubClass
                interface_protocol, // bInterfaceProtocol
                0, // iInterface
            ])?;

        Ok(())
    }

Best regards,

Stephan

This looks very neat. In my dfu-bootloader project i did something similar:

    /// Writes a interface descriptor.
    ///
    /// # Arguments
    ///
    /// * `number` - Interface number previously allocated with
    ///   [`UsbBusAllocator::interface`](crate::bus::UsbBusAllocator::interface).
    /// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
    ///   that do not conform to any class.
    /// * `interface_sub_class` - Sub-class code. Depends on class.
    /// * `interface_protocol` - Protocol code. Depends on class and sub-class.
    pub fn interface(&mut self, number: InterfaceNumber, interface_class: u8,
                     interface_sub_class: u8, interface_protocol: u8) -> Result<()> {
        self.interface_ex(number, interface_class, interface_sub_class,
                          interface_protocol, device::DEFAULT_ALTERNATE_SETTING,
                          0)
    }

    /// Extended writer for an interface descriptor
    pub fn interface_ex(&mut self, number: InterfaceNumber,
        interface_class: u8, interface_sub_class: u8, interface_protocol: u8,
        alternate_setting: u8, i_interface: u8) -> Result<()>
    {
        match self.num_interfaces_mark {
            Some(mark) => self.buf[mark] += 1,
            None => return Err(UsbError::InvalidState),
        };

        self.num_endpoints_mark = Some(self.position + 4);

        self.write(
            descriptor_type::INTERFACE,
            &[
                number.into(), // bInterfaceNumber
                //device::DEFAULT_ALTERNATE_SETTING, // bAlternateSetting (how to even handle these...)
                alternate_setting, // bAlternateSetting
                0, // bNumEndpoints
                interface_class, // bInterfaceClass
                interface_sub_class, // bInterfaceSubClass
                interface_protocol, // bInterfaceProtocol
                i_interface, // iInterface
            ])?;

        Ok(())
    }

I am slowly getting back into this project and updating everything. @mvirkkunen Could you please take a look at our two implementations? Maybe we can glue something together and make a PR for that. It would be awesome to have this feature in upstream.

I've started using this approach, it would be great to get something merged in to usb-device. @mvirkkunen are you OK with the USB class implementer dealing with the interface setting control requests?

I just created a PR (combined and beatified versions of the above two code snippets) for easier review and discussion.

Support was merged in #55. Thanks!

Great! Thank you.