rust-embedded-community / usbd-serial

Work-in progress minimal CDC-ACM (USB serial port) class for usb-device
MIT License
118 stars 35 forks source link

First packet getting lost #30

Closed paunstefan closed 1 year ago

paunstefan commented 1 year ago

Hello,

I'm trying to use USB serial to read and write some config options from a Raspberry Pi Pico used for a macro keypad. The Pico acts both as a HID device (using the usbd_hid crate) and a Serial device. The main thread just builds the HID reports, and the USB polling and handling of the serial communication happens inside the IRQ handler. The Pico never starts the communication, it always responds to request packets sent by the PC.

My problem is that the first packet sent by the PC is never received, but from then on it seems they are all received. Am I using the crate in a wrong way or are there some details of the USB Serial protocol that I don't understand?

The IRQ handler:

#[interrupt]
unsafe fn USBCTRL_IRQ() {
    // Handle USB request
    let usb_dev = USB_DEVICE.as_mut().unwrap();
    let usb_hid = USB_HID.as_mut().unwrap();
    let serial = USB_SERIAL.as_mut().unwrap();

    if usb_dev.poll(&mut [usb_hid, serial]) {
        let mut buf = [0u8; 67];
        let rd = serial.read(&mut buf);

        let response = match rd {
            Ok(0) => None,
            Ok(n) => config::process_command(&mut buf, n), // This function just builds a new packet to be sent back to the PC
            Err(_) => None,
        };

        if response.is_some() {
            serial.write(response.unwrap());
        }
    }
}

I also have an extra question because I'm curious and I don't want to open an additional issue. If in the IRQ handler I just set a flag (a volatile bool) if the poll returns true, and I do everything else in the main thread, including the serial reading and writing, the device doesn't seem to receive anything, read always returns WouldBlock. What could be the cause for this behavior?

Config:

MuratUrsavas commented 1 year ago

I'm having the same issue with a very similar setup, but not sure the problem is within the usbd_serial. usb_dev.poll() could be also failing to transfer the very first frame.

My observation is, the frame is detected but it is giving a "WouldBlock" error. Hence we can't read it in that poll call, which is an interrupt handler in my case (using RTIC, btw). But in the next poll call, everything is fine and no further frames get lost.

Config:

MuratUrsavas commented 1 year ago

I'd like to write my notes here while searching for the culprit.

My first observation is that somehow the first frame is not processed correctly by the USB peripheral. I've done some tests with it and seen that only one byte is correctly received by the EP1 (which has OUT buffer at 0x501001c0). And it's not always the first or last byte. Random byte inside that frame appear in the first byte of the OUT buffer but that's it.

No error indication, no failed transfers or unusual SUSPEND or STALL states. No real clue.

But after the first frame, every sent frame appears at the exact buffer without a problem. Either we have a sneaky configuration issue which is getting reset implicitly after the first frame, or there is an unknown errata.

MuratUrsavas commented 1 year ago

First frame as "FIRST FRAME" over 115200,8,N,1 CDC Channel Screenshot from 2022-11-28 13-52-27

Second frame as "SECOND FRAME" Screenshot from 2022-11-28 13-52-50

MuratUrsavas commented 1 year ago

My observation is, the frame is detected but it is giving a "WouldBlock" error.

This is simply incorrect because I was assuming RX was creating USBCTRL_IRQ. Actually it just indicates polling time has arrived in this case.

MuratUrsavas commented 1 year ago

I think I've found the root cause. Before the first frame, the LENGTH_0 data under EP1_OUT_BUFFER_CONTROL register is zero. That is creating issues. It's implicitly set to 0x40 while trying to receive first frame, and then everything continues as expected.

I've manually set the LENGTH_0 to 0x40 before trying to receive first frame and everything was fine.

I have a hunch that this is happening due to SerialPort::new() which uses uninitialized_mem. I guess I should be using SerialPort::new_with_store().

To be continued...

MuratUrsavas commented 1 year ago

I've finally revealed the culprit. It's happening on rp2040_hal. I'll try to fix the issue and leave a note at here.

MuratUrsavas commented 1 year ago

Now I'm able to receive the first frame without any issues. You can find my patch here:

https://github.com/rp-rs/rp-hal/pull/510

It should be available after rp2040_hal 0.6.2.

MuratUrsavas commented 1 year ago

I think this issue can be closed since my fix has been approved and will be available shortly.