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.
Currently, all the entries in my frame list point to the same queue head at physical address 0x081fdf60. I inserted a device descriptor request into this queue and interrupts keep firing endlessly. I don't understand why. I'd expect it should fire just once.
Here's the contents of the memory before the first interrupt:
In the subsequent interrupts only the frame number changes. But I noticed a weird thing about the frame number. The first interrupt gives the frame # 2. The second interrupt also gives the frame # 2. Only starting from the third interrupt the frame numbers get incrementing. I don't understand why and if it's related.
The UHCI will fire an interrupt at the end of the frame if any executed TD has the IOC bit set, whether the TD was active or not.
Since you keep getting interrupts, your schedule is executing a TD (valid or not) that has the IOC bit set. If you don't remove the executed TD from the schedule, the TD will be executed again, even though it is inactive, and an interrupt will fire at the end of that frame.
If that is not the case, there are a few other reasons, one being if your schedule runs off into "no man's land" and any of the dwords it tries to execute have the IOC bit set, it will fire an interrupt. What does your schedule do when all TDs have been executed in that frame? Do you have the T bit set in the last QUEUE or TD executed?
To counter this, all TDs (not counting ISOs) should be in a QUEUE. Once the TD is executed and an interrupt is fired, you remove the QUEUE from the schedule and the controller will not execute that TD again.
1) When removing a Queue from the schedule, you *must* first write a valid address (or mark the existing address as TERMinated) to the schedule, physically removing the Queue from the linked list, *before* removing or altering the Queue or any TDs within it. The schedule must remain intact throughout the frame before you alter any part of that Queue or list of TDs. Imagine freeing the memory occupied by the Queue and TD(s) before writing a valid pointer to the previous Queue Head, removing the Queue from the linked list. This is where you can get into "no man's land" real quick.
2) Do you use Breadth first or Depth first scheduling? If you use Breadth first, the controller will execute only one TD per Queue per Frame. 1023 frames will be executed before the controller comes back to this Frame. This will make for extremely slow transfers unless you have multiple frames point to the same schedule. Even then, the controller will only execute one TD per Queue per millisecond, still making for slow transfers.
3) Whether you use Breadth first or Depth first scheduling, the controller will update the Vertical entry of the Queue with the Next Pointer value from the last executed TD in that Queue. If Breadth first, the controller will then move to and execute any other Queues in your schedule before coming back to this one. If Depth first and time allows, the controller will then execute the Vertical entry in the current Queue, which now points to the next TD in your linked list of TDs. Therefore, any previous TD in this linked list of TDs (as long as it is within a Queue), are no longer reachable by the controller and will not get executed again. With this in mind, after the last TD in the list of TDs is executed, and the controller updates the Vertical entry of the Queue, this Veritical entry should now have the T bit set, since the last TD in the list had the T bit set. Therefore, in theory, your TDs will never be executed again, as long as you follow this design.
4) Do you have a working toggle bit mechanism? The controller must see a valid toggle bit sequence for any pipe or it will stall the pipe. When a stall is found, the Vertical entry is not updated(1), and the TD can and will be tried again on the next go around, in turn firing the interrupt because the IOC bit was set.
If you follow the above notes, none of your TDs will ever be executed again. Therefore, in your current situation, you must have a bad link, a TD not within a Queue, or you are running in "no man's land" somewhere.
Hope that helps,
Ben
(1) That statement is from memory, I would have to check my notes to verify it is true, but I believe it to be.
I tried to do some checks to eliminate some hypothetical causes. One of the things I did - when handling the interrupt, I cleared the bit 0 in the USBSTS. Surprisingly, it helped - no more interrupts were fired.
Well... Sure, it makes sense that the driver should clear the status register after having handled the current status. But I can't recall having seen anywhere in the UHCI specifications that not clearing that bit will cause the interrupts being fired over and over again.... I wonder if this behavior is vendor specific.
I'm glad you figured it out. Sorry I didn't think to mention that you must clear the status. Most if not all hardware with this kind of status register requires you to clear it after an interrupt, and since you mentioned the status bit in a previous post, I assumed you had done so.