USB2.0 control endpoint stall upon set_configuration request

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
melef
Posts: 4
Joined: Mon Apr 03, 2023 7:34 am
Libera.chat IRC: melef

USB2.0 control endpoint stall upon set_configuration request

Post by melef »

I'm working on xhci controller driver for a proprietary OS. I'm having a problem with enumerating USB2 devices. USB3 devices get enumerated without issues. Here's what my driver does:

1. Attach device and a port status change event is posted.
2. if the event is a USB2 attach, reset the port and wait for it to complete the reset.
3. Send an enableSlot command to the controller.
4. if success, send an AddressDevice command to the controller.
5. If success, read the device descriptor and get the maxPacketSize
6. Update the input context and send an EvaluateContext command.
7. If success, read the configuration descriptor.
8. Configure contexts, send ConfigureEndpoint command to the controller.
9. If success, send a SET_CONFIGURATION request to the device.
10. Device responds with a transfer event with a success completion code.

Now, if I attach a USB3 device, all 10 steps complete without issues. However, if I attach a USB2 device, only the first 9 steps complete and the device responds with a STALL on the 10th step.

I have tried more things than I can even list here, including playing with the BSR, sending SET_ADDRESS requests manually, trying other configurations, different devices, sending wrong configurations... They all return a STALL on the status stage trb.

Is there any way to know what is causing the usb2 devices to stall? How can I fix this? Any pointers are much appreciated.
User avatar
bellezzasolo
Member
Member
Posts: 110
Joined: Sun Feb 20, 2011 2:01 pm

Re: USB2.0 control endpoint stall upon set_configuration req

Post by bellezzasolo »

It could be any number of things. But are you sending a DATA stage TRB? Set Configuration is the first command that doesn't have a data payload, so it doesn't need one.
Whoever said you can't do OS development on Windows?
https://github.com/ChaiSoft/ChaiOS
melef
Posts: 4
Joined: Mon Apr 03, 2023 7:34 am
Libera.chat IRC: melef

Re: USB2.0 control endpoint stall upon set_configuration req

Post by melef »

No I'm not sending a DATA stage.
User avatar
bellezzasolo
Member
Member
Posts: 110
Joined: Sun Feb 20, 2011 2:01 pm

Re: USB2.0 control endpoint stall upon set_configuration req

Post by bellezzasolo »

melef wrote:No I'm not sending a DATA stage.
Without seeing the code it's difficult to say.

This is what I have for the procedure:

Code: Select all

static usb_status_t ConfigureDevice(UsbDeviceInfo* device, int CONFIG, uint8_t HubPorts)
{
	usb_status_t status = USB_FAIL;

	REQUEST_PACKET device_packet;
	device_packet.request_type = USB_BM_REQUEST_INPUT | USB_BM_REQUEST_STANDARD | USB_BM_REQUEST_DEVICE;
	device_packet.request = USB_BREQUEST_GET_DESCRIPTOR;
	device_packet.value = USB_DESCRIPTOR_WVALUE(USB_DESCRIPTOR_CONFIGURATION, CONFIG-1);
	device_packet.index = 0;
	
	auto confdesc = GetCompleteDescriptor<usb_configuration_descriptor>(device, device_packet, &GetConfigLength);
	if (!confdesc)
		return USB_FAIL;
	usb_interface_descriptor* defiface = raw_offset<usb_interface_descriptor*>(confdesc, confdesc->bLength);
	usb_descriptor* descep = raw_offset<usb_descriptor*>(defiface, defiface->bLength);
	usb_endpoint_descriptor** endpointdata = new usb_endpoint_descriptor * [defiface->bNumEndpoints + 1];
	int i = 0;
	while (raw_diff(descep, confdesc) < confdesc->wTotalLength)
	{
		if (descep->bDescriptorType == USB_DESCRIPTOR_ENDPOINT)
		{
			endpointdata[i++] = (usb_endpoint_descriptor*)descep;
			if (i == defiface->bNumEndpoints) break;
		}
		descep = raw_offset<usb_descriptor*>(descep, descep->bLength);
	}
	endpointdata[defiface->bNumEndpoints] = nullptr;
	if (USB_FAILED(status = device->ConfigureEndpoint(endpointdata, 1, 0, 0, HubPorts)))
		return status;
	//Send set configuration
	device_packet.request_type = USB_BM_REQUEST_OUTPUT | USB_BM_REQUEST_STANDARD | USB_BM_REQUEST_DEVICE;
	device_packet.request = USB_BREQUEST_SET_CONFIGURATION;
	device_packet.value = USB_DESCRIPTOR_WVALUE(0, CONFIG);
	device_packet.index = 0;
	device_packet.length = 0;
	void* devdesc;
	if (USB_FAILED(status = device->RequestData(device_packet, (void**)&devdesc)))
		return status;
	kputs(u"Device Configured\n");
	return USB_SUCCESS;
}

Set_Configuration is USB_BM_REQUEST_OUTPUT, something very simple that might potentially get overlooked.

The one that caught me out for a while was that for retrieving the descriptor, the index is zero-based, (CONFIG-1), but it's one-base (CONFIG) for SET_CONFIGURATION.

And then it could even be an issue with the endpoint configuration.

Code: Select all

virtual usb_status_t ConfigureEndpoint(usb_endpoint_descriptor** pEndpoints, uint8_t config, uint8_t iface, uint8_t alternate, uint8_t downstreamports)
{
	paddr_t incontxt;
	size_t epAdd = 1;
	size_t highbit = 0;

	auto parseep = [](usb_endpoint_descriptor* ep, uint8_t& addr, uint8_t& dirIn)
	{
		addr = ep->bEndpointAddress & 0xF;
		dirIn = ep->bEndpointAddress >> 7;
	};
	
	auto BitToSet = [&](usb_endpoint_descriptor* ep)
	{
		uint8_t addr, dirIn;
		parseep(ep, addr, dirIn);
		return 2 * addr + dirIn;
	};

	for (int n = 0; pEndpoints[n]; ++n)
	{
		auto bitset = BitToSet(pEndpoints[n]);
		epAdd |= (1 << bitset);
		highbit = bitset > highbit ? bitset : highbit;
	}
	auto mapin = controller->CreateInputContext(incontxt, epAdd, 0, config, iface, alternate);
	mapin->slot.ContextEntries = highbit;
	if (downstreamports > 0)
	{
		mapin->slot.Hub = 1;
		mapin->slot.NumberPorts = downstreamports;
	}

	for (int n = 0; pEndpoints[n]; ++n)
	{
		auto ep = pEndpoints[n];
		uint8_t addr, dirIn;
		parseep(ep, addr, dirIn);
		auto epct = controller->GetEndpointContext(mapin, addr, dirIn);
		epct->MaxPacketSize = ep->wMaxPacketSize & 0x7FF;

		auto usbtyp = ep->bmAttributes & USB_EP_ATTR_TYPE_MASK;
		epct->EpType = usbtyp | (dirIn << 2);

		if (dirIn)
		{
			auto ring = controller->MakeEventRing(1);
			epct->TRDequeuePtr = (uint64_t)get_physical_address(ring->dequeueptr) | 1;
		}

		auto MaxEsit = epct->MaxPacketSize * (epct->MaxBurstSize + 1);
		epct->HID = 1;

		switch (usbtyp)
		{
		case USB_EP_ATTR_TYPE_CONTROL:
			break;
		case USB_EP_ATTR_TYPE_BULK:
			break;
		case USB_EP_ATTR_TYPE_INTERRUPT:
			epct->CErr = 3;
			epct->MaxEsitLow = MaxEsit & 0xFFFF;
			epct->MaxEsitHigh = MaxEsit >> 16;
			epct->Interval = 11;
			epct->AverageTrbLength = 0x400;
			break;
		case USB_EP_ATTR_TYPE_ISOCHRONOUS:
			break;
		default:
			break;
		}
	}

	//while (1);

	//Issue address device command
	void* last_command = controller->cmdring.enqueue(create_configureendpoint_command(incontxt, slotid, false));
	uint64_t* curevt = (uint64_t*)controller->waitComplete(last_command, 1000);
	if (get_trb_completion_code(curevt) != XHCI_COMPLETION_SUCCESS)
	{
		kprintf(u"Error configuring endpoint: %x\n", get_trb_completion_code(curevt));
		return USB_FAIL;
	}

	return USB_SUCCESS;
}
Whoever said you can't do OS development on Windows?
https://github.com/ChaiSoft/ChaiOS
melef
Posts: 4
Joined: Mon Apr 03, 2023 7:34 am
Libera.chat IRC: melef

Re: USB2.0 control endpoint stall upon set_configuration req

Post by melef »

The code has gotten very big so I'm gonna start by posting the contexts and other states and if we couldn't see something there then I will post as much as I can from the code. All these states are dumped after the status stage TRB completes with a STALL.
Also, I'm only configuring one endpoint just for simplicity for now.

First, here's the descriptors of a device that fails to enumerate:

Code: Select all

Device Descriptor:
===================
BcdUSB 			= 0x200
DeviceClass 		= 0
DeviceSubClass 		= 0
DeviceProtocol 		= 0
MaxPacketSize0 		= 64
IdVendor 		= 8644
IdProduct 		= 3271
BcdDevice 		= 4352
StrManufacturer 	= 1
StrProduct 		= 2
StrSerialNumber 	= 3
NumConfigurations 	= 1

Configuration Descriptor:
---------------------------
Length 			= 9
Decriptor Type 		= 2
Total length 		= 32
Num Interfaces 		= 1
Configuration Value 	= 1
configuration 		= 0
attributes 		= 128
Max Power 		= 250
Interface Descriptor:
----------------------
Length 			= 9
Decriptor Type 		= 4
Interface Number 	= 0
Alternate Setting 	= 0
Number of endpoints 	= 2
Interace Class 		= 8
Interface Subclass 	= 6
Interface protocol 	= 80
Interface  		0
Endpoint Descriptor:
----------------------
Length 			= 7
Decriptor Type 		= 5
Endpoint Address 	= 129
Attributes 		= 2
Max packet size 	= 512
Interval 		= 255
Here's the endpoint output context for endpoint 129 after the configure endpoint command:

Code: Select all

[xhci]EP State = 0x1		
[xhci]MULT = 0x0		
[xhci]MaxPStreams = 0x0		
[xhci]LSA = 0x0		
[xhci]Interval = 0xf		
[xhci]MAX esit payload hi = 0x0		
[xhci]CERR = 0x3		
[xhci]EP type = 0x6		
[xhci]HID = 0x0		
[xhci]Max burst Size = 0x0		
[xhci]Max packet size = 0x200		
[xhci]Transfer dq ptr = 0x1c03e001		
[xhci]avg Trb length = 0x1000		
[xhci]Max esit payload lo = 0x0


Here's the output slot context when the status stage completes with a stall:

Code: Select all

[xhci]Route String = 0x0		
[xhci]Speed = 0x3		
[xhci]MTT = 0x0		
[xhci]HUB = 0x0		
[xhci]Context Entries = 0x3		
[xhci]Max Exit Latency = 0x0		
[xhci]Root hub port number = 0x1		
[xhci]Number of ports = 0x0		
[xhci]TT Hub Slot ID = 0x0		
[xhci]TT Port Number = 0x0		
[xhci]TTT = 0x0		
[xhci]Interruputer Target = 0x0		
[xhci]USB Device Address = 0x4		
[xhci]Slot State = 0x3	
Here's the control endpoint output context when the status stage completes with stall:

Code: Select all

[xhci]EP State = 0x2		
[xhci]MULT = 0x0		
[xhci]MaxPStreams = 0x0		
[xhci]LSA = 0x0		
[xhci]Interval = 0x0		
[xhci]MAX esit payload hi = 0x0		
[xhci]CERR = 0x3		
[xhci]EP type = 0x4		
[xhci]HID = 0x0		
[xhci]Max burst Size = 0x0		
[xhci]Max packet size = 0x40		
[xhci]Transfer dq ptr = 0x1c03d0a1		
[xhci]avg Trb length = 0x8		
[xhci]Max esit payload lo = 0x0
Here's the port state:

Code: Select all

Port state: 
========
PLS:0 
PP:1
CCS:1 
PED:1 
PR:0
Here are the Setup TRB I'm sending for the set_configuration request:

Code: Select all

10900
0
8
841
And here's the status TRB:

Code: Select all

0
0
0
1021
And here's the transfer event TRB in response to the status:

Code: Select all

1c03d0a0
0
6000000
4018001
rdos
Member
Member
Posts: 3296
Joined: Wed Oct 01, 2008 1:55 pm

Re: USB2.0 control endpoint stall upon set_configuration req

Post by rdos »

melef wrote: Is there any way to know what is causing the usb2 devices to stall? How can I fix this? Any pointers are much appreciated.
Stall on the control pipe typically means your messages are wrong or you are sending them with wrong DATA0/1 fields. With XHCI, it can also be that the device is not correctly configured in the device context (particularly if you only have problems with non-USB3 devices).

Note that all rules that apply to usb2 still apply with XHCI. The XHCI controller only takes care of the speed conversion, and you need to give the correct parameters to make it operate properly.
melef
Posts: 4
Joined: Mon Apr 03, 2023 7:34 am
Libera.chat IRC: melef

Re: USB2.0 control endpoint stall upon set_configuration req

Post by melef »

The problem was that I need to set the DIR bit of the status stage trb of the set configuration request to 1. USB3 devices want DIR = 0. I don't know but it works.
User avatar
bellezzasolo
Member
Member
Posts: 110
Joined: Sun Feb 20, 2011 2:01 pm

Re: USB2.0 control endpoint stall upon set_configuration req

Post by bellezzasolo »

melef wrote:The problem was that I need to set the DIR bit of the status stage trb of the set configuration request to 1. USB3 devices want DIR = 0. I don't know but it works.
USB2 specification says (8.5.3.1), which is duplicated in the USB 3 spec (8.12.2.1):
Status reporting is always in the function-to-host direction.
Taking a look in the xHCI spec (4.11.2.2):
System software is responsible for ensuring that the Direction (DIR) flag of the Data Stage and Status Stage TRBs are consistent with the USB SETUP Data defined bmRequestType:Data Transfer Direction (DTD) flag and wLength field. Refer to Table 4-7 for mapping.
Table 4-7 says that the Status Stage direction flag should be OUT only for an IN Data Stage.
Image

But here is where I think we reach the critical bit:
The Direction (DIR) flag in the Status Stage TRB indicates the direction of the control transfer acknowledgement. For USB2 devices, DIR directly determines the PID that shall be used for the associated USB2 transaction. For USB3 devices, a Status TP is defined which is used for the status stage of all Enhanced SuperSpeed control transfers. Refer to section 8.5 of the USB3 spec for the definition of the SS Status TP Direction flag.

The Direction (DIR) flag in the Data Stage TRB defines the transfer direction for all TRBS in the Data Stage TD. For USB2 devices, DIR directly determines the PID that shall be used for the Data Stage transaction. For USB3 devices, if DIR = OUT a DP is generated with write data, if DIR = IN an ACK TP is generated to request read data from the device.
So hopping across to USB3 spec 8.8, the direction flag is actually part of the addressing triple, and for the default control endpoint this is zero (=OUT).
Whoever said you can't do OS development on Windows?
https://github.com/ChaiSoft/ChaiOS
Post Reply