The OHCI registers are used for the usual stuff like enabling and disabling interrupts. Since the USB time is divided in to 1ms frames and various interrupts may need to be triggered at frame boundary time, a timer-based approach was taken. Whenever the bus is enabled ohci->eof_timer will be set.
The actual USB transfers are stored in main memory (along with endpoint and transfer descriptors). The ED's for all the control and bulk endpoints are found by consulting the HcControlHeadED and HcBulkHeadED registers respectively. Interrupt ED's are different, they are found by looking in the HCCA (another communication area in main memory).
At the start of every frame (in function ohci_sof) we traverse all enabled ED lists and queue up as many transfers as possible. No attention is paid to control/bulk service ratios or bandwidth requirements since our USB could conceivably contain a dozen high speed busses so this would artificially limit the performance.
Once we have a transfer ready to go (in function ohciServiceTd) we allocate an URB on the stack, fill in all the relevant fields and submit it using the VUSBIRhSubmitUrb function. The roothub device and the virtual USB core code (vusb.c) coordinates everything else from this point onwards.
When the URB has been successfully handed to the lower level driver, our prepare callback gets called and we can remove the TD from the ED transfer list. This stops us queueing it twice while it completes. bird: no, we don't remove it because that confuses the guest! (=> crashes)
Completed URBs are reaped at the end of every frame (in function ohci_frame_boundary). Our completion routine makes use of the ED and TD fields in the URB to store the physical addresses of the descriptors so that they may be modified in the roothub callbacks. Our completion routine (ohciRhXferComplete) carries out a number of tasks:
As for error handling OHCI allows for 3 retries before failing a transfer, an error count is stored in each transfer descriptor. A halt flag is also stored in the transfer descriptor. That allows for ED's to be disabled without stopping the bus and de-queuing them.
When the bus is started and stopped we call VUSBIDevPowerOn/Off() on our roothub to indicate it's powering up and powering down. Whenever we power down, the USB core makes sure to synchronously complete all outstanding requests so that the OHCI is never seen in an inconsistent state by the guest OS (Transfers are not meant to be unlinked until they've actually completed, but we can't do that unless we work synchronously, so we just have to fake it). bird: we do work synchronously now, anything causes guest crashes.