BenLunt wrote:
Bonfra wrote:
Basically, I'm starting and stopping the schedule for every transaction. before every operation, I clear it all with TD_TERMINATE and put a queue head as the first element pointing to the first TD so they get evaluated breath first.
(Note that Breadth First is horizontal. Only one TD in that Queue will be executed, moving to the next Queue in this frame. Any concurrent TDs in any of these QHs will not be executed until the next frame or if any QH points back to any of these existing QHs. Are your intentions Depth First instead of Breadth First?)
In my opinion, it is not safe practice to start and stop the schedule. I am away from my desk, which means I am away from my notes, but if memory serves, the UHCI expects all memory to be static until the next frame. i.e.: during a frame, all memory that can be accessed by the controller is assumed to have already be written to physical memory. i.e.: you should not write to any Queue or TD that can be accessed by the UHCI during this frame (1ms). Same for reads, you should not assume any read is valid until the end-of-frame interrupt has been triggered.
Any read or write via an emulator will be instant. i.e.: as soon as the write is encountered, any further read from that memory will read the new value. However, on real hardware, the controller can and probably will read a full page (4k for example) of physical memory, using that cached page until a memory address is outside that 4k, triggering another 4k read. Therefore, any write to that 4k memory after the controller has read it, will not be valid until the next frame when the controller is required to read it again from physical memory.
At end-of-frame, the controller is to write back any memory that it may have modified (status flags, length, etc.), as well as when you stop the controller. What if you have written to a memory position that the controller thinks has been static, in turn writing back its values, over-writing your modification?
Again, this is all from memory, but if it serves me correctly, you should not write to any memory that the controller will access during that particular frame, and you must not read from any address that the controller can access during this frame until the end-of-frame is triggered.
A suggested way to do this is to have multiple periodical schedules within your frame's stack. i.e.: have every odd frame number point to one periodical stack, and every even frame number point to another stack. As for control/bulk TDs, do a similar technique. Every odd frame uses one stream of Control/Bulk Queues/TDs and every even numbered frame uses another stream. Then all you have to do is see which frame number the UHCI is on and use the correct periodical/control/bulk stream. With this technique, you can safely read and write to the "dormant" stream at will, as long as you take less than 1ms to make the modifications *and* none of the streams overlap each other. i.e.: no QH/TD in the odd stream is present in the even stream.
That seems like a bit of a faff, as well as a potential waste of peak device bandwidth.
According to the spec, any link fields are read-only to controller, so the controller will not be modifying any link fields., So long as updates to the link fields are atomic, that should suffice for the controller.
In my code, for each transfer, I build offline a QH with however many TD are required to transfer all the data for the request. Being offline, and not linked into the UHCI schedule, this will not be dependent on the when the UHCI reads data based on the schedule.
Say you want to insert the new QH2 AFTER QH1, you fill in QH2.next pointer with the QH1.next, so that both QH1 and the new QH2 point to the same next QH (or null, if there is nothing else on the end of the list.)
Then, you update QH1.next to point to QH2 in a singe write. This write is (should be) atomic, so the UHCI will either get the old value, or the new value, but either way, the chain of QH should be coherent, and being read-only to the UHCI, it will not be updated by the UHCI controller.
Removing a QH is similarly an atomic single write, setting QH1.next from QH2.next (if we're removing QH2). Being a single write, it will be atomic. Thinking about it, though, the controller might be traversing this QH when you remove it, so you should perhaps remove QH2, then wait for the schedule to move to the next frame before reusing the QH. You can do this by putting the removed QH into a separate queue, not linked to the UHCI schedule, and after the end of frame interrupt, you can just walk the list of now removed and surplus QH, releasing the resources safe in the knowledge the controller has finished the frame so definitely won't be referencing them again. I currently don't do this waiting, so I can actually free the resources used by the QH/TD while the controller might be actually traversing them in the next frame, so that's something for me to fix!