Page 1 of 1

UHCI framelist stuck

Posted: Thu Nov 03, 2022 9:20 am
by Bonfra
The scheduler of the UHCI controller freezes at a specific index in the frame list.
After initializing the controller and checking which ports have something connected, I try to retrieve the device descriptor. I tried with only the problematic packets in the framelist to have a cleaner environment to inspect: these are the transfer descriptors present in the framelist:

Code: Select all

print tds[0]
$1 = {link = 0x1, flags = 0x80002d, maxlen = 0xe00000, bufptr = 0x9fa00}
print tds[1]
$2 = {link = 0x1, flags = 0x800069, maxlen = 0xe80000, bufptr = 0x9fa20}
reading the IO_FRNUM register, it executes 0, goes to 1 and every subsequent read is always a 1.
The setup packet is the following:

Code: Select all

print &setup
$3 = (usb_request_packet_t *) 0x9fa00
print setup
$4 = {type = 0x80, request = 0x6, value = 0x100, index = 0x0, size = 0x12}
print packets[0]
$5 = {type = USB_PACKET_TYPE_SETUP, maxlen = 0x8, toggle = 0x0, buffer = 0x9fa00}
and the next packet(s) just point to the device descriptor

Code: Select all

print packets[1]
$7 = {type = USB_PACKET_TYPE_IN, maxlen = 0x8, toggle = 0x1, buffer = 0x9fa20}
print packets[2]
$8 = {type = USB_PACKET_TYPE_IN, maxlen = 0x8, toggle = 0x0, buffer = 0x9fa28}
...
Anyway, no point in showing them all and later the USB_PACKET_TYPE_OUT since it never reaches that packet. Are these data malformed in some way?
next post I'll put some meaningful code (keep in mind for the moment its mainly a collage of things I found on some repos)

Re: UHCI framelist stuck

Posted: Thu Nov 03, 2022 9:24 am
by Bonfra
this functions takes an array of packets and posts them in the framelist for the controller to execute

Code: Select all

usb_transfer_status_t transfer_packets(void* data, uint64_t addr, uint64_t endpoint, const usb_packet_t* packets, size_t num_packets)
    uhci_controller_t* controller = data;
    for(uint32_t i = 0; i < 1024; ++i)
        controller->frame_list[i] = FRAMELIST_TERMINATE;

    volatile alignas(0x20) transfer_descriptor_t tds[num_packets];

    for(size_t i = 0; i < num_packets; i++)
    {
        tds[i].link = TD_TERMINATE;
        tds[i].flags = TD_STATUS_ACTIVE;

        if(i == num_packets - 1)
            tds[i].flags |= TD_IOC;

        if(port_status(controller, addr) == USB_PORT_STATUS_CONNECT_LOW_SPEED)
            tds[i].flags |= TD_LOW_SPEED;
        
        tds[i].maxlen = ((packets[i].maxlen - 1) << 21) | (endpoint << 15) | (addr << 8);
        
        switch (packets[i].type)
        {
        case USB_PACKET_TYPE_SETUP: tds[i].flags |= TD_PID_SETUP; break;
        case USB_PACKET_TYPE_IN: tds[i].flags |= TD_PID_IN; break;
        case USB_PACKET_TYPE_OUT: tds[i].flags |= TD_PID_OUT; break;
        }

        if(packets[i].toggle)
            tds[i].maxlen |= TD_DATA_TOGGLE;

        tds[i].bufptr = (uint32_t)(uint64_t)packets[i].buffer;
        controller->frame_list[i] = (uint32_t)(uint64_t)&tds[i];
    }

    sched_run(controller);

    while(!(inportw(controller->io + IO_USBSTS) & USBSTS_INT)) // stuck here with inportw(controller->io + IO_FRNUM) == 1
        asm("pause");

    sched_stop(controller);

    return USB_TRANSFER_STATUS_OK;
} 
and here the code that builds the packets

Code: Select all

		alignas(0x20) usb_device_descriptor_t descriptor;

		alignas(0x20) usb_request_packet_t setup;
		setup.type = USB_REQUEST_DIR_DEVICE_TO_HOST | USB_REQUEST_TYPE_STANDARD;
		setup.request = USB_REQUEST_GET_DESCRIPTOR;
		setup.value = USB_DESCRIPTOR_DEVICE << 8;
		setup.index = 0;
		setup.size = sizeof(usb_device_descriptor_t);

		alignas(0x20) usb_packet_t packets[2 + setup.size / 8];
		memset(packets, 0, sizeof(packets));

		packets[0].type = USB_PACKET_TYPE_SETUP;
		packets[0].maxlen = 8;
		packets[0].buffer = &setup;

		int toggle = 1;
		int pid = 1;
		for(int i = 0; i < setup.size; i += 8)
		{
			packets[pid].type = USB_PACKET_TYPE_IN;
			packets[pid].maxlen = 8;
			packets[pid].buffer = (void*)&descriptor + i;
			packets[pid].toggle = toggle;
			toggle = toggle ? 0 : 1;
			pid++;
		}

		packets[pid].type = USB_PACKET_TYPE_OUT;
		packets[pid].maxlen = 0x800;
		packets[pid].buffer = (void*)0x80000000;
		packets[pid].toggle = 1;

		 hci.driver->transfer_packets(hci.data, 0, 0, packets, pid);

Re: UHCI framelist stuck

Posted: Thu Nov 03, 2022 5:55 pm
by BenLunt
Bonfra wrote:The scheduler of the UHCI controller freezes at a specific index in the frame list.
After initializing the controller and checking which ports have something connected, I try to retrieve the device descriptor. I tried with only the problematic packets in the framelist to have a cleaner environment to inspect: these are the transfer descriptors present in the framelist:

Code: Select all

print tds[0]
$1 = {link = 0x1, flags = 0x80002d, maxlen = 0xe00000, bufptr = 0x9fa00}
print tds[1]
$2 = {link = 0x1, flags = 0x800069, maxlen = 0xe80000, bufptr = 0x9fa20}
As for the 'flags' DWORD, you should set the C_ERR field to 3. Also, shouldn't the 2D and 69 be in the 'maxlen' DWORD, not the 'flags' DWORD?
Is the device a low-speed device? If so, the low-speed bit in the 'flags' DWORD should be set. The SPD bit should be set in the DATA IN packet.
Also, if System Management is still active, writing to physical address 0x9FA00 and above is quite dangerous. You should probably try a different address.
Bonfra wrote: reading the IO_FRNUM register, it executes 0, goes to 1 and every subsequent read is always a 1.
This means that the RUN/STOP bit in the Command register is now zero and the HCHalted bit should now be set. This is due to an error.
Since your 'maxlen' DWORD doesn't have the PID field set (the 2D and 69 above), this is probably the error.

Ben
- https://www.fysnet.net/the_universal_serial_bus.htm

Re: UHCI framelist stuck

Posted: Fri Nov 04, 2022 7:35 am
by Bonfra
BenLunt wrote:Also, shouldn't the 2D and 69 be in the 'maxlen' DWORD, not the 'flags' DWORD?
it really was this... how could I've not noticed :[ yup just doing this returns a meaningful descriptor

Code: Select all

$1 = {length = 0x12, type = 0x1, usb_release_num = 0x200, class = 0x0, subclass = 0x0, protocol = 0x0, max_packet_size = 0x8, vendor = 0x46f4, product = 0x1, dev_release_num = 0x0, manufacturer_index = 0x1, product_index = 0x2, serial_number_index = 0x3, num_configurations = 0x1}
BenLunt wrote:The SPD bit should be set in the DATA IN packet.
Isn't this for interrupt stuff if the packet is shorter than expected? how is it useful in this context? (other than detecting wrong responses)

Re: UHCI framelist stuck

Posted: Fri Nov 04, 2022 3:54 pm
by BenLunt
Bonfra wrote:
BenLunt wrote:The SPD bit should be set in the DATA IN packet.
Isn't this for interrupt stuff if the packet is shorter than expected? how is it useful in this context? (other than detecting wrong responses)
In this particular case, the SPD bit doesn't need to be set. However, in general, it should.

For example, if you have a Transfer Descriptor (TD) with a single SETUP, five INs, and a STATUS packet, what happens if the second IN packet is short?
If you don't enable the SPD, the controller will try to execute the remaining INs even though there is no more data to receive, finally (maybe) making it to the STATUS packet.
If you enable the SPD and set up your TD correctly, the controller will skip the remaining INs and go directly to the STATUS packet, automatically.

Place the SETUP and all INs in the 'depth' first direction within the Queue, then place the STATUS in the 'Horizontal' direction in the Queue. At the first Short Packet, the controller will stop executing in the depth first and move to the horizontal pointer, which should now be your STATUS packet. All remaining INs in the depth direction will be, for lack of a better term, discarded.

This makes it really easy when you retrieve an unknown length of data from the pipe. i.e.: There is no header telling you how much data you can retrieve.

Ben

Re: UHCI framelist stuck

Posted: Fri Nov 04, 2022 11:33 pm
by nullplan
BenLunt wrote:This makes it really easy when you retrieve an unknown length of data from the pipe. i.e.: There is no header telling you how much data you can retrieve.
But that happens very rarely, doesn't it? All the standard descriptors have a fixed length or else tell you the total length at a know offset. Are there any classes that have variable-length descriptors without a total length field?

Re: UHCI framelist stuck

Posted: Sat Nov 05, 2022 8:37 am
by BenLunt
nullplan wrote:
BenLunt wrote:This makes it really easy when you retrieve an unknown length of data from the pipe. i.e.: There is no header telling you how much data you can retrieve.
But that happens very rarely, doesn't it? All the standard descriptors have a fixed length or else tell you the total length at a know offset. Are there any classes that have variable-length descriptors without a total length field?
Descriptors, not usually. But there is much more to USB than Descriptors. :-)

If your framework already has the idea of short packets at the Descriptor level, then when you get to Bulk and Interrupt pipes, it is a simple task to allow for short packets, letting the controller move on to the next item with no interaction with software because of the short packet.

If memory serves, one instance that I can think of is the Request Sense request. You are expecting 18 bytes, but can and usually receive a few less. But that comes from memory, so I may be thinking of something else.

Ben