Page 3 of 3

Re: Playing audio through USB ADC?

Posted: Wed Jul 07, 2021 1:41 pm
by nullplan
Ethin wrote:Suffering a bit of a problem (and the USB trace events aren't very helpful in QEMU). I do the following but each time I do, I get a USB stall:

Read two (2) bytes from the descriptor to get its length and descriptor type (called n)
Read n bytes to read the full descriptor header
read n[total length] to read the descriptor header and all descriptors that follow
Why the three stages? I have often seen people read a descriptor in two stages, but that first one seems unnecessary. You should be able to just read the first eight bytes of the descriptor, then verify the header information holds up, then transfer all of it. You are already assuming that the header is at least seven bytes long, so it is not that big of a leap.
Ethin wrote:So I'm quite confused as to what I'm doing wrong here. Ideas/thoughts?
So I looked at the QEMU source code. -3 is the error code for a stall, obviously. And the error code USB_RET_STALL appears 81 times in the source code. Here are the places I could find in the main device core:
  • If your SETUP token is not exactly 8 bytes long
  • If the length given in the SETUP packet exceeds 4kB
  • If you have an IN DATA token in the SETUP data phase that doesn't have the USB_DIR_IN bit set (I think that might be your problem)
  • If you have an IN DATA token while not in the SETUP data phase or ack phase
  • If you have a DATA OUT token in the SETUP data phase that has the USB_DIR_IN bit set
  • If you have a DATA OUT token in the wrong SETUP state (as above)
  • If you have a parameter SETUP packet (whatever that is) with too large a size in the setup packet
  • If you have a token with unknown type (neither SETUP nor IN nor OUT)
There are others: for audio devices if you try to get or set unknown attributes in the mixer, or set them beyond their limits, or if you try to send audio data while the audio interface is still turned off. And still others for other kinds of devices, but you are not there yet. For now, try to set USB_DIR_IN in the RequestType and see if that changes anything.

Re: Playing audio through USB ADC?

Posted: Wed Jul 07, 2021 2:01 pm
by Ethin
nullplan wrote:
Ethin wrote:Suffering a bit of a problem (and the USB trace events aren't very helpful in QEMU). I do the following but each time I do, I get a USB stall:

Read two (2) bytes from the descriptor to get its length and descriptor type (called n)
Read n bytes to read the full descriptor header
read n[total length] to read the descriptor header and all descriptors that follow
Why the three stages? I have often seen people read a descriptor in two stages, but that first one seems unnecessary. You should be able to just read the first eight bytes of the descriptor, then verify the header information holds up, then transfer all of it. You are already assuming that the header is at least seven bytes long, so it is not that big of a leap.
Ethin wrote:So I'm quite confused as to what I'm doing wrong here. Ideas/thoughts?
So I looked at the QEMU source code. -3 is the error code for a stall, obviously. And the error code USB_RET_STALL appears 81 times in the source code. Here are the places I could find in the main device core:
  • If your SETUP token is not exactly 8 bytes long
  • If the length given in the SETUP packet exceeds 4kB
  • If you have an IN DATA token in the SETUP data phase that doesn't have the USB_DIR_IN bit set (I think that might be your problem)
  • If you have an IN DATA token while not in the SETUP data phase or ack phase
  • If you have a DATA OUT token in the SETUP data phase that has the USB_DIR_IN bit set
  • If you have a DATA OUT token in the wrong SETUP state (as above)
  • If you have a parameter SETUP packet (whatever that is) with too large a size in the setup packet
  • If you have a token with unknown type (neither SETUP nor IN nor OUT)
There are others: for audio devices if you try to get or set unknown attributes in the mixer, or set them beyond their limits, or if you try to send audio data while the audio interface is still turned off. And still others for other kinds of devices, but you are not there yet. For now, try to set USB_DIR_IN in the RequestType and see if that changes anything.
I changed the code to this (it was the only constant I could find):

Code: Select all

    UINT8* Header = 0;
        status = st->BootServices->AllocatePool(EfiBootServicesData, 65536, (void*)&Header);
    if (EFI_ERROR(status))
      goto failed;

    EFI_USB_DEVICE_REQUEST req = {0};
    req.RequestType = (USB_DEV_GET_DESCRIPTOR_REQ_TYPE | USB_REQ_TYPE_CLASS | USB_ENDPOINT_DIR_IN);
    req.Request = USB_REQ_GET_DESCRIPTOR;
    req.Value = (0x24 << 8);
    req.Index = interfaceDescriptor.InterfaceNumber;
    req.Length = 2;
    status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
  req.Length = (Header[6] << 8) | Header[5];
    status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
And I get the same error (device error, stall). I'm definitely completely confused and I don't think I've violated any of the other preconditions that you listed.

Re: Playing audio through USB ADC?

Posted: Wed Jul 07, 2021 10:30 pm
by nullplan
So here's a question: Isn't GET_DESCRIPTOR a standard request? Even if you are asking for a class specific descriptor?

I've read a bit more in the QEMU source code. It appears that GET_DESCRIPTOR is always a standard device request. So you must set the request type to USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_STANDARD. I suspect that is already done in USB_DEV_GET_DESCRIPTOR_REQ_TYPE. So the USB_REQ_TYPE_CLASS is definitely wrong. It appears to be the case that you must get the entire configuration descriptor, and then parse that one bytewise, rather than ask for descriptor type 0x24 directly. So

Code: Select all

req.RequestType = USB_DEV_GET_DESCRIPTOR_REQ_TYPE;
req.Request = USB_REQ_GET_DESCRIPTOR;
req.Value = USB_DT_CONFIG;
req.Index = 0; /* I have never seen a USB device with more than one configuration. */
req.Length = 9; /* At least get the full config descriptor */
status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
/* handle error */
req.Length = Header[2] | Header[3] << 8;
status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
That ought to get you the whole config descriptor, including all interface descriptors and their special descriptors.

Re: Playing audio through USB ADC?

Posted: Wed Jul 07, 2021 11:20 pm
by Ethin
nullplan wrote:So here's a question: Isn't GET_DESCRIPTOR a standard request? Even if you are asking for a class specific descriptor?

I've read a bit more in the QEMU source code. It appears that GET_DESCRIPTOR is always a standard device request. So you must set the request type to USB_DIR_IN | USB_RECIP_DEVICE | USB_TYPE_STANDARD. I suspect that is already done in USB_DEV_GET_DESCRIPTOR_REQ_TYPE. So the USB_REQ_TYPE_CLASS is definitely wrong. It appears to be the case that you must get the entire configuration descriptor, and then parse that one bytewise, rather than ask for descriptor type 0x24 directly. So

Code: Select all

req.RequestType = USB_DEV_GET_DESCRIPTOR_REQ_TYPE;
req.Request = USB_REQ_GET_DESCRIPTOR;
req.Value = USB_DT_CONFIG;
req.Index = 0; /* I have never seen a USB device with more than one configuration. */
req.Length = 9; /* At least get the full config descriptor */
status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
/* handle error */
req.Length = Header[2] | Header[3] << 8;
status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
That ought to get you the whole config descriptor, including all interface descriptors and their special descriptors.
Nope, its not working. Here's what I did:

Code: Select all

    EFI_USB_DEVICE_REQUEST req = {0};
    req.RequestType = USB_DEV_GET_DESCRIPTOR_REQ_TYPE;
    req.Request = USB_REQ_GET_DESCRIPTOR;
    req.Value = USB_DESC_TYPE_CONFIG;
    req.Index = 0;
    req.Length = 9;
    status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
  req.Length = Header[2] | Header[3] << 8;
    status = UsbIo->UsbControlTransfer(UsbIo, &req, EfiUsbDataIn, PcdGet32 (PcdUsbTransferTimeoutValue), &Header, req.Length, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
    UINTN DescriptorPosition = 0;
    for (UINTN i = 0; i < 0xFFFF; ++i) {
      if (Header[i] == 0x24 && Header[i + 1] == 0x01) {
        DescriptorPosition = i - 1;
        break;
      }
    }
    if (DescriptorPosition == 0) {
      status = EFI_NOT_FOUND;
      goto failed;
    }
      Print(L"Length is %d, total length is %d, total length of interface descriptor is %d, USB ADC version is %04x, %d interfaces in collection\n", Header[DescriptorPosition], req.Length, Header[DescriptorPosition + 3] | Header[DescriptorPosition + 4] << 8, Header[DescriptorPosition + 5] | Header[DescriptorPosition + 6] << 8, Header[DescriptorPosition + 7]);
I get (in the EFI shell) the error:
usb_desc_get_descriptor: 1 unknown type 0 (len 9)
There's a function for retrieving the configuration descriptor (UsbIo->UsbGetConfigurationDescriptor) but it doesn't do this.
Edit: Okay, so I've been really, really stupid. EDK II comes with lots of extra libraries on top of the protocols that it exposes. One of them is UefiUsbLib. UefiUsbLib has a function called UsbGetDescriptor():

Code: Select all

/**
  Get the descriptor of the specified USB device.

  Submit a USB get descriptor request for the USB device specified by UsbIo, Value,
  and Index, and return the descriptor in the buffer specified by Descriptor.
  The status of the transfer is returned in Status.
  If UsbIo is NULL, then ASSERT().
  If Descriptor is NULL, then ASSERT().
  If Status is NULL, then ASSERT().

  @param  UsbIo             A pointer to the USB I/O Protocol instance for the specific USB target.
  @param  Value             The device request value.
  @param  Index             The device request index.
  @param  DescriptorLength  The size, in bytes, of Descriptor.
  @param  Descriptor        A pointer to the descriptor buffer to get.
  @param  Status            A pointer to the status of the transfer.

  @retval EFI_SUCCESS           The request executed successfully.
  @retval EFI_OUT_OF_RESOURCES  The request could not be completed because the
                                buffer specified by DescriptorLength and Descriptor
                                is not large enough to hold the result of the request.
  @retval EFI_TIMEOUT           A timeout occurred executing the request.
  @retval EFI_DEVICE_ERROR      The request failed due to a device error. The transfer
                                status is returned in Status.

**/
EFI_STATUS
EFIAPI
UsbGetDescriptor (
  IN  EFI_USB_IO_PROTOCOL     *UsbIo,
  IN  UINT16                  Value,
  IN  UINT16                  Index,
  IN  UINT16                  DescriptorLength,
  OUT VOID                    *Descriptor,
  OUT UINT32                  *Status
  );
I automatically dismissed it because I didn't know how to use it but now that I know more about USB it all makes sense. Will try it now and update you guys on the status.

Re: Playing audio through USB ADC?

Posted: Wed Jul 07, 2021 11:34 pm
by Ethin
Okay, so its still giving me the same error (1 unknown type 0 (len 9)). I've minimized the code to just two calls to UsbGetDescriptor:

Code: Select all

    status = UsbGetDescriptor(UsbIo, USB_DESC_TYPE_CONFIG, interfaceDescriptor.InterfaceNumber, 9, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
    status = UsbGetDescriptor(UsbIo, USB_DESC_TYPE_CONFIG, interfaceDescriptor.InterfaceNumber, Header[2] | Header[3] << 8, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
Definitely confused. Trying to find that string isn't easy since its using format specifiers and I'm not sure which ones. Trying to find it with grep/ripgrep doesn't work -- I get the wrong results.

Re: Playing audio through USB ADC?

Posted: Thu Jul 08, 2021 8:27 am
by Octocontrabass
Ethin wrote:I get (in the EFI shell) the error:
That error is coming from QEMU, not the EFI shell. I can't tell you what it means, though - I don't know a whole lot about USB.

Re: Playing audio through USB ADC?

Posted: Thu Jul 08, 2021 10:30 am
by Ethin
Well, I think both of us had one of those "stupid" moments. Look at this code in qemu:

Code: Select all

int usb_desc_get_descriptor(USBDevice *dev, USBPacket *p,
                            int value, uint8_t *dest, size_t len)
{
    bool msos = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
    const USBDesc *desc = usb_device_get_usb_desc(dev);
    const USBDescDevice *other_dev;
    uint8_t buf[256];
    uint8_t type = value >> 8;
    uint8_t index = value & 0xff;
    int flags, ret = -1;
And then this statement from USB 3.2:
The wValue field specifies the descriptor type in the high byte (refer to Table 9-6) and the descriptor index in the low byte. The descriptor index is used to select a specific descriptor (only for configuration and string descriptors) when several descriptors of the same type are implemented in a device. For example, a device can implement several configuration descriptors. For other standard descriptors that can be retrieved via a GetDescriptor() request, a descriptor index of zero shall be used. The range of values used for a descriptor index is from 0 to one less than the number of descriptors of that type (excluding string descriptors) implemented by the device.
And it goes on to say:
]The wIndex field specifies the Language ID for string descriptors or is reset to zero for other descriptors. The wLength field specifies the number of bytes to return. If the descriptor is longer than the wLength field, only the initial bytes of the descriptor are returned. If the descriptor is shorter than the wLength field, the device indicates the end of the control transfer by sending a short packet when further data is requested.

The standard request to a device supports four types of descriptors: device, configuration, BOS (Binary device Object Store), and string. As noted in Section 9.2.6.6, a device operating at Gen X speed reports the other speeds it supports via the BOS descri ptor and shall not support the device_qualifier and other_speed_configuration descriptors. A request for a configuration descriptor returns the configuration descriptor, all interface descriptors, endpoint descriptors and endpoint companion descriptors (w hen operating at Gen X speed) for all of the interfaces in a single request. The first interface descriptor follows the configuration descriptor. The endpoint descriptors for the first interface follow the first interface descriptor. In addition, Enhanced SuperSpeed devices shall return Endpoint Companion descriptors for each of the endpoints in that interface to return the endpoint capabilities required for Enhanced SuperSpeed devices, which would not fit inside the existing endpoint descriptor footprint. If there are additional interfaces, their interface descriptor, endpoint descriptors, and endpoint companion descriptors (when operating at Gen X speed) follow the first interface’s endpoint and endpoint companion (when operating at Gen X speed) descriptors.

This specification also defines a flexible and extensible framework for describing and adding device-level capabilities to the set of USB standard specifications. The BOS descriptor (refer to Section 9.6.2) defines a root descriptor that is similar to the configuration descriptor, and is the base descriptor for accessing a family of related descriptors. A host can read a BOS descriptor and learn from the wTotalLength field the entire size of the device-level descriptor set, or it can read in the entire BOS descriptor set of device capabilities. There is no way for a host to read individual device capability descriptors. The entire set can only be accessed via reading the BOS descriptor with a GetDescrip tor() request and using the length reported in the wTotalLength field.

Class-specific and/or vendor-specific descriptors follow the standard descriptors they extend or modify.

All devices shall provide a device descriptor and at least one configuration des criptor. If a device does not support a requested descriptor, it responds with a Request Error.

Default state: This is a valid request when the device is in the Default state.

Address state: This is a valid request when the device is in the Address state .

Configured state: This is a valid request when the device is in the Configured state.
(That's from SEC. 9.4.3.)
So we need to encode the descriptor type and index in the wValue field. Maybe something like:

Code: Select all

    status = UsbGetDescriptor(UsbIo, interfaceDescriptor.InterfaceNumber | (USB_DESC_TYPE_CONFIG << 8), 0, 9, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
    status = UsbGetDescriptor(UsbIo, interfaceDescriptor.InterfaceNumber | (USB_DESC_TYPE_CONFIG << 8), 0, Header[2] | Header[3] << 8, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
Right?

Re: Playing audio through USB ADC?

Posted: Fri Jul 09, 2021 9:16 am
by nullplan
No! The index for the config descriptor is the configuration. In theory, a device with multiple configurations could exist (would be indicated in the device descriptor), and then the index would enumerate the different configurations. But for the overwhelming majority of devices, there is only one configuration. So you can just leave that index byte as zero. (If you wanted to be totally correct, you would iterate the index from 0 to DeviceDescriptor.bNumConfigurations.)

Re: Playing audio through USB ADC?

Posted: Fri Jul 09, 2021 1:53 pm
by Ethin
I did that and now I'm getting a #GP. Which is odd because I'm not performing any operation (that I know of) that causes that exception.

Code: Select all

    status = UsbGetDescriptor(UsbIo, (USB_DESC_TYPE_CONFIG << 8) | 0, 0, 9, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
    status = UsbGetDescriptor(UsbIo, (USB_DESC_TYPE_CONFIG << 8) | 0, 0, Header[2] | Header[3] << 8, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
Utilizing preprocessor definitions/enumeration discriminants, this translates to:

Code: Select all

    status = UsbGetDescriptor(UsbIo, (0x02 << 8) | 0, 0, 9, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
    status = UsbGetDescriptor(UsbIo, (0x02 << 8) | 0, 0, Header[2] | Header[3] << 8, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
Which is then converted to just:

Code: Select all

    status = UsbGetDescriptor(UsbIo, 0x200, 0, 9, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
}
    status = UsbGetDescriptor(UsbIo, 0x200, 0, Header[2] | Header[3] << 8, (void*)&Header, &UsbStatus);
    if (EFI_ERROR(status)) {
      st->BootServices->FreePool(Header);
      goto failed;
    }
The memory allocation doesn't fail -- I have checks in place for that outcome:

Code: Select all

    UINT8* Header = 0;
        status = st->BootServices->AllocatePool(EfiBootServicesData, 65536, (void*)&Header);
    if (EFI_ERROR(status))
      goto failed;
This just gets more and more confusing to me.
Edit: Well I'll be damned. I feel stupid now. I was converting the pointer into a UINT8**. So the code was overwriting my app code. Whoops? :P
Now I just need to fix my bit manipulation stuff in the Print() call because I'm combining the wrong data.

Re: Playing audio through USB ADC?

Posted: Tue Jul 13, 2021 9:50 am
by Ethin
So, I've essentially just tried what @Octocontrabass suggested -- setting the interfaces to 1 and then sending data to the endpoint. (I toggle the interfaces.) I do something like this and get an unsupported error:

Code: Select all

    for (UINTN i = 0; i < Header[DescriptorPosition + 7]; ++i) {
      status = UsbSetInterface(UsbIo, Header[DescriptorPosition + 8 + i], 0, &UsbStatus);
      if (EFI_ERROR(status))
        goto failed;
    }
    for (UINTN i = 0; i < Header[DescriptorPosition + 7]; ++i) {
      status = UsbSetInterface(UsbIo, Header[DescriptorPosition + 8 + i], 1, &UsbStatus);
      if (EFI_ERROR(status))
        goto failed;
    }
    if (interfaceDescriptor.InterfaceSubClass == 0x02) {
      status = UsbIo->UsbIsochronousTransfer(UsbIo, 0x00|0x80, SINE_SAMPLES, 48000, &UsbStatus);
      if (EFI_ERROR(status))
        goto failed;
    }
I only do this for the audio streaming interface -- I don't think this would work on an audio control interface. Am I doing something wrong? For reference, SINE_SAMPLES is an array of signed 16-bit PCM samples of a sine wave at 48000 Hz.