/drivers/usb/ehci.asm |
---|
0,0 → 1,1959 |
; Code for EHCI controllers. |
; Standard driver stuff |
format PE DLL native |
entry start |
__DEBUG__ equ 1 |
__DEBUG_LEVEL__ equ 1 |
section '.reloc' data readable discardable fixups |
section '.text' code readable executable |
include '../proc32.inc' |
include '../struct.inc' |
include '../macros.inc' |
include '../fdo.inc' |
include '../../kernel/trunk/bus/usb/common.inc' |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; EHCI register declarations. |
; Part 1. Capability registers. |
; Base is MMIO from the PCI space. |
EhciCapLengthReg = 0 |
EhciVersionReg = 2 |
EhciStructParamsReg = 4 |
EhciCapParamsReg = 8 |
EhciPortRouteReg = 0Ch |
; Part 2. Operational registers. |
; Base is (base for part 1) + (value of EhciCapLengthReg). |
EhciCommandReg = 0 |
EhciStatusReg = 4 |
EhciInterruptReg = 8 |
EhciFrameIndexReg = 0Ch |
EhciCtrlDataSegReg = 10h |
EhciPeriodicListReg = 14h |
EhciAsyncListReg = 18h |
EhciConfigFlagReg = 40h |
EhciPortsReg = 44h |
; Possible values of ehci_pipe.NextQH.Type bitfield. |
EHCI_TYPE_ITD = 0 ; isochronous transfer descriptor |
EHCI_TYPE_QH = 1 ; queue head |
EHCI_TYPE_SITD = 2 ; split-transaction isochronous TD |
EHCI_TYPE_FSTN = 3 ; frame span traversal node |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; Hardware part of EHCI general transfer descriptor. |
struct ehci_hardware_td |
NextTD dd ? |
; Bit 0 is Terminate bit, 1 = there is no next TD. |
; Bits 1-4 must be zero. |
; With masked 5 lower bits, this is the physical address of the next TD, if any. |
AlternateNextTD dd ? |
; Similar to NextTD, used if the transfer terminates with a short packet. |
Token dd ? |
; 1. Lower byte is Status field: |
; bit 0 = ping state for USB2 endpoints, ERR handshake signal for USB1 endpoints |
; bit 1 = split transaction state, meaningless for USB2 endpoints |
; bit 2 = missed micro-frame |
; bit 3 = transaction error |
; bit 4 = babble detected |
; bit 5 = data buffer error |
; bit 6 = halted |
; bit 7 = active |
; 2. Next two bits (bits 8-9) are PID code, 0 = OUT, 1 = IN, 2 = SETUP. |
; 3. Next two bits (bits 10-11) is ErrorCounter. Initialized as 3, decremented |
; on each error; if it goes to zero, transaction is stopped. |
; 4. Next 3 bits (bits 12-14) are CurrentPage field. |
; 5. Next bit (bit 15) is InterruptOnComplete bit. |
; 6. Next 15 bits (bits 16-30) are TransferLength field, |
; number of bytes to transfer. |
; 7. Upper bit (bit 31) is DataToggle bit. |
BufferPointers rd 5 |
; The buffer to be transferred can be spanned on up to 5 physical pages. |
; The first item of this array is the physical address of the first byte in |
; the buffer, other items are physical addresses of next pages. Lower 12 bits |
; in other items must be set to zero; ehci_pipe.Overlay reuses some of them. |
BufferPointersHigh rd 5 |
; Upper dwords of BufferPointers for controllers with 64-bit memory access. |
; Always zero. |
ends |
; EHCI general transfer descriptor. |
; * The structure describes transfers to be performed on Control, Bulk or |
; Interrupt endpoints. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 52 bytes and corresponds to |
; the Queue Element Transfer Descriptor from EHCI specification. |
; * The hardware requires 32-bytes alignment of the hardware part, so |
; the entire descriptor must be 32-bytes aligned. Since the allocator |
; (usb_allocate_common) allocates memory sequentially from page start |
; (aligned on 0x1000 bytes), block size for the allocator must be divisible |
; by 32; ehci_alloc_td ensures this. |
; * The hardware also requires that the hardware part must not cross page |
; boundary; the allocator satisfies this automatically. |
struct ehci_gtd ehci_hardware_td |
Flags dd ? |
; Copy of flags from the call to usb_*_transfer_async. |
ends |
; EHCI-specific part of a pipe descriptor. |
; * This structure corresponds to the Queue Head from the EHCI specification. |
; * The hardware requires 32-bytes alignment of the hardware part. |
; Since the allocator (usb_allocate_common) allocates memory sequentially |
; from page start (aligned on 0x1000 bytes), block size for the allocator |
; must be divisible by 32; ehci_alloc_pipe ensures this. |
; * The hardware requires also that the hardware part must not cross page |
; boundary; the allocator satisfies this automatically. |
struct ehci_pipe |
NextQH dd ? |
; 1. First bit (bit 0) is Terminate bit, 1 = there is no next QH. |
; 2. Next two bits (bits 1-2) are Type field of the next QH, |
; one of EHCI_TYPE_* constants. |
; 3. Next two bits (bits 3-4) are reserved, must be zero. |
; 4. With masked 5 lower bits, this is the physical address of the next object |
; to be processed, usually next QH. |
Token dd ? |
; 1. Lower 7 bits are DeviceAddress field. This is the address of the |
; target device on the USB bus. |
; 2. Next bit (bit 7) is Inactivate-on-next-transaction bit. Can be nonzero |
; only for interrupt/isochronous USB1 endpoints. |
; 3. Next 4 bits (bits 8-11) are Endpoint field. This is the target endpoint |
; number. |
; 4. Next 2 bits (bits 12-13) are EndpointSpeed field, one of EHCI_SPEED_*. |
; 5. Next bit (bit 14) is DataToggleControl bit, |
; 0 = use DataToggle bit from QH, 1 = from TD. |
; 6. Next bit (bit 15) is Head-of-reclamation-list. The head of Control list |
; has 1 here, all other QHs have zero. |
; 7. Next 11 bits (bits 16-26) are MaximumPacketLength field for the target |
; endpoint. |
; 8. Next bit (bit 27) is ControlEndpoint bit, must be 1 for USB1 control |
; endpoints and 0 for all others. |
; 9. Upper 4 bits (bits 28-31) are NakCountReload field. |
; Zero for USB1 endpoints, zero for periodic endpoints. |
; For control/bulk USB2 endpoints, the code sets it to 4, |
; which is rather arbitrary. |
Flags dd ? |
; 1. Lower byte is S-mask, each bit corresponds to one microframe per frame; |
; bit is set <=> enable transactions in this microframe. |
; 2. Next byte is C-mask, each bit corresponds to one microframe per frame; |
; bit is set <=> enable complete-split transactions in this microframe. |
; Meaningful only for USB1 endpoints. |
; 3. Next 14 bits give address of the target device as hub:port, bits 16-22 |
; are the USB address of the hub, bits 23-29 are the port number. |
; Meaningful only for USB1 endpoints. |
; 4. Upper 2 bits define number of consequetive transactions per micro-frame |
; which host is allowed to permit for this endpoint. |
; For control/bulk endpoints, it must be 1. |
; For periodic endpoints, the value is taken from the endpoint descriptor. |
HeadTD dd ? |
; The physical address of the first TD for this pipe. |
; Lower 5 bits must be zero. |
Overlay ehci_hardware_td ? |
; Working area for the current TD, if there is any. |
; When TD is retired, it is written to that TD and Overlay is loaded |
; from the new TD, if any. |
BaseList dd ? |
; Pointer to head of the corresponding pipe list. |
ends |
; This structure describes the static head of every list of pipes. |
; The hardware requires 32-bytes alignment of this structure. |
; All instances of this structure are located sequentially in ehci_controller, |
; ehci_controller is page-aligned, so it is sufficient to make this structure |
; 32-bytes aligned and verify that the first instance is 32-bytes aligned |
; inside ehci_controller. |
; The hardware also requires that 44h bytes (size of 64-bit Queue Head |
; Descriptor) starting at the beginning of this structure must not cross page |
; boundary. If not, most hardware still behaves correctly (in fact, the last |
; dword can have any value and this structure is never written), but on some |
; hardware some things just break in mysterious ways. |
struct ehci_static_ep |
; Hardware fields are the same as in ehci_pipe. |
; Only NextQH and Overlay.Token are actually used. |
; NB: some emulators ignore Token.Halted bit (probably assuming that it is set |
; only when device fails and emulation never fails) and always follow |
; [Alternate]NextTD when they see that OverlayToken.Active bit is zero; |
; so it is important to also set [Alternate]NextTD to 1. |
NextQH dd ? |
Token dd ? |
Flags dd ? |
HeadTD dd ? |
NextTD dd ? |
AlternateNextTD dd ? |
OverlayToken dd ? |
NextList dd ? |
SoftwarePart rd sizeof.usb_static_ep/4 |
Bandwidths rw 8 |
dd ? |
ends |
if sizeof.ehci_static_ep mod 32 |
.err ehci_static_ep must be 32-bytes aligned |
end if |
if ehci_static_ep.OverlayToken <> ehci_pipe.Overlay.Token |
.err ehci_static_ep.OverlayToken misplaced |
end if |
; EHCI-specific part of controller data. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 4096 bytes and corresponds to |
; the Periodic Frame List from the EHCI specification. |
; * The hardware requires page-alignment of the hardware part, so |
; the entire descriptor must be page-aligned. |
; This structure is allocated with kernel_alloc (see usb_init_controller), |
; this gives page-aligned data. |
; * The controller is described by both ehci_controller and usb_controller |
; structures, for each controller there is one ehci_controller and one |
; usb_controller structure. These structures are located sequentially |
; in the memory: beginning from some page start, there is ehci_controller |
; structure - this enforces hardware alignment requirements - and then |
; usb_controller structure. |
; * The code keeps pointer to usb_controller structure. The ehci_controller |
; structure is addressed as [ptr + ehci_controller.field - sizeof.ehci_controller]. |
struct ehci_controller |
; ------------------------------ hardware fields ------------------------------ |
FrameList rd 1024 |
; Entry n corresponds to the head of the frame list to be executed in |
; the frames n,n+1024,n+2048,n+3072,... |
; The first bit of each entry is Terminate bit, 1 = the frame is empty. |
; Bits 1-2 are Type field, one of EHCI_TYPE_* constants. |
; Bits 3-4 must be zero. |
; With masked 5 lower bits, the entry is a physical address of the first QH/TD |
; to be executed. |
; ------------------------------ software fields ------------------------------ |
; Every list has the static head, which is an always halted QH. |
; The following fields are static heads, one per list: |
; 32+16+8+4+2+1 = 63 for Periodic lists, 1 for Control list and 1 for Bulk list. |
IntEDs ehci_static_ep |
rb 62 * sizeof.ehci_static_ep |
; Beware. |
; Two following strings ensure that 44h bytes at any static head |
; do not cross page boundary. Without that, the code "works on my machine"... |
; but fails on some hardware in seemingly unrelated ways. |
; One hardware TD (without any software fields) fit in the rest of the page. |
ehci_controller.ControlDelta = 2000h - (ehci_controller.IntEDs + 63 * sizeof.ehci_static_ep) |
StopQueueTD ehci_hardware_td |
; Used as AlternateNextTD for transfers when short packet is considered |
; as an error; short packet must stop the queue in this case, not advance |
; to the next transfer. |
rb ehci_controller.ControlDelta - sizeof.ehci_hardware_td |
; Padding for page-alignment. |
ControlED ehci_static_ep |
BulkED ehci_static_ep |
MMIOBase1 dd ? |
; Virtual address of memory-mapped area with part 1 of EHCI registers EhciXxxReg. |
MMIOBase2 dd ? |
; Pointer inside memory-mapped area MMIOBase1; points to part 2 of EHCI registers. |
StructuralParams dd ? |
; Copy of EhciStructParamsReg value. |
CapabilityParams dd ? |
; Copy of EhciCapParamsReg value. |
DeferredActions dd ? |
; Bitmask of events from EhciStatusReg which were observed by the IRQ handler |
; and needs to be processed in the IRQ thread. |
PortRoutes rb 16 |
; Companion port route description. |
; Each byte describes routing of one port, value = PCI function. |
; This field must be the last one: |
; UHCI/OHCI code uses this field without knowing the entire structure. |
ends |
if ehci_controller.IntEDs mod 32 |
.err Static endpoint descriptors must be 32-bytes aligned inside ehci_controller |
end if |
; Description of #HCI-specific data and functions for |
; controller-independent code. |
; Implements the structure usb_hardware_func from hccommon.inc for EHCI. |
iglobal |
align 4 |
ehci_hardware_func: |
dd USBHC_VERSION |
dd 'EHCI' |
dd sizeof.ehci_controller |
dd ehci_kickoff_bios |
dd ehci_init |
dd ehci_process_deferred |
dd ehci_set_device_address |
dd ehci_get_device_address |
dd ehci_port_disable |
dd ehci_new_port.reset |
dd ehci_set_endpoint_packet_size |
dd ehci_alloc_pipe |
dd ehci_free_pipe |
dd ehci_init_pipe |
dd ehci_unlink_pipe |
dd ehci_alloc_td |
dd ehci_free_td |
dd ehci_alloc_transfer |
dd ehci_insert_transfer |
dd ehci_new_device |
ehci_name db 'EHCI',0 |
endg |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Called once when driver is loading and once at shutdown. |
; When loading, must initialize itself, register itself in the system |
; and return eax = value obtained when registering. |
proc start |
virtual at esp |
dd ? ; return address |
.reason dd ? ; DRV_ENTRY or DRV_EXIT |
.cmdline dd ? ; normally NULL |
end virtual |
cmp [.reason], DRV_ENTRY |
jnz .nothing |
mov ecx, ehci_ep_mutex |
invoke MutexInit |
mov ecx, ehci_gtd_mutex |
invoke MutexInit |
push esi edi |
mov esi, [USBHCFunc] |
mov edi, usbhc_api |
movi ecx, sizeof.usbhc_func/4 |
rep movsd |
pop edi esi |
invoke RegUSBDriver, ehci_name, 0, ehci_hardware_func |
.nothing: |
ret |
endp |
; Controller-specific initialization function. |
; Called from usb_init_controller. Initializes the hardware and |
; EHCI-specific parts of software structures. |
; eax = pointer to ehci_controller to be initialized |
; [ebp-4] = pcidevice |
proc ehci_init |
; inherit some variables from the parent (usb_init_controller) |
.devfn equ ebp - 4 |
.bus equ ebp - 3 |
; 1. Store pointer to ehci_controller for further use. |
push eax |
mov edi, eax |
mov esi, eax |
; 2. Initialize ehci_controller.FrameList. |
; Note that FrameList is located in the beginning of ehci_controller, |
; so esi and edi now point to ehci_controller.FrameList. |
; First 32 entries of FrameList contain physical addresses |
; of first 32 Periodic static heads, further entries duplicate these. |
; See the description of structures for full info. |
; 2a. Get physical address of first static head. |
; Note that 1) it is located in the beginning of a page |
; and 2) first 32 static heads fit in the same page, |
; so one call to get_phys_addr without correction of lower 12 bits |
; is sufficient. |
if (ehci_controller.IntEDs / 0x1000) <> ((ehci_controller.IntEDs + 32 * sizeof.ehci_static_ep) / 0x1000) |
.err assertion failed |
end if |
if (ehci_controller.IntEDs mod 0x1000) <> 0 |
.err assertion failed |
end if |
add eax, ehci_controller.IntEDs |
call [GetPhysAddr] |
; 2b. Fill first 32 entries. |
inc eax |
inc eax ; set Type to EHCI_TYPE_QH |
movi ecx, 32 |
mov edx, ecx |
@@: |
stosd |
add eax, sizeof.ehci_static_ep |
loop @b |
; 2c. Fill the rest entries. |
mov ecx, 1024 - 32 |
rep movsd |
; 3. Initialize static heads ehci_controller.*ED. |
; Use the loop over groups: first group consists of first 32 Periodic |
; descriptors, next group consists of next 16 Periodic descriptors, |
; ..., last group consists of the last Periodic descriptor. |
; 3a. Prepare for the loop. |
; make esi point to the second group, other registers are already set. |
add esi, 32*4 + 32*sizeof.ehci_static_ep |
; 3b. Loop over groups. On every iteration: |
; edx = size of group, edi = pointer to the current group, |
; esi = pointer to the next group. |
.init_static_eds: |
; 3c. Get the size of next group. |
shr edx, 1 |
; 3d. Exit the loop if there is no next group. |
jz .init_static_eds_done |
; 3e. Initialize the first half of the current group. |
; Advance edi to the second half. |
push esi |
call ehci_init_static_ep_group |
pop esi |
; 3f. Initialize the second half of the current group |
; with the same values. |
; Advance edi to the next group, esi/eax to the next of the next group. |
call ehci_init_static_ep_group |
jmp .init_static_eds |
.init_static_eds_done: |
; 3g. Initialize the last static head. |
xor esi, esi |
call ehci_init_static_endpoint |
; While we are here, initialize StopQueueTD. |
if (ehci_controller.StopQueueTD <> ehci_controller.IntEDs + 63 * sizeof.ehci_static_ep) |
.err assertion failed |
end if |
inc [edi+ehci_hardware_td.NextTD] ; 0 -> 1 |
inc [edi+ehci_hardware_td.AlternateNextTD] ; 0 -> 1 |
; leave other fields as zero, including Active bit |
; 3i. Initialize the head of Control list. |
add edi, ehci_controller.ControlDelta |
lea esi, [edi+sizeof.ehci_static_ep] |
call ehci_init_static_endpoint |
or byte [edi-sizeof.ehci_static_ep+ehci_static_ep.Token+1], 80h |
; 3j. Initialize the head of Bulk list. |
sub esi, sizeof.ehci_static_ep |
call ehci_init_static_endpoint |
; 4. Create a virtual memory area to talk with the controller. |
; 4a. Enable memory & bus master access. |
invoke PciRead16, dword [.bus], dword [.devfn], 4 |
or al, 6 |
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax |
; 4b. Read memory base address. |
invoke PciRead32, dword [.bus], dword [.devfn], 10h |
; DEBUGF 1,'K : phys MMIO %x\n',eax |
and al, not 0Fh |
; 4c. Create mapping for physical memory. 200h bytes are always sufficient. |
invoke MapIoMem, eax, 200h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .fail |
; DEBUGF 1,'K : MMIO %x\n',eax |
if ehci_controller.MMIOBase1 <> ehci_controller.BulkED + sizeof.ehci_static_ep |
.err assertion failed |
end if |
stosd ; fill ehci_controller.MMIOBase1 |
; 5. Read basic parameters of the controller. |
; 5a. Structural parameters. |
mov ebx, [eax+EhciStructParamsReg] |
; 5b. Port routing rules. |
; If bit 7 in HCSPARAMS is set, read and unpack EhciPortRouteReg. |
; Otherwise, bits 11:8 are N_PCC = number of ports per companion, |
; bits 15:12 are number of companions, maybe zero, |
; first N_PCC ports are routed to the first companion and so on. |
xor esi, esi |
test bl, bl |
js .read_routes |
test bh, 0x0F |
jz .no_companions |
test bh, 0xF0 |
jz .no_companions |
xor edx, edx |
.fill_routes: |
movzx ecx, bh |
and ecx, 15 |
@@: |
mov byte [edi+esi+ehci_controller.PortRoutes-(ehci_controller.MMIOBase1+4)], dl |
inc esi |
cmp esi, 16 |
jz .routes_filled |
dec ecx |
jnz @b |
movzx ecx, bh |
shr ecx, 4 |
inc edx |
cmp edx, ecx |
jb .fill_routes |
.no_companions: |
mov byte [edi+esi+ehci_controller.PortRoutes-(ehci_controller.MMIOBase1+4)], 0xFF |
inc esi |
cmp esi, 16 |
jnz .no_companions |
jmp .routes_filled |
.read_routes: |
rept 2 counter |
{ |
mov ecx, [eax+EhciPortRouteReg+(counter-1)*4] |
@@: |
mov edx, ecx |
shr ecx, 4 |
and edx, 15 |
mov byte [edi+esi+ehci_controller.PortRoutes-(ehci_controller.MMIOBase1+4)], dl |
inc esi |
cmp esi, 8*counter |
jnz @b |
} |
.routes_filled: |
; DEBUGF 1,'K : EhciPortRouteReg: %x %x\n',[eax+EhciPortRouteReg],[eax+EhciPortRouteReg+4] |
; DEBUGF 1,'K : routes:\nK : ' |
;rept 8 counter |
;{ |
; DEBUGF 1,' %x',[edi+ehci_controller.PortRoutes-(ehci_controller.MMIOBase1+4)+counter-1]:2 |
;} |
; DEBUGF 1,'\nK : ' |
;rept 8 counter |
;{ |
; DEBUGF 1,' %x',[edi+ehci_controller.PortRoutes+8-(ehci_controller.MMIOBase1+4)+counter-1]:2 |
;} |
; DEBUGF 1,'\n' |
movzx ecx, byte [eax+EhciCapLengthReg] |
mov edx, [eax+EhciCapParamsReg] |
add eax, ecx |
if ehci_controller.MMIOBase2 <> ehci_controller.MMIOBase1 + 4 |
.err assertion failed |
end if |
stosd ; fill ehci_controller.MMIOBase2 |
if ehci_controller.StructuralParams <> ehci_controller.MMIOBase2 + 4 |
.err assertion failed |
end if |
if ehci_controller.CapabilityParams <> ehci_controller.StructuralParams + 4 |
.err assertion failed |
end if |
mov [edi], ebx ; fill ehci_controller.StructuralParams |
mov [edi+4], edx ; fill ehci_controller.CapabilityParams |
DEBUGF 1,'K : HCSPARAMS=%x, HCCPARAMS=%x\n',ebx,edx |
and ebx, 15 |
mov [edi+usb_controller.NumPorts+sizeof.ehci_controller-ehci_controller.StructuralParams], ebx |
mov edi, eax |
; now edi = MMIOBase2 |
; 6. Transfer the controller to a known state. |
; 6b. Stop the controller if it is running. |
movi ecx, 10 |
test dword [edi+EhciStatusReg], 1 shl 12 |
jnz .stopped |
and dword [edi+EhciCommandReg], not 1 |
@@: |
movi esi, 1 |
invoke Sleep |
test dword [edi+EhciStatusReg], 1 shl 12 |
jnz .stopped |
loop @b |
dbgstr 'Failed to stop EHCI controller' |
jmp .fail_unmap |
.stopped: |
; 6c. Reset the controller. Wait up to 50 ms checking status every 1 ms. |
or dword [edi+EhciCommandReg], 2 |
movi ecx, 50 |
@@: |
movi esi, 1 |
invoke Sleep |
test dword [edi+EhciCommandReg], 2 |
jz .reset_ok |
loop @b |
dbgstr 'Failed to reset EHCI controller' |
jmp .fail_unmap |
.reset_ok: |
; 7. Configure the controller. |
pop esi ; restore the pointer saved at step 1 |
add esi, sizeof.ehci_controller |
; 7a. If the controller is 64-bit, say to it that all structures are located |
; in first 4G. |
test byte [esi+ehci_controller.CapabilityParams-sizeof.ehci_controller], 1 |
jz @f |
mov dword [edi+EhciCtrlDataSegReg], 0 |
@@: |
; 7b. Hook interrupt and enable appropriate interrupt sources. |
invoke PciRead8, dword [.bus], dword [.devfn], 3Ch |
; al = IRQ |
; DEBUGF 1,'K : attaching to IRQ %x\n',al |
movzx eax, al |
invoke AttachIntHandler, eax, ehci_irq, esi |
; mov dword [edi+EhciStatusReg], 111111b ; clear status |
; disable Frame List Rollover interrupt, enable all other sources |
mov dword [edi+EhciInterruptReg], 110111b |
; 7c. Inform the controller of the address of periodic lists head. |
lea eax, [esi-sizeof.ehci_controller] |
invoke GetPhysAddr |
mov dword [edi+EhciPeriodicListReg], eax |
; 7d. Inform the controller of the address of asynchronous lists head. |
lea eax, [esi+ehci_controller.ControlED-sizeof.ehci_controller] |
invoke GetPhysAddr |
mov dword [edi+EhciAsyncListReg], eax |
; 7e. Configure operational details and run the controller. |
mov dword [edi+EhciCommandReg], \ |
(1 shl 16) + \ ; interrupt threshold = 1 microframe = 0.125ms |
(0 shl 11) + \ ; disable Async Park Mode |
(0 shl 8) + \ ; zero Async Park Mode Count |
(1 shl 5) + \ ; Async Schedule Enable |
(1 shl 4) + \ ; Periodic Schedule Enable |
(0 shl 2) + \ ; 1024 elements in FrameList |
1 ; Run |
; 7f. Route all ports to this controller, not companion controllers. |
mov dword [edi+EhciConfigFlagReg], 1 |
DEBUGF 1,'K : EHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] |
; 8. Apply port power, if needed, and disable all ports. |
xor ecx, ecx |
@@: |
mov dword [edi+EhciPortsReg+ecx*4], 1000h ; Port Power enabled, all other bits disabled |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb @b |
test byte [esi+ehci_controller.StructuralParams-sizeof.ehci_controller], 10h |
jz @f |
push esi |
movi esi, 20 |
invoke Sleep |
pop esi |
@@: |
; 9. Return pointer to usb_controller. |
xchg eax, esi |
ret |
; On error, pop the pointer saved at step 1 and return zero. |
; Note that the main code branch restores the stack at step 7 and never fails |
; after step 7. |
.fail_unmap: |
pop eax |
push eax |
invoke FreeKernelSpace, [eax+ehci_controller.MMIOBase1] |
.fail: |
pop ecx |
xor eax, eax |
ret |
endp |
; Helper procedure for step 3 of ehci_init, see comments there. |
; Initializes the static head of one list. |
; esi = pointer to the "next" list, edi = pointer to head to initialize. |
; Advances edi to the next head, keeps esi. |
proc ehci_init_static_endpoint |
xor eax, eax |
inc eax ; set Terminate bit |
mov [edi+ehci_static_ep.NextTD], eax |
mov [edi+ehci_static_ep.AlternateNextTD], eax |
test esi, esi |
jz @f |
mov eax, esi |
invoke GetPhysAddr |
inc eax |
inc eax ; set Type to EHCI_TYPE_QH |
@@: |
mov [edi+ehci_static_ep.NextQH], eax |
mov [edi+ehci_static_ep.NextList], esi |
mov byte [edi+ehci_static_ep.OverlayToken], 1 shl 6 ; halted |
add edi, ehci_static_ep.SoftwarePart |
mov eax, [USBHCFunc] |
call [eax+usbhc_func.usb_init_static_endpoint] |
add edi, sizeof.ehci_static_ep - ehci_static_ep.SoftwarePart |
ret |
endp |
; Helper procedure for step 3 of ehci_init, see comments there. |
; Initializes one half of group of static heads. |
; edx = size of the next group = half of size of the group, |
; edi = pointer to the group, esi = pointer to the next group. |
; Advances esi, edi to next group, keeps edx. |
proc ehci_init_static_ep_group |
push edx |
@@: |
call ehci_init_static_endpoint |
add esi, sizeof.ehci_static_ep |
dec edx |
jnz @b |
pop edx |
ret |
endp |
; Controller-specific pre-initialization function: take ownership from BIOS. |
; Some BIOSes, although not all of them, use USB controllers themselves |
; to support USB flash drives. In this case, |
; we must notify the BIOS that we don't need that emulation and know how to |
; deal with USB devices. |
proc ehci_kickoff_bios |
; 1. Get the physical address of MMIO registers. |
invoke PciRead32, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 10h |
and al, not 0Fh |
; 2. Create mapping for physical memory. 200h bytes are always sufficient. |
invoke MapIoMem, eax, 200h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .nothing |
push eax ; push argument for step 8 |
; 3. Some BIOSes enable controller interrupts as a result of giving |
; controller away. At this point the system knows nothing about how to serve |
; EHCI interrupts, so such an interrupt will send the system into an infinite |
; loop handling the same IRQ again and again. Thus, we need to block EHCI |
; interrupts. We can't do this at the controller level until step 5, |
; because the controller is currently owned by BIOS, so we block all hardware |
; interrupts on this processor until step 5. |
pushf |
cli |
; 4. Take the ownership over the controller. |
; 4a. Locate take-ownership capability in the PCI configuration space. |
; Limit the loop with 100h iterations; since the entire configuration space is |
; 100h bytes long, hitting this number of iterations means that something is |
; corrupted. |
; Use a value from MMIO as a starting point. |
mov edx, [eax+EhciCapParamsReg] |
movzx edi, byte [eax+EhciCapLengthReg] |
add edi, eax |
push 0 |
mov bl, dh ; get Extended Capabilities Pointer |
test bl, bl |
jz .has_ownership2 |
cmp bl, 40h |
jb .no_capability |
.look_bios_handoff: |
test bl, 3 |
jnz .no_capability |
; In each iteration, read the current dword, |
invoke PciRead32, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx |
; check, whether the capability ID is take-ownership ID = 1, |
cmp al, 1 |
jz .found_bios_handoff |
; if not, advance to next-capability link and continue loop. |
dec byte [esp] |
jz .no_capability |
mov bl, ah |
cmp bl, 40h |
jae .look_bios_handoff |
.no_capability: |
dbgstr 'warning: cannot locate take-ownership capability' |
jmp .has_ownership2 |
.found_bios_handoff: |
; 4b. Check whether BIOS has ownership. |
; Some BIOSes release ownership before loading OS, but forget to unwatch for |
; change-ownership requests; they cannot handle ownership request, so |
; such a request sends the system into infinite loop of handling the same SMI |
; over and over. Avoid this. |
inc ebx |
inc ebx |
test eax, 0x10000 |
jz .has_ownership |
; 4c. Request ownership. |
inc ebx |
invoke PciWrite8, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx, 1 |
; 4d. Some BIOSes set ownership flag, but forget to watch for change-ownership |
; requests; if so, there is no sense in waiting. |
inc ebx |
invoke PciRead32, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx |
dec ebx |
dec ebx |
test ah, 20h |
jz .force_ownership |
; 4e. Wait for result no more than 1 s, checking for status every 1 ms. |
; If successful, go to 5. |
mov dword [esp], 1000 |
@@: |
invoke PciRead8, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx |
test al, 1 |
jz .has_ownership |
push esi |
movi esi, 1 |
invoke Sleep |
pop esi |
dec dword [esp] |
jnz @b |
dbgstr 'warning: taking EHCI ownership from BIOS timeout' |
.force_ownership: |
; 4f. BIOS has not responded within the timeout. |
; Let's just clear BIOS ownership flag and hope that everything will be ok. |
invoke PciWrite8, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx, 0 |
.has_ownership: |
; 5. Just in case clear all SMI event sources except change-ownership. |
dbgstr 'has_ownership' |
inc ebx |
inc ebx |
invoke PciRead16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx |
and ax, 2000h |
invoke PciWrite16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx, eax |
.has_ownership2: |
pop ecx |
; 6. Disable all controller interrupts until the system will be ready to |
; process them. |
mov dword [edi+EhciInterruptReg], 0 |
; 7. Now we can unblock interrupts in the processor. |
popf |
; 8. Release memory mapping created in step 2 and return. |
invoke FreeKernelSpace |
.nothing: |
ret |
endp |
; IRQ handler for EHCI controllers. |
ehci_irq.noint: |
spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock] |
; Not our interrupt: restore registers and return zero. |
xor eax, eax |
pop edi esi ebx |
ret |
proc ehci_irq |
push ebx esi edi ; save registers to be cdecl |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.controller dd ? |
end virtual |
; 1. ebx will hold whether some deferred processing is needed, |
; that cannot be done from the interrupt handler. Initialize to zero. |
xor ebx, ebx |
; 2. Get the mask of events which should be processed. |
mov esi, [.controller] |
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
spin_lock_irqsave [esi+usb_controller.WaitSpinlock] |
mov eax, [edi+EhciStatusReg] |
; 3. Check whether that interrupt has been generated by our controller. |
; (One IRQ can be shared by several devices.) |
and eax, [edi+EhciInterruptReg] |
jz .noint |
; 4. Clear the events we know of. |
; Note that this should be done before processing of events: |
; new events could arise while we are processing those, this way we won't lose |
; them (the controller would generate another interrupt after completion |
; of this one). |
; DEBUGF 1,'K : EHCI interrupt: status = %x\n',eax |
mov [edi+EhciStatusReg], eax |
; 5. Sanity check. |
test al, 10h |
jz @f |
DEBUGF 1,'K : something terrible happened with EHCI %x (%x)\n',esi,al |
@@: |
; We can't do too much from an interrupt handler. Inform the processing thread |
; that it should perform appropriate actions. |
or [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], eax |
spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock] |
inc ebx |
invoke usbhc_api.usb_wakeup_if_needed |
; 6. Interrupt processed; return non-zero. |
mov al, 1 |
pop edi esi ebx ; restore used registers to be cdecl |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; and stores USB device address in the ehci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
proc ehci_set_device_address |
mov byte [ebx+ehci_pipe.Token-sizeof.ehci_pipe], cl |
jmp [usbhc_api.usb_subscribe_control] |
endp |
; This procedure returns USB device address from the ehci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = endpoint address |
proc ehci_get_device_address |
mov eax, [ebx+ehci_pipe.Token-sizeof.ehci_pipe] |
and eax, 7Fh |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; if the device does not accept SET_ADDRESS command and needs |
; to be disabled at the port level. |
; in: esi -> usb_controller, ecx = port (zero-based) |
proc ehci_port_disable |
mov eax, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
and dword [eax+EhciPortsReg+ecx*4], not (4 or 2Ah) |
ret |
endp |
; This procedure is called from usb_get_descr8_callback when |
; the packet size for zero endpoint becomes known and |
; stores the packet size in ehci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
proc ehci_set_endpoint_packet_size |
mov eax, [ebx+ehci_pipe.Token-sizeof.ehci_pipe] |
and eax, not (0x7FF shl 16) |
shl ecx, 16 |
or eax, ecx |
mov [ebx+ehci_pipe.Token-sizeof.ehci_pipe], eax |
; Wait until hardware cache is evicted. |
jmp [usbhc_api.usb_subscribe_control] |
endp |
uglobal |
align 4 |
; Data for memory allocator, see memory.inc. |
ehci_ep_first_page dd ? |
ehci_ep_mutex MUTEX |
ehci_gtd_first_page dd ? |
ehci_gtd_mutex MUTEX |
endg |
; This procedure allocates memory for pipe. |
; Both hardware+software parts must be allocated, returns pointer to usb_pipe |
; (software part). |
proc ehci_alloc_pipe |
push ebx |
mov ebx, ehci_ep_mutex |
invoke usbhc_api.usb_allocate_common, (sizeof.ehci_pipe + sizeof.usb_pipe + 1Fh) and not 1Fh |
test eax, eax |
jz @f |
add eax, sizeof.ehci_pipe |
@@: |
pop ebx |
ret |
endp |
; This procedure frees memory for pipe allocated by ehci_alloc_pipe. |
; void stdcall with one argument = pointer to usb_pipe. |
proc ehci_free_pipe |
virtual at esp |
dd ? ; return address |
.ptr dd ? |
end virtual |
sub [.ptr], sizeof.ehci_pipe |
jmp [usbhc_api.usb_free_common] |
endp |
; This procedure is called from API usb_open_pipe and processes |
; the controller-specific part of this API. See docs. |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
proc ehci_init_pipe |
virtual at ebp+8 |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; 1. Zero all fields in the hardware part. |
push eax ecx |
sub edi, sizeof.ehci_pipe |
xor eax, eax |
movi ecx, sizeof.ehci_pipe/4 |
rep stosd |
pop ecx eax |
; 2. Setup PID in the first TD and make sure that the it is not active. |
xor edx, edx |
test byte [.endpoint], 80h |
setnz dh |
mov [eax+ehci_gtd.Token-sizeof.ehci_gtd], edx |
mov [eax+ehci_gtd.NextTD-sizeof.ehci_gtd], 1 |
mov [eax+ehci_gtd.AlternateNextTD-sizeof.ehci_gtd], 1 |
; 3. Store physical address of the first TD. |
sub eax, sizeof.ehci_gtd |
call [GetPhysAddr] |
mov [edi+ehci_pipe.Overlay.NextTD-sizeof.ehci_pipe], eax |
; 4. Fill ehci_pipe.Flags except for S- and C-masks. |
; Copy location from the config pipe. |
mov eax, [ecx+ehci_pipe.Flags-sizeof.ehci_pipe] |
and eax, 3FFF0000h |
; Use 1 requests per microframe for control/bulk endpoints, |
; use value from the endpoint descriptor for periodic endpoints |
movi edx, 1 |
test [.type], 1 |
jz @f |
mov edx, [.maxpacket] |
shr edx, 11 |
inc edx |
@@: |
shl edx, 30 |
or eax, edx |
mov [edi+ehci_pipe.Flags-sizeof.ehci_pipe], eax |
; 5. Fill ehci_pipe.Token. |
mov eax, [ecx+ehci_pipe.Token-sizeof.ehci_pipe] |
; copy following fields from the config pipe: |
; DeviceAddress, EndpointSpeed, ControlEndpoint if new type is control |
mov ecx, eax |
and eax, 307Fh |
and ecx, 8000000h |
or ecx, 4000h |
mov edx, [.endpoint] |
and edx, 15 |
shl edx, 8 |
or eax, edx |
mov edx, [.maxpacket] |
shl edx, 16 |
or eax, edx |
; for control endpoints, use DataToggle from TD, otherwise use DataToggle from QH |
cmp [.type], CONTROL_PIPE |
jnz @f |
or eax, ecx |
@@: |
; for control/bulk USB2 endpoints, set NakCountReload to 4 |
test eax, USB_SPEED_HS shl 12 |
jz .nonak |
cmp [.type], CONTROL_PIPE |
jz @f |
cmp [.type], BULK_PIPE |
jnz .nonak |
@@: |
or eax, 40000000h |
.nonak: |
mov [edi+ehci_pipe.Token-sizeof.ehci_pipe], eax |
; 5. Select the corresponding list and insert to the list. |
; 5a. Use Control list for control pipes, Bulk list for bulk pipes. |
lea edx, [esi+ehci_controller.ControlED.SoftwarePart-sizeof.ehci_controller] |
cmp [.type], BULK_PIPE |
jb .insert ; control pipe |
lea edx, [esi+ehci_controller.BulkED.SoftwarePart-sizeof.ehci_controller] |
jz .insert ; bulk pipe |
.interrupt_pipe: |
; 5b. For interrupt pipes, let the scheduler select the appropriate list |
; and the appropriate microframe(s) (which goes to S-mask and C-mask) |
; based on the current bandwidth distribution and the requested bandwidth. |
; There are two schedulers, one for high-speed devices, |
; another for split transactions. |
; This could fail if the requested bandwidth is not available; |
; if so, return an error. |
test word [edi+ehci_pipe.Flags-sizeof.ehci_pipe+2], 3FFFh |
jnz .interrupt_tt |
call ehci_select_hs_interrupt_list |
jmp .interrupt_common |
.interrupt_tt: |
call ehci_select_tt_interrupt_list |
.interrupt_common: |
test edx, edx |
jz .return0 |
mov word [edi+ehci_pipe.Flags-sizeof.ehci_pipe], ax |
.insert: |
mov [edi+ehci_pipe.BaseList-sizeof.ehci_pipe], edx |
; Insert to the head of the corresponding list. |
; Note: inserting to the head guarantees that the list traverse in |
; ehci_process_updated_schedule, once started, will not interact with new pipes. |
; However, we still need to ensure that links in the new pipe (edi.NextVirt) |
; are initialized before links to the new pipe (edx.NextVirt). |
; 5c. Insert in the list of virtual addresses. |
mov ecx, [edx+usb_pipe.NextVirt] |
mov [edi+usb_pipe.NextVirt], ecx |
mov [edi+usb_pipe.PrevVirt], edx |
mov [ecx+usb_pipe.PrevVirt], edi |
mov [edx+usb_pipe.NextVirt], edi |
; 5d. Insert in the hardware list: copy previous NextQH to the new pipe, |
; store the physical address of the new pipe to previous NextQH. |
mov ecx, [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart] |
mov [edi+ehci_pipe.NextQH-sizeof.ehci_pipe], ecx |
lea eax, [edi-sizeof.ehci_pipe] |
call [GetPhysAddr] |
inc eax |
inc eax |
mov [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], eax |
; 6. Return with nonzero eax. |
ret |
.return0: |
xor eax, eax |
ret |
endp |
; This function is called from ehci_process_deferred when |
; a new device was connected at least USB_CONNECT_DELAY ticks |
; and therefore is ready to be configured. |
; ecx = port, esi -> ehci_controller, edi -> EHCI MMIO |
proc ehci_new_port |
; 1. If the device operates at low-speed, just release it to a companion. |
mov eax, [edi+EhciPortsReg+ecx*4] |
DEBUGF 1,'K : EHCI %x port %d state is %x\n',esi,ecx,eax |
mov edx, eax |
and ah, 0Ch |
cmp ah, 4 |
jz .low_speed |
; 2. Devices operating at full-speed and high-speed must now have ah == 8. |
; Some broken hardware asserts both D+ and D- even after initial decoupling; |
; if so, stop initialization here, no sense in further actions. |
cmp ah, 0Ch |
jz .se1 |
; 3. If another port is resetting right now, mark this port as 'reset pending' |
; and return. |
bts [esi+usb_controller.PendingPorts], ecx |
cmp [esi+usb_controller.ResettingPort], -1 |
jnz .nothing |
btr [esi+usb_controller.PendingPorts], ecx |
; Otherwise, fall through to ohci_new_port.reset. |
; This function is called from ehci_new_port and usb_test_pending_port. |
; It starts reset signalling for the port. Note that in USB first stages |
; of configuration can not be done for several ports in parallel. |
.reset: |
push edi |
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
mov eax, [edi+EhciPortsReg+ecx*4] |
; 1. Store information about resetting hub (roothub) and port. |
and [esi+usb_controller.ResettingHub], 0 |
mov [esi+usb_controller.ResettingPort], cl |
; 2. Initiate reset signalling. |
or ah, 1 |
and al, not (4 or 2Ah) |
mov [edi+EhciPortsReg+ecx*4], eax |
; 3. Store the current time and set status to 1 = reset signalling active. |
invoke GetTimerTicks |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 1 |
; dbgstr 'high-speed or full-speed device, resetting' |
DEBUGF 1,'K : EHCI %x: port %d has HS or FS device, resetting\n',esi,ecx |
pop edi |
.nothing: |
ret |
.low_speed: |
; dbgstr 'low-speed device, releasing' |
DEBUGF 1,'K : EHCI %x: port %d has LS device, releasing\n',esi,ecx |
or dh, 20h |
and dl, not 2Ah |
mov [edi+EhciPortsReg+ecx*4], edx |
ret |
.se1: |
dbgstr 'SE1 after connect debounce. Broken hardware?' |
ret |
endp |
; This procedure is called from several places in main USB code |
; and allocates required packets for the given transfer. |
; ebx = pipe, other parameters are passed through the stack: |
; buffer,size = data to transfer |
; flags = same as in usb_open_pipe: bit 0 = allow short transfer, other bits reserved |
; td = pointer to the current end-of-queue descriptor |
; direction = |
; 0000b for normal transfers, |
; 1000b for control SETUP transfer, |
; 1101b for control OUT transfer, |
; 1110b for control IN transfer |
; returns eax = pointer to the new end-of-queue descriptor |
; (not included in the queue itself) or 0 on error |
proc ehci_alloc_transfer stdcall uses edi, \ |
buffer:dword, size:dword, flags:dword, td:dword, direction:dword |
locals |
origTD dd ? |
packetSize dd ? ; must be last variable, see usb_init_transfer |
endl |
; 1. Save original value of td: |
; it will be useful for rollback if something would fail. |
mov eax, [td] |
mov [origTD], eax |
; One transfer descriptor can describe up to 5 pages. |
; In the worst case (when the buffer is something*1000h+0FFFh) |
; this corresponds to 4001h bytes. If the requested size is |
; greater, we should split the transfer into several descriptors. |
; Boundaries to split must be multiples of endpoint transfer size |
; to avoid short packets except in the end of the transfer. |
cmp [size], 4001h |
jbe .lastpacket |
; 2. While the remaining data cannot fit in one descriptor, |
; allocate full descriptors (of maximal possible size). |
; 2a. Calculate size of one descriptor: must be a multiple of transfer size |
; and must be not greater than 4001h. |
movzx ecx, word [ebx+ehci_pipe.Token+2-sizeof.ehci_pipe] |
and ecx, (1 shl 11) - 1 |
mov eax, 4001h |
xor edx, edx |
mov edi, eax |
div ecx |
sub edi, edx |
mov [packetSize], edi |
.fullpackets: |
call ehci_alloc_packet |
test eax, eax |
jz .fail |
mov [td], eax |
add [buffer], edi |
sub [size], edi |
cmp [size], 4001h |
ja .fullpackets |
; 3. The remaining data can fit in one packet; |
; allocate the last descriptor with size = size of remaining data. |
.lastpacket: |
mov eax, [size] |
mov [packetSize], eax |
call ehci_alloc_packet |
test eax, eax |
jz .fail |
; 9. Update flags in the last packet. |
mov edx, [flags] |
mov [ecx+ehci_gtd.Flags-sizeof.ehci_gtd], edx |
; 10. Fill AlternateNextTD field in all allocated TDs. |
; If the caller says that short transfer is ok, the queue must advance to |
; the next descriptor, which is in eax. |
; Otherwise, the queue should stop, so make AlternateNextTD point to |
; always-inactive descriptor StopQueueTD. |
push eax |
test dl, 1 |
jz .disable_short |
sub eax, sizeof.ehci_gtd |
jmp @f |
.disable_short: |
mov eax, [ebx+usb_pipe.Controller] |
add eax, ehci_controller.StopQueueTD - sizeof.ehci_controller |
@@: |
call [GetPhysAddr] |
mov edx, [origTD] |
@@: |
cmp edx, [esp] |
jz @f |
mov [edx+ehci_gtd.AlternateNextTD-sizeof.ehci_gtd], eax |
mov edx, [edx+usb_gtd.NextVirt] |
jmp @b |
@@: |
pop eax |
ret |
.fail: |
mov edi, ehci_hardware_func |
mov eax, [td] |
invoke usbhc_api.usb_undo_tds, [origTD] |
xor eax, eax |
ret |
endp |
; Helper procedure for ehci_alloc_transfer. |
; Allocates and initializes one transfer descriptor. |
; ebx = pipe, other parameters are passed through the stack; |
; fills the current last descriptor and |
; returns eax = next descriptor (not filled). |
proc ehci_alloc_packet |
; inherit some variables from the parent ehci_alloc_transfer |
virtual at ebp-8 |
.origTD dd ? |
.packetSize dd ? |
rd 2 |
.buffer dd ? |
.transferSize dd ? |
.Flags dd ? |
.td dd ? |
.direction dd ? |
end virtual |
; 1. Allocate the next TD. |
call ehci_alloc_td |
test eax, eax |
jz .nothing |
; 2. Initialize controller-independent parts of both TDs. |
push eax |
invoke usbhc_api.usb_init_transfer |
pop eax |
; 3. Copy PID to the new descriptor. |
mov edx, [ecx+ehci_gtd.Token-sizeof.ehci_gtd] |
mov [eax+ehci_gtd.Token-sizeof.ehci_gtd], edx |
mov [eax+ehci_gtd.NextTD-sizeof.ehci_gtd], 1 |
mov [eax+ehci_gtd.AlternateNextTD-sizeof.ehci_gtd], 1 |
; 4. Save the returned value (next descriptor). |
push eax |
; 5. Store the physical address of the next descriptor. |
sub eax, sizeof.ehci_gtd |
call [GetPhysAddr] |
mov [ecx+ehci_gtd.NextTD-sizeof.ehci_gtd], eax |
; 6. For zero-length transfers, store zero in all fields for buffer addresses. |
; Otherwise, fill them with real values. |
xor eax, eax |
mov [ecx+ehci_gtd.Flags-sizeof.ehci_gtd], eax |
repeat 10 |
mov [ecx+ehci_gtd.BufferPointers-sizeof.ehci_gtd+(%-1)*4], eax |
end repeat |
cmp [.packetSize], eax |
jz @f |
mov eax, [.buffer] |
call [GetPhysAddr] |
mov [ecx+ehci_gtd.BufferPointers-sizeof.ehci_gtd], eax |
and eax, 0xFFF |
mov edx, [.packetSize] |
add edx, eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x1000 |
call [GetPgAddr] |
mov [ecx+ehci_gtd.BufferPointers+4-sizeof.ehci_gtd], eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x2000 |
call [GetPgAddr] |
mov [ecx+ehci_gtd.BufferPointers+8-sizeof.ehci_gtd], eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x3000 |
call [GetPgAddr] |
mov [ecx+ehci_gtd.BufferPointers+12-sizeof.ehci_gtd], eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x4000 |
call [GetPgAddr] |
mov [ecx+ehci_gtd.BufferPointers+16-sizeof.ehci_gtd], eax |
@@: |
; 7. Fill Token field: |
; set Status = 0 (inactive, ehci_insert_transfer would mark everything active); |
; keep current PID if [.direction] is zero, use two lower bits of [.direction] |
; otherwise shifted as (0|1|2) -> (2|0|1); |
; set error counter to 3; |
; set current page to 0; |
; do not interrupt on complete (ehci_insert_transfer sets this bit where needed); |
; set DataToggle to bit 2 of [.direction]. |
mov eax, [ecx+ehci_gtd.Token-sizeof.ehci_gtd] |
and eax, 300h ; keep PID code |
mov edx, [.direction] |
test edx, edx |
jz .haspid |
and edx, 3 |
dec edx |
jns @f |
add edx, 3 |
@@: |
mov ah, dl |
mov edx, [.direction] |
and edx, not 3 |
shl edx, 29 |
or eax, edx |
.haspid: |
or eax, 0C00h |
mov edx, [.packetSize] |
shl edx, 16 |
or eax, edx |
mov [ecx+ehci_gtd.Token-sizeof.ehci_gtd], eax |
; 4. Restore the returned value saved in step 2. |
pop eax |
.nothing: |
ret |
endp |
; This procedure is called from several places in main USB code |
; and activates the transfer which was previously allocated by |
; ehci_alloc_transfer. |
; ecx -> last descriptor for the transfer, ebx -> usb_pipe |
proc ehci_insert_transfer |
or byte [ecx+ehci_gtd.Token+1-sizeof.ehci_gtd], 80h ; set IOC bit |
mov eax, [esp+4] |
.activate: |
or byte [eax+ehci_gtd.Token-sizeof.ehci_gtd], 80h ; set Active bit |
cmp eax, ecx |
mov eax, [eax+usb_gtd.NextVirt] |
jnz .activate |
ret |
endp |
; This function is called from ehci_process_deferred when |
; reset signalling for a new device needs to be finished. |
proc ehci_port_reset_done |
movzx ecx, [esi+usb_controller.ResettingPort] |
and dword [edi+EhciPortsReg+ecx*4], not 12Ah |
invoke GetTimerTicks |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 2 |
; DEBUGF 1,'K : EHCI %x: reset port %d done\n',esi,ecx |
ret |
endp |
; This function is called from ehci_process_deferred when |
; a new device has been reset, recovered after reset and needs to be configured. |
proc ehci_port_init |
; 1. Get the status and set it to zero. |
; If reset has been failed (device disconnected during reset), |
; continue to next device (if there is one). |
xor eax, eax |
xchg al, [esi+usb_controller.ResettingStatus] |
test al, al |
jns @f |
jmp [usbhc_api.usb_test_pending_port] |
@@: |
; 2. Get the port status. High-speed devices should be now enabled, |
; full-speed devices are left disabled; |
; if the port is disabled, release it to a companion and continue to |
; next device (if there is one). |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov eax, [edi+EhciPortsReg+ecx*4] |
DEBUGF 1,'K : EHCI %x status of port %d is %x\n',esi,ecx,eax |
test al, 4 |
jnz @f |
; DEBUGF 1,'K : USB port disabled after reset, status = %x\n',eax |
dbgstr 'releasing to companion' |
or ah, 20h |
mov [edi+EhciPortsReg+ecx*4], eax |
jmp [usbhc_api.usb_test_pending_port] |
@@: |
; 3. Call the worker procedure to notify the protocol layer |
; about new EHCI device. It is high-speed. |
movi eax, USB_SPEED_HS |
call ehci_new_device |
test eax, eax |
jnz .nothing |
; 4. If something at the protocol layer has failed |
; (no memory, no bus address), disable the port and stop the initialization. |
.disable_exit: |
and dword [edi+EhciPortsReg+ecx*4], not (4 or 2Ah) |
jmp [usbhc_api.usb_test_pending_port] |
.nothing: |
ret |
endp |
; This procedure is called from ehci_port_init and from hub support code |
; when a new device is connected and has been reset. |
; It calls usb_new_device at the protocol layer with correct parameters. |
; in: esi -> usb_controller, eax = speed. |
proc ehci_new_device |
push ebx ecx ; save used registers (ecx is important for ehci_port_init) |
; 1. Store the speed for the protocol layer. |
mov [esi+usb_controller.ResettingSpeed], al |
; 2. Shift speed bits to the proper place in ehci_pipe.Token. |
shl eax, 12 |
; 3. For high-speed devices, go to step 5 with edx = 0. |
xor edx, edx |
cmp ah, USB_SPEED_HS shl (12-8) |
jz .common |
; 4. For low-speed and full-speed devices, fill address:port |
; of the last high-speed hub (the closest to the device hub) |
; for split transactions, and set ControlEndpoint bit in eax; |
; ehci_init_pipe assumes that the parent pipe is a control pipe. |
push eax |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov edx, [esi+usb_controller.ResettingHub] |
invoke usbhc_api.usb_get_tt |
inc ecx |
mov edx, [edx+ehci_pipe.Token-sizeof.ehci_pipe] |
shl ecx, 23 |
and edx, 7Fh |
shl edx, 16 |
or edx, ecx ; ehci_pipe.Flags |
pop eax |
or eax, 1 shl 27 ; ehci_pipe.Token |
.common: |
; 5. Create pseudo-pipe in the stack. |
; See ehci_init_pipe: only .Controller, .Token, .Flags fields are used. |
push esi ; usb_pipe.Controller |
mov ecx, esp |
sub esp, sizeof.ehci_pipe - ehci_pipe.Flags - 4 |
push edx ; ehci_pipe.Flags |
push eax ; ehci_pipe.Token |
; 6. Notify the protocol layer. |
invoke usbhc_api.usb_new_device |
; 7. Cleanup the stack after step 5 and return. |
add esp, sizeof.ehci_pipe - ehci_pipe.Flags + 8 |
pop ecx ebx ; restore used registers |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; processes regular actions and those actions which can't be safely done |
; from interrupt handler. |
; Returns maximal time delta before the next call. |
proc ehci_process_deferred |
push ebx edi ; save used registers to be stdcall |
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
; 1. Get the mask of events to process. |
xor eax, eax |
xchg eax, [esi+ehci_controller.DeferredActions-sizeof.ehci_controller] |
push eax |
; 2. Initialize the return value. |
push -1 |
; Handle roothub events. |
; 3a. Test whether there are such events. |
test al, 4 |
jz .skip_roothub |
; Status of some port has changed. Loop over all ports. |
; 3b. Prepare for the loop: start from port 0. |
xor ecx, ecx |
.portloop: |
; 3c. Get the port status and changes of it. |
; If there are no changes, just continue to the next port. |
mov eax, [edi+EhciPortsReg+ecx*4] |
test al, 2Ah |
jz .nextport |
; 3d. Clear change bits and read the status again. |
; (It is possible, although quite unlikely, that some event occurs between |
; the first read and the clearing, invalidating the old status. If an event |
; occurs after the clearing, we will not miss it, looking in the next scan. |
mov [edi+EhciPortsReg+ecx*4], eax |
mov ebx, eax |
mov eax, [edi+EhciPortsReg+ecx*4] |
DEBUGF 1,'K : EHCI %x: status of port %d changed to %x\n',esi,ecx,ebx |
; 3e. Handle overcurrent. |
; Note: that needs work. |
test bl, 20h ; overcurrent change |
jz .noovercurrent |
test al, 10h ; overcurrent active |
jz .noovercurrent |
DEBUGF 1,'K : overcurrent at port %d\n',ecx |
.noovercurrent: |
; 3f. Handle changing of connection status. |
test bl, 2 |
jz .nocsc |
; There was a connect or disconnect event at this port. |
; 3g. Disconnect the old device on this port, if any. |
; If the port was resetting, indicate fail; later stages will process it. |
; Ignore connect event immediately after resetting. |
cmp [esi+usb_controller.ResettingHub], 0 |
jnz .csc.noreset |
cmp cl, [esi+usb_controller.ResettingPort] |
jnz .csc.noreset |
cmp [esi+usb_controller.ResettingStatus], 2 |
jnz @f |
test al, 1 |
jnz .nextport |
@@: |
mov [esi+usb_controller.ResettingStatus], -1 |
.csc.noreset: |
bts [esi+usb_controller.NewDisconnected], ecx |
; 3h. Change connected status. For the connection event, also store |
; the connection time; any further processing is permitted only after |
; USB_CONNECT_DELAY ticks. |
test al, 1 |
jz .disconnect |
invoke GetTimerTicks |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
bts [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.disconnect: |
btr [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.nocsc: |
; 3i. Handle port disabling. |
; Note: that needs work. |
test al, 8 |
jz @f |
test al, 4 |
jz @f |
DEBUGF 1,'K : port %d disabled\n',ecx |
@@: |
; 3j. Continue the loop for the next port. |
.nextport: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_roothub: |
; 4. Process disconnect events. This should be done after step 3 |
; (which includes the first stage of disconnect processing). |
invoke usbhc_api.usb_disconnect_stage2 |
; 5. Check for previously connected devices. |
; If there is a connected device which was connected less than |
; USB_CONNECT_DELAY ticks ago, plan to wake up when the delay will be over. |
; Otherwise, call ehci_new_port. |
; This should be done after step 3. |
xor ecx, ecx |
cmp [esi+usb_controller.NewConnected], ecx |
jz .skip_newconnected |
.portloop2: |
bt [esi+usb_controller.NewConnected], ecx |
jnc .noconnect |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ConnectedTime+ecx*4] |
sub eax, USB_CONNECT_DELAY |
jge .connected |
neg eax |
cmp [esp], eax |
jb .nextport2 |
mov [esp], eax |
jmp .nextport2 |
.connected: |
btr [esi+usb_controller.NewConnected], ecx |
call ehci_new_port |
jmp .portloop2 |
.noconnect: |
.nextport2: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop2 |
.skip_newconnected: |
; 6. Process wait lists. |
; 6a. Periodic endpoints. |
; If a request is pending >8 microframes, satisfy it. |
; If a request is pending <=8 microframes, schedule next wakeup in 0.01s. |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
cmp eax, [esi+usb_controller.ReadyPipeHeadPeriodic] |
jz .noperiodic |
mov edx, [edi+EhciFrameIndexReg] |
sub edx, [esi+usb_controller.StartWaitFrame] |
and edx, 0x3FFF |
cmp edx, 8 |
jbe @f |
mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax |
jmp .noperiodic |
@@: |
pop eax |
push 1 ; wakeup in 0.01 sec for next test |
.noperiodic: |
; 6b. Asynchronous endpoints. |
; Satisfy a request when InterruptOnAsyncAdvance fired. |
test byte [esp+4], 20h |
jz @f |
; dbgstr 'async advance int' |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
@@: |
; Some hardware in some (rarely) conditions set the status bit, |
; but just does not generate the corresponding interrupt. |
; Force checking the status here. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
cmp [esi+usb_controller.ReadyPipeHeadAsync], eax |
jz .noasync |
spin_lock_irq [esi+usb_controller.WaitSpinlock] |
mov edx, [edi+EhciStatusReg] |
test dl, 20h |
jz @f |
mov dword [edi+EhciStatusReg], 20h |
and dword [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], not 20h |
dbgstr 'warning: async advance int missed' |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
jmp .async_unlock |
@@: |
cmp dword [esp], 100 |
jb .async_unlock |
mov dword [esp], 100 |
.async_unlock: |
spin_unlock_irq [esi+usb_controller.WaitSpinlock] |
.noasync: |
; 7. Finalize transfers processed by hardware. |
; It is better to perform this step after step 4 (disconnect events), |
; although not strictly obligatory. This way, an active transfer aborted |
; due to disconnect would be handled with more specific USB_STATUS_CLOSED, |
; not USB_STATUS_NORESPONSE. |
test byte [esp+4], 3 |
jz @f |
call ehci_process_updated_schedule |
@@: |
; 8. Test whether reset signalling has been started and should be stopped now. |
; This must be done after step 7, because completion of some transfer could |
; result in resetting a new port. |
.test_reset: |
; 8a. Test whether reset signalling is active. |
cmp [esi+usb_controller.ResettingStatus], 1 |
jnz .no_reset_in_progress |
; 8b. Yep. Test whether it should be stopped. |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_TIME |
jge .reset_done |
; 8c. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_done: |
; 8d. Yep, call the worker function and proceed to 8e. |
call ehci_port_reset_done |
.no_reset_in_progress: |
; 8e. Test whether reset process is done, either successful or failed. |
cmp [esi+usb_controller.ResettingStatus], 0 |
jz .skip_reset |
; 8f. Yep. Test whether it should be stopped. |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_recovery_done |
; 8g. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_recovery_done: |
; 8h. Yep, call the worker function. This could initiate another reset, |
; so return to the beginning of this step. |
call ehci_port_init |
jmp .test_reset |
.skip_reset: |
; 9. Process wait-done notifications, test for new wait requests. |
; Note: that must be done after steps 4 and 7 which could create new requests. |
; 9a. Call the worker function. |
invoke usbhc_api.usb_process_wait_lists |
; 9b. If it reports that an asynchronous endpoint should be removed, |
; doorbell InterruptOnAsyncAdvance and schedule wakeup in 1s |
; (sometimes it just does not fire). |
test al, 1 shl CONTROL_PIPE |
jz @f |
mov edx, [esi+usb_controller.WaitPipeListAsync] |
mov [esi+usb_controller.WaitPipeRequestAsync], edx |
or dword [edi+EhciCommandReg], 1 shl 6 |
; dbgstr 'async advance doorbell' |
cmp dword [esp], 100 |
jb @f |
mov dword [esp], 100 |
@@: |
; 9c. If it reports that a periodic endpoint should be removed, |
; save the current frame and schedule wakeup in 0.01 sec. |
test al, 1 shl INTERRUPT_PIPE |
jz @f |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [esi+usb_controller.WaitPipeRequestPeriodic], eax |
mov edx, [edi+EhciFrameIndexReg] |
mov [esi+usb_controller.StartWaitFrame], edx |
mov dword [esp], 1 ; wakeup in 0.01 sec for next test |
@@: |
; 10. Pop the return value, restore the stack after step 1 and return. |
pop eax |
pop ecx |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; This procedure is called in the USB thread from ehci_process_deferred |
; when EHCI IRQ handler has signalled that new IOC-packet was processed. |
; It scans all lists for completed packets and calls ehci_process_finalized_td |
; for those packets. |
proc ehci_process_updated_schedule |
; Important note: we cannot hold the list lock during callbacks, |
; because callbacks sometimes open and/or close pipes and thus acquire/release |
; the corresponding lock itself. |
; Fortunately, pipes can be finally freed only by another step of |
; ehci_process_deferred, so all pipes existing at the start of this function |
; will be valid while this function is running. Some pipes can be removed |
; from the corresponding list, some pipes can be inserted; insert/remove |
; functions guarantee that traversing one list yields all pipes that were in |
; that list at the beginning of the traversing (possibly with some new pipes, |
; possibly without some new pipes, that doesn't matter). |
push edi |
; 1. Process all Periodic lists. |
lea edi, [esi+ehci_controller.IntEDs-sizeof.ehci_controller+ehci_static_ep.SoftwarePart] |
lea ebx, [esi+ehci_controller.IntEDs+63*sizeof.ehci_static_ep-sizeof.ehci_controller+ehci_static_ep.SoftwarePart] |
@@: |
call ehci_process_updated_list |
cmp edi, ebx |
jnz @b |
; 2. Process the Control list. |
add edi, ehci_controller.ControlDelta |
call ehci_process_updated_list |
; 3. Process the Bulk list. |
call ehci_process_updated_list |
; 4. Return. |
pop edi |
ret |
endp |
; This procedure is called from ehci_process_updated_schedule, see comments there. |
; It processes one list, esi -> usb_controller, edi -> usb_static_ep, |
; and advances edi to next head. |
proc ehci_process_updated_list |
push ebx |
; 1. Perform the external loop over all pipes. |
mov ebx, [edi+usb_static_ep.NextVirt] |
.loop: |
cmp ebx, edi |
jz .done |
; store pointer to the next pipe in the stack |
push [ebx+usb_static_ep.NextVirt] |
; 2. For every pipe, perform the internal loop over all descriptors. |
; All descriptors are organized in the queue; we process items from the start |
; of the queue until a) the last descriptor (not the part of the queue itself) |
; or b) an active (not yet processed by the hardware) descriptor is reached. |
lea ecx, [ebx+usb_pipe.Lock] |
invoke MutexLock |
mov ebx, [ebx+usb_pipe.LastTD] |
push ebx |
mov ebx, [ebx+usb_gtd.NextVirt] |
.tdloop: |
; 3. For every descriptor, test active flag and check for end-of-queue; |
; if either of conditions holds, exit from the internal loop. |
cmp ebx, [esp] |
jz .tddone |
cmp byte [ebx+ehci_gtd.Token-sizeof.ehci_gtd], 0 |
js .tddone |
; Release the queue lock while processing one descriptor: |
; callback function could (and often would) schedule another transfer. |
push ecx |
invoke MutexUnlock |
call ehci_process_updated_td |
pop ecx |
invoke MutexLock |
jmp .tdloop |
.tddone: |
invoke MutexUnlock |
pop ebx |
; End of internal loop, restore pointer to the next pipe |
; and continue the external loop. |
pop ebx |
jmp .loop |
.done: |
pop ebx |
add edi, sizeof.ehci_static_ep |
ret |
endp |
; This procedure is called from ehci_process_updated_list, which is itself |
; called from ehci_process_updated_schedule, see comments there. |
; It processes one completed descriptor. |
; in: ebx -> usb_gtd, out: ebx -> next usb_gtd. |
proc ehci_process_updated_td |
; mov eax, [ebx+usb_gtd.Pipe] |
; cmp [eax+usb_pipe.Type], INTERRUPT_PIPE |
; jnz @f |
; DEBUGF 1,'K : finalized TD for pipe %x:\n',eax |
; lea eax, [ebx-sizeof.ehci_gtd] |
; DEBUGF 1,'K : %x %x %x %x\n',[eax],[eax+4],[eax+8],[eax+12] |
; DEBUGF 1,'K : %x %x %x %x\n',[eax+16],[eax+20],[eax+24],[eax+28] |
;@@: |
; 1. Remove this descriptor from the list of descriptors for this pipe. |
invoke usbhc_api.usb_unlink_td |
; 2. Calculate actual number of bytes transferred. |
mov eax, [ebx+ehci_gtd.Token-sizeof.ehci_gtd] |
lea edx, [eax+eax] |
shr edx, 17 |
sub edx, [ebx+usb_gtd.Length] |
neg edx |
; 3. Check whether we need some special processing beyond notifying the driver. |
; Transfer errors require special processing. |
; Short packets require special processing if |
; a) this is not the last descriptor for transfer stage |
; (in this case we need to process subsequent descriptors for the stage too) |
; or b) the caller considers short transfers to be an error. |
; ehci_alloc_transfer sets bit 0 of ehci_gtd.Flags to 0 if short packet |
; in this descriptor requires special processing and to 1 otherwise. |
; If special processing is not needed, advance to 4 with ecx = 0. |
; Otherwise, go to 6. |
xor ecx, ecx |
test al, 40h |
jnz .error |
test byte [ebx+ehci_gtd.Flags-sizeof.ehci_gtd], 1 |
jnz .notify |
cmp edx, [ebx+usb_gtd.Length] |
jnz .special |
.notify: |
; 4. Either the descriptor in ebx was processed without errors, |
; or all necessary error actions were taken and ebx points to the last |
; related descriptor. |
invoke usbhc_api.usb_process_gtd |
; 5. Free the current descriptor and return the next one. |
push [ebx+usb_gtd.NextVirt] |
stdcall ehci_free_td, ebx |
pop ebx |
ret |
.error: |
push ebx |
sub ebx, sizeof.ehci_gtd |
DEBUGF 1,'K : TD failed:\n' |
DEBUGF 1,'K : %x %x %x %x\n',[ebx],[ebx+4],[ebx+8],[ebx+12] |
DEBUGF 1,'K : %x %x %x %x\n',[ebx+16],[ebx+20],[ebx+24],[ebx+28] |
pop ebx |
DEBUGF 1,'K : pipe now:\n' |
mov ecx, [ebx+usb_gtd.Pipe] |
sub ecx, sizeof.ehci_pipe |
DEBUGF 1,'K : %x %x %x %x\n',[ecx],[ecx+4],[ecx+8],[ecx+12] |
DEBUGF 1,'K : %x %x %x %x\n',[ecx+16],[ecx+20],[ecx+24],[ecx+28] |
DEBUGF 1,'K : %x %x %x %x\n',[ecx+32],[ecx+36],[ecx+40],[ecx+44] |
.special: |
; 6. Special processing is needed. |
; 6a. Save the status and length. |
push edx |
push eax |
; 6b. Traverse the list of descriptors looking for the final descriptor |
; for this transfer. Free and unlink non-final descriptors. |
; Final descriptor will be freed in step 5. |
.look_final: |
invoke usbhc_api.usb_is_final_packet |
jnc .found_final |
push [ebx+usb_gtd.NextVirt] |
stdcall ehci_free_td, ebx |
pop ebx |
invoke usbhc_api.usb_unlink_td |
jmp .look_final |
.found_final: |
; 6c. Restore the status saved in 6a and transform it to the error code. |
; Notes: |
; * any USB transaction error results in Halted bit; if it is not set, |
; but we are here, it must be due to short packet; |
; * babble is considered a fatal USB transaction error, |
; other errors just lead to retrying the transaction; |
; if babble is detected, return the corresponding error; |
; * if several non-fatal errors have occured during transaction retries, |
; all corresponding bits are set. In this case, return some error code, |
; the order is quite arbitrary. |
pop eax ; status |
movi ecx, USB_STATUS_UNDERRUN |
test al, 40h ; not Halted? |
jz .know_error |
mov cl, USB_STATUS_OVERRUN |
test al, 10h ; Babble detected? |
jnz .know_error |
mov cl, USB_STATUS_BUFOVERRUN |
test al, 20h ; Data Buffer error? |
jnz .know_error |
mov cl, USB_STATUS_NORESPONSE |
test al, 8 ; Transaction Error? |
jnz .know_error |
mov cl, USB_STATUS_STALL |
.know_error: |
; 6d. If error code is USB_STATUS_UNDERRUN and the last TD allows short packets, |
; it is not an error; in this case, go to 4 with ecx = 0. |
cmp ecx, USB_STATUS_UNDERRUN |
jnz @f |
test byte [ebx+ehci_gtd.Flags-sizeof.ehci_gtd], 1 |
jz @f |
xor ecx, ecx |
pop edx ; length |
jmp .notify |
@@: |
; 6e. Abort the entire transfer. |
; There are two cases: either there is only one transfer stage |
; (everything except control transfers), then ebx points to the last TD and |
; all previous TD were unlinked and dismissed (if possible), |
; or there are several stages (a control transfer) and ebx points to the last |
; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, |
; because Setup stage can not produce short packets); for Data stage, we need |
; to unlink and free (if possible) one more TD and advance ebx to the next one. |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .normal |
push ecx |
push [ebx+usb_gtd.NextVirt] |
stdcall ehci_free_td, ebx |
pop ebx |
invoke usbhc_api.usb_unlink_td |
pop ecx |
.normal: |
; 6f. For bulk/interrupt transfers we have no choice but halt the queue, |
; the driver should intercede (through some API which is not written yet). |
; Control pipes normally recover at the next SETUP transaction (first stage |
; of any control transfer), so we hope on the best and just advance the queue |
; to the next transfer. (According to the standard, "A control pipe may also |
; support functional stall as well, but this is not recommended."). |
mov edx, [ebx+usb_gtd.Pipe] |
mov eax, [ebx+ehci_gtd.NextTD-sizeof.ehci_gtd] |
or al, 1 |
mov [edx+ehci_pipe.Overlay.NextTD-sizeof.ehci_pipe], eax |
mov [edx+ehci_pipe.Overlay.AlternateNextTD-sizeof.ehci_pipe], eax |
cmp [edx+usb_pipe.Type], CONTROL_PIPE |
jz .control |
; Bulk/interrupt transfer; halt the queue. |
mov [edx+ehci_pipe.Overlay.Token-sizeof.ehci_pipe], 40h |
pop edx |
jmp .notify |
; Control transfer. |
.control: |
and [edx+ehci_pipe.Overlay.Token-sizeof.ehci_pipe], 0 |
dec [edx+ehci_pipe.Overlay.NextTD-sizeof.ehci_pipe] |
pop edx |
jmp .notify |
endp |
; This procedure unlinks the pipe from the corresponding pipe list. |
; esi -> usb_controller, ebx -> usb_pipe |
proc ehci_unlink_pipe |
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE |
jnz @f |
test word [ebx+ehci_pipe.Flags-sizeof.ehci_pipe+2], 3FFFh |
jnz .interrupt_fs |
call ehci_hs_interrupt_list_unlink |
jmp .interrupt_common |
.interrupt_fs: |
call ehci_fs_interrupt_list_unlink |
.interrupt_common: |
@@: |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
mov edx, esi |
sub edx, eax |
cmp edx, sizeof.ehci_controller |
mov edx, [ebx+ehci_pipe.NextQH-sizeof.ehci_pipe] |
jb .prev_is_static |
mov [eax+ehci_pipe.NextQH-sizeof.ehci_pipe], edx |
ret |
.prev_is_static: |
mov [eax+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], edx |
ret |
endp |
proc ehci_alloc_td |
push ebx |
mov ebx, ehci_gtd_mutex |
invoke usbhc_api.usb_allocate_common, (sizeof.ehci_gtd + sizeof.usb_gtd + 1Fh) and not 1Fh |
test eax, eax |
jz @f |
add eax, sizeof.ehci_gtd |
@@: |
pop ebx |
ret |
endp |
; This procedure is called from several places from main USB code and |
; frees all additional data associated with the transfer descriptor. |
; EHCI has no additional data, so just free ehci_gtd structure. |
proc ehci_free_td |
sub dword [esp+4], sizeof.ehci_gtd |
jmp [usbhc_api.usb_free_common] |
endp |
include 'ehci_scheduler.inc' |
section '.data' readable writable |
include '../peimport.inc' |
include_debug_strings |
IncludeIGlobals |
IncludeUGlobals |
align 4 |
usbhc_api usbhc_func |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/drivers/usb/ehci_scheduler.inc |
---|
0,0 → 1,861 |
; Implementation of periodic transaction scheduler for USB. |
; Bandwidth dedicated to periodic transactions is limited, so |
; different pipes should be scheduled as uniformly as possible. |
; USB2 scheduler. |
; There are two parts: high-speed pipes and split-transaction pipes. |
; |
; High-speed scheduler uses the same algorithm as USB1 scheduler: |
; when adding a pipe, optimize the following quantity: |
; * for every microframe, take all bandwidth scheduled to periodic transfers, |
; * calculate maximum over all microframes, |
; * select a variant which minimizes that maximum; |
; * if there are several such variants, |
; prefer those that are closer to end of frame |
; to minimize collisions with split transactions; |
; when removing a pipe, do nothing (except for bookkeeping). |
; in: esi -> usb_controller |
; out: edx -> usb_static_ep, eax = S-Mask |
proc ehci_select_hs_interrupt_list |
; inherit some variables from usb_open_pipe |
virtual at ebp-12 |
.targetsmask dd ? |
.bandwidth dd ? |
.target dd ? |
dd ? |
dd ? |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; prolog, initialize local vars |
or [.bandwidth], -1 |
or [.target], -1 |
or [.targetsmask], -1 |
push ebx edi ; save used registers to be stdcall |
; 1. In EHCI, every list describes one millisecond = 8 microframes. |
; Thus, there are two significantly different branches: |
; for pipes with interval >= 8 microframes, advance to 2, |
; for pipes which should be planned in every frame (one or more microframes), |
; go to 9. |
; Note: the actual interval for high-speed devices is 2^([.interval]-1), |
; (the core specification forbids [.interval] == 0) |
mov ecx, [.interval] |
dec ecx |
cmp ecx, 3 |
jb .every_frame |
; 2. Determine the actual interval in milliseconds. |
sub ecx, 3 |
cmp ecx, 5 ; maximum 32ms |
jbe @f |
movi ecx, 5 |
@@: |
; There are four nested loops, |
; * Loop #4 (the innermost one) calculates the total periodic bandwidth |
; scheduled in the given microframe of the given millisecond. |
; * Loop #3 calculates the maximum over all milliseconds |
; in the given variant, that is the quantity we're trying to minimize. |
; * Loops #1 and #2 check all variants; |
; loop #1 is responsible for the target millisecond, |
; loop #2 is responsible for the microframe within millisecond. |
; 3. Prepare for loops. |
; ebx = number of iterations of loop #1 |
; [esp] = delta of counter for loop #3, in bytes |
; [esp+4] = delta between the first group and the target group, in bytes |
movi ebx, 1 |
movi edx, sizeof.ehci_static_ep |
shl ebx, cl |
shl edx, cl |
mov eax, 64*sizeof.ehci_static_ep |
sub eax, edx |
sub eax, edx |
push eax |
push edx |
; 4. Select the best variant. |
; 4a. Loop #1: initialize counter = pointer to ehci_static_ep for |
; the target millisecond in the first group. |
lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] |
.varloop0: |
; 4b. Loop #2: initialize counter = microframe within the target millisecond. |
xor ecx, ecx |
.varloop: |
; 4c. Loop #3: save counter of loop #1, |
; initialize counter with the value of loop #1 counter, |
; initialize maximal bandwidth = zero. |
xor edi, edi |
push edx |
virtual at esp |
.saved_counter1 dd ? ; step 4c |
.loop3_delta dd ? ; step 3 |
.target_delta dd ? ; step 3 |
end virtual |
.calc_max_bandwidth: |
; 4d. Loop #4: initialize counter with the value of loop #3 counter, |
; initialize total bandwidth = zero. |
xor eax, eax |
push edx |
.calc_bandwidth: |
; 4e. Loop #4: add the bandwidth from the current list |
; and advance to the next list, while there is one. |
add ax, [edx+ehci_static_ep.Bandwidths+ecx*2] |
mov edx, [edx+ehci_static_ep.NextList] |
test edx, edx |
jnz .calc_bandwidth |
; 4f. Loop #4 end: restore counter of loop #3. |
pop edx |
; 4g. Loop #3: update maximal bandwidth. |
cmp eax, edi |
jb @f |
mov edi, eax |
@@: |
; 4h. Loop #3: advance the counter and repeat while within the first group. |
lea eax, [esi+ehci_controller.IntEDs+32*sizeof.ehci_static_ep-sizeof.ehci_controller] |
add edx, [.loop3_delta] |
cmp edx, eax |
jb .calc_max_bandwidth |
; 4i. Loop #3 end: restore counter of loop #1. |
pop edx |
; 4j. Loop #2: if the current variant is better (maybe not strictly) |
; then the previous optimum, update the optimal bandwidth and the target. |
cmp edi, [.bandwidth] |
ja @f |
jb .update |
cmp ecx, [.targetsmask] |
jb @f |
.update: |
mov [.bandwidth], edi |
mov [.target], edx |
mov [.targetsmask], ecx |
@@: |
; 4k. Loop #2: continue 8 times for every microframe. |
inc ecx |
cmp ecx, 8 |
jb .varloop |
; 4l. Loop #1: advance counter and repeat ebx times, |
; ebx was calculated in step 3. |
add edx, sizeof.ehci_static_ep |
dec ebx |
jnz .varloop0 |
; 5. Calculate bandwidth for the new pipe. |
mov eax, [.maxpacket] |
call calc_hs_bandwidth |
mov ecx, [.maxpacket] |
shr ecx, 11 |
inc ecx |
and ecx, 3 |
imul eax, ecx |
; 6. Get the pointer to the best list. |
pop edx ; restore value from step 3 |
pop edx ; get delta calculated in step 3 |
add edx, [.target] |
; 7. Check that bandwidth for the new pipe plus old bandwidth |
; still fits to maximum allowed by the core specification |
; current [.bandwidth] + new bandwidth <= limit; |
; USB2 specification allows maximum 60000*80% bit times for periodic microframe |
mov ecx, [.bandwidth] |
add ecx, eax |
cmp ecx, 48000 |
ja .no_bandwidth |
; 8. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return. |
mov ecx, [.targetsmask] |
add [edx+ehci_static_ep.Bandwidths+ecx*2], ax |
add edx, ehci_static_ep.SoftwarePart |
movi eax, 1 |
shl eax, cl |
pop edi ebx ; restore used registers to be stdcall |
ret |
.no_bandwidth: |
dbgstr 'Periodic bandwidth limit reached' |
xor eax, eax |
xor edx, edx |
pop edi ebx |
ret |
.every_frame: |
; The pipe should be scheduled every frame in two or more microframes. |
; 9. Calculate maximal bandwidth for every microframe: three nested loops. |
; 9a. The outermost loop: ebx = microframe to calculate. |
xor ebx, ebx |
.calc_all_bandwidths: |
; 9b. The intermediate loop: |
; edx = pointer to ehci_static_ep in the first group, [esp] = counter, |
; edi = maximal bandwidth |
lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] |
xor edi, edi |
push 32 |
.calc_max_bandwidth2: |
; 9c. The innermost loop: calculate bandwidth for the given microframe |
; in the given frame. |
xor eax, eax |
push edx |
.calc_bandwidth2: |
add ax, [edx+ehci_static_ep.Bandwidths+ebx*2] |
mov edx, [edx+ehci_static_ep.NextList] |
test edx, edx |
jnz .calc_bandwidth2 |
pop edx |
; 9d. The intermediate loop continued: update maximal bandwidth. |
cmp eax, edi |
jb @f |
mov edi, eax |
@@: |
add edx, sizeof.ehci_static_ep |
dec dword [esp] |
jnz .calc_max_bandwidth2 |
pop eax |
; 9e. Push the calculated maximal bandwidth and continue the outermost loop. |
push edi |
inc ebx |
cmp ebx, 8 |
jb .calc_all_bandwidths |
virtual at esp |
.bandwidth7 dd ? |
.bandwidth6 dd ? |
.bandwidth5 dd ? |
.bandwidth4 dd ? |
.bandwidth3 dd ? |
.bandwidth2 dd ? |
.bandwidth1 dd ? |
.bandwidth0 dd ? |
end virtual |
; 10. Select the best variant. |
; edx = S-Mask = bitmask of scheduled microframes |
movi edx, 0x11 |
cmp ecx, 1 |
ja @f |
mov dl, 0x55 |
jz @f |
mov dl, 0xFF |
@@: |
; try all variants edx, edx shl 1, edx shl 2, ... |
; while they fit in the lower byte (8 microframes per frame) |
.select_best_mframe: |
xor edi, edi |
mov ecx, edx |
mov eax, esp |
.calc_mframe: |
add cl, cl |
jnc @f |
cmp edi, [eax] |
jae @f |
mov edi, [eax] |
@@: |
add eax, 4 |
test cl, cl |
jnz .calc_mframe |
cmp [.bandwidth], edi |
jb @f |
mov [.bandwidth], edi |
mov [.targetsmask], edx |
@@: |
add dl, dl |
jnc .select_best_mframe |
; 11. Restore stack after step 9. |
add esp, 8*4 |
; 12. Get the pointer to the target list (responsible for every microframe). |
lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+62*sizeof.ehci_static_ep-sizeof.ehci_controller] |
; 13. Calculate bandwidth on the bus. |
mov eax, [.maxpacket] |
call calc_hs_bandwidth |
mov ecx, [.maxpacket] |
shr ecx, 11 |
inc ecx |
and ecx, 3 |
imul eax, ecx |
; 14. Check that current [.bandwidth] + new bandwidth <= limit; |
; USB2 specification allows maximum 60000*80% bit times for periodic microframe. |
mov ecx, [.bandwidth] |
add ecx, eax |
cmp ecx, 48000 |
ja .no_bandwidth |
; 15. Update bandwidths including the new pipe. |
mov ecx, [.targetsmask] |
lea edi, [edx+ehci_static_ep.Bandwidths-ehci_static_ep.SoftwarePart] |
.update_bandwidths: |
shr ecx, 1 |
jnc @f |
add [edi], ax |
@@: |
add edi, 2 |
test ecx, ecx |
jnz .update_bandwidths |
; 16. Return target list and target S-Mask. |
mov eax, [.targetsmask] |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; Pipe is removing, update the corresponding lists. |
; We do not reorder anything, so just update book-keeping variable |
; in the list header. |
proc ehci_hs_interrupt_list_unlink |
movzx eax, word [ebx+ehci_pipe.Token-sizeof.ehci_pipe+2] |
; calculate bandwidth |
call calc_hs_bandwidth |
mov ecx, [ebx+ehci_pipe.Flags-sizeof.ehci_pipe] |
shr ecx, 30 |
imul eax, ecx |
movzx ecx, byte [ebx+ehci_pipe.Flags-sizeof.ehci_pipe] |
; get target list |
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe] |
; update bandwidth |
.dec_bandwidth: |
shr ecx, 1 |
jnc @f |
sub word [edx+ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart], ax |
@@: |
add edx, 2 |
test ecx, ecx |
jnz .dec_bandwidth |
; return |
ret |
endp |
; Helper procedure for USB2 scheduler: calculate bandwidth on the bus. |
; in: low 11 bits of eax = payload size in bytes |
; out: eax = maximal bandwidth in HS-bits |
proc calc_hs_bandwidth |
and eax, (1 shl 11) - 1 ; get payload for one transaction |
add eax, 3 ; add 3 bytes for other fields in data packet, PID+CRC16 |
; Multiply by 8 for bytes -> bits and then by 7/6 to accomodate bit stuffing; |
; total 28/3 = 9+1/3 |
mov edx, 55555556h |
lea ecx, [eax*9] |
mul edx |
; Add 989 extra bits: 68 bits for Token packet (32 for SYNC, 24 for token+address, |
; 4 extra bits for possible bit stuffing in token+address, 8 for EOP), |
; 736 bits for bus turn-around, 40 bits for SYNC+EOP in Data packet, |
; 8 bits for inter-packet delay, 49 bits for Handshake packet, |
; 88 bits for another inter-packet delay. |
lea eax, [ecx+edx+989] |
ret |
endp |
; Split-transaction scheduler (aka TT scheduler, TT stands for Transaction |
; Translator, section 11.14 of the core spec) needs to schedule three event |
; types on two buses: Start-Split and Complete-Split on HS bus and normal |
; transaction on FS/LS bus. |
; Assume that FS/LS bus is more restricted and more important to be scheduled |
; uniformly, so select the variant which minimizes maximal used bandwidth |
; on FS/LS bus and does not overflow HS bus. |
; If there are several such variants, prefer variants which is closest to |
; start of frame, and within the same microframe consider HS bandwidth |
; utilization as a last criteria. |
; The procedure ehci_select_tt_interrupt_list has been splitted into several |
; macro, each representing a logical step of the procedure, |
; to simplify understanding what is going on. Consider all the following macro |
; as logical parts of one procedure, they are meaningless outside the context. |
; Given a frame, calculate bandwidth occupied by already opened pipes |
; in every microframe. |
; Look for both HS and FS/LS buses: there are 16 words of information, |
; 8 for HS bus, 8 for FS/LS bus, for every microframe. |
; Since we count already opened pipes, the total bandwidth in every microframe |
; is less than 60000 bits (and even 60000*80% bits), otherwise the scheduler |
; would not allow to open those pipes. |
; edi -> first list for the frame |
macro tt_calc_bandwidth_in_frame |
{ |
local .lists, .pipes, .pipes_done, .carry |
; 1. Zero everything. |
xor eax, eax |
mov edx, edi |
repeat 4 |
mov dword [.budget+(%-1)*4], eax |
end repeat |
repeat 4 |
mov dword [.hs_bandwidth+(%-1)*4], eax |
end repeat |
mov [.total_budget], ax |
; Loop over all lists for the given frame. |
.lists: |
; 2. Total HS bandwidth for all pipes in one list is kept inside list header, |
; add it. Note that overflow is impossible, so we may add entire dwords. |
mov ebx, [edx+ehci_static_ep.SoftwarePart+usb_static_ep.NextVirt] |
repeat 4 |
mov eax, dword [edx+ehci_static_ep.Bandwidths+(%-1)*4] |
add dword [.hs_bandwidth+(%-1)*4], eax |
end repeat |
; Loop over all pipes in the given list. |
add edx, ehci_static_ep.SoftwarePart |
.pipes: |
cmp ebx, edx |
jz .pipes_done |
; 3. For every pipe in every list for the given frame: |
; 3a. Check whether the pipe resides on the same FS/LS bus as the new pipe. |
; If not, skip this pipe. |
mov eax, [ebx+usb_pipe.DeviceData] |
mov eax, [eax+usb_device_data.TTHub] |
cmp eax, [.tthub] |
jnz @f |
; 3b. Calculate FS/LS budget for the opened pipe. |
; Note that eax = TTHub after 3a. |
call tt_calc_budget |
; 3c. Update total budget: add the value from 3b |
; to the budget of the first microframe scheduled for this pipe. |
bsf ecx, [ebx+ehci_pipe.Flags-sizeof.ehci_pipe] |
add [.budget+ecx*2], ax |
@@: |
mov ebx, [ebx+usb_pipe.NextVirt] |
jmp .pipes |
.pipes_done: |
mov edx, [edx+ehci_static_ep.NextList-ehci_static_ep.SoftwarePart] |
test edx, edx |
jnz .lists |
; 4. If the budget for some microframe is exceeded, carry it to the following |
; microframe(s). The actual size of one microframe is 187.5 raw bytes; |
; the core spec says that 188 bytes should be scheduled in every microframe. |
xor eax, eax |
xor ecx, ecx |
.carry: |
xor edx, edx |
add ax, [.budget+ecx*2] |
cmp ax, 188 |
jbe @f |
mov dx, ax |
mov ax, 188 |
sub dx, ax |
@@: |
mov [.budget+ecx*2], ax |
add [.total_budget], ax |
mov ax, dx |
inc ecx |
cmp ecx, 8 |
jb .carry |
} |
; Checks whether the new pipe fits in the existing FS budget |
; starting from the given microframe. If not, mark the microframe |
; as impossible for scheduling. |
; in: ecx = microframe |
macro tt_exclude_microframe_if_no_budget |
{ |
local .loop, .good, .bad |
; 1. If the new budget plus the current budget does not exceed 188 bytes, |
; the variant is possible. |
mov ax, [.budget+ecx*2] |
mov edx, ecx |
add ax, [.new_budget] |
sub ax, 188 |
jbe .good |
; 2. Otherwise, |
; a) nothing should be scheduled in some following microframes, |
; b) after adding the new budget everything should fit in first 6 microframes, |
; this guarantees that even in the worst case 90% limit is satisfied. |
.loop: |
cmp edx, 5 |
jae .bad |
cmp [.budget+(edx+1)*2], 0 |
jnz .bad |
inc edx |
sub ax, 188 |
ja .loop |
.bad: |
btr [.possible_microframes], ecx |
.good: |
} |
; Calculate data corresponding to the particular scheduling variant for the new pipe. |
; Data describe the current scheduling state collected over all frames touched |
; by the given variant: maximal HS bandwidth, maximal FS/LS budget, |
; which microframes fit in the current FS/LS budget for all frames. |
macro tt_calc_statistics_for_one_variant |
{ |
local .frames, .microframes |
; 1. Initialize: zero maximal bandwidth, |
; first 6 microframes are possible for scheduling. |
xor eax, eax |
repeat 4 |
mov dword [.max_hs_bandwidth+(%-1)*4], eax |
end repeat |
mov [.max_fs_bandwidth], ax |
mov [.possible_microframes], 0x3F |
; Loop over all frames starting with [.variant] advancing by [.variant_delta]. |
mov edi, [.variant] |
.frames: |
; 2. Calculate statistics for one frame. |
tt_calc_bandwidth_in_frame |
; 3. Update maximal FS budget. |
mov ax, [.total_budget] |
cmp ax, [.max_fs_bandwidth] |
jb @f |
mov [.max_fs_bandwidth], ax |
@@: |
; 4. For every microframe, update maximal HS bandwidth |
; and check whether the microframe is allowed for scheduling. |
xor ecx, ecx |
.microframes: |
mov ax, [.hs_bandwidth+ecx*2] |
cmp ax, [.max_hs_bandwidth+ecx*2] |
jb @f |
mov [.max_hs_bandwidth+ecx*2], ax |
@@: |
tt_exclude_microframe_if_no_budget |
inc ecx |
cmp ecx, 8 |
jb .microframes |
; Stop loop when outside of first descriptor group. |
lea eax, [esi+ehci_controller.IntEDs+32*sizeof.ehci_static_ep-sizeof.ehci_controller] |
add edi, [.variant_delta] |
cmp edi, eax |
jb .frames |
} |
struct usb_split_info |
microframe_mask dd ? ; lower byte is S-mask, second byte is C-mask |
ssplit_bandwidth dd ? |
csplit_bandwidth dd ? |
ends |
; Check whether the current variant and the current microframe are allowed |
; for scheduling. If so, check whether they are better than the previously |
; selected variant+microframe, if any. If so, update the previously selected |
; variant+microframe to current ones. |
; ecx = microframe, [.variant] = variant |
macro tt_check_variant_microframe |
{ |
local .nothing, .update, .ssplit, .csplit, .csplit_done |
; 1. If the current microframe does not fit in existing FS budget, do nothing. |
bt [.possible_microframes], ecx |
jnc .nothing |
; 2. Calculate maximal HS bandwidth over all affected microframes. |
; 2a. Start-split phase: one or more microframes starting with ecx, |
; coded in lower byte of .info.microframe_mask. |
xor ebx, ebx |
xor edx, edx |
.ssplit: |
lea eax, [ecx+edx] |
movzx eax, [.max_hs_bandwidth+eax*2] |
add eax, [.info.ssplit_bandwidth] |
cmp ebx, eax |
ja @f |
mov ebx, eax |
@@: |
inc edx |
bt [.info.microframe_mask], edx |
jc .ssplit |
; 2b. Complete-split phase: zero or more microframes starting with |
; ecx+(last start-split microframe)+2, |
; coded in second byte of .info.microframe_mask. |
add edx, 8 |
.csplit: |
inc edx |
bt [.info.microframe_mask], edx |
jnc .csplit_done |
lea eax, [ecx+edx] |
cmp eax, 8 |
jae .csplit_done |
movzx eax, [.max_hs_bandwidth+(eax-8)*2] |
add eax, [.info.csplit_bandwidth] |
cmp ebx, eax |
ja .csplit |
mov ebx, eax |
jmp .csplit |
.csplit_done: |
; 3. Check that current HS bandwidth + new bandwidth <= limit; |
; USB2 specification allows maximum 60000*80% bit times for periodic microframe. |
cmp ebx, 48000 |
ja .nothing |
; 4. This variant is possible for scheduling. |
; Check whether it is better than the currently selected one. |
; 4a. The primary criteria: FS/LS bandwidth. |
mov ax, [.max_fs_bandwidth] |
cmp ax, [.best_fs_bandwidth] |
ja .nothing |
jb .update |
; 4b. The secondary criteria: prefer microframes which are closer to start of frame. |
cmp ecx, [.targetsmask] |
ja .nothing |
jb .update |
; 4c. The last criteria: HS bandwidth. |
cmp ebx, [.bandwidth] |
ja .nothing |
.update: |
; 5. This variant is better than the previously selected. |
; Update the best variant with current data. |
mov [.best_fs_bandwidth], ax |
mov [.bandwidth], ebx |
mov [.targetsmask], ecx |
mov eax, [.variant] |
mov [.target], eax |
.nothing: |
} |
; TT scheduler: add new pipe. |
; in: esi -> usb_controller, edi -> usb_pipe |
; out: edx -> usb_static_ep, eax = S-Mask |
proc ehci_select_tt_interrupt_list |
virtual at ebp-12-.local_vars_size |
.local_vars_start: |
.info usb_split_info |
.new_budget dw ? |
.total_budget dw ? |
.possible_microframes dd ? |
.tthub dd ? |
.budget rw 8 |
.hs_bandwidth rw 8 |
.max_hs_bandwidth rw 8 |
.max_fs_bandwidth dw ? |
.best_fs_bandwidth dw ? |
.variant dd ? |
.variant_delta dd ? |
.target_delta dd ? |
.local_vars_size = $ - .local_vars_start |
if .local_vars_size > 24*4 |
err Modify stack frame size in |
end if |
.targetsmask dd ? |
.bandwidth dd ? |
.target dd ? |
dd ? |
dd ? |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
mov eax, [edi+ehci_pipe.Token-sizeof.ehci_pipe] |
shr eax, 16 |
and eax, (1 shl 11) - 1 |
push ebx edi |
; 1. Compute the real interval. FS/LS devices encode the interval as |
; number of milliseconds. Use the maximal power of two that is not greater than |
; the given interval and EHCI scheduling area = 32 frames. |
cmp [.interval], 1 |
adc [.interval], 0 |
mov ecx, 64 |
mov eax, 64 * sizeof.ehci_static_ep |
@@: |
shr ecx, 1 |
cmp [.interval], ecx |
jb @b |
mov [.interval], ecx |
; 2. Compute variables for further calculations. |
; 2a. [.variant_delta] is delta between two lists from the first group |
; that correspond to the same variant. |
imul ecx, sizeof.ehci_static_ep |
mov [.variant_delta], ecx |
; 2b. [.target_delta] is delta between the final answer from the group |
; corresponding to [.interval] and the item from the first group. |
sub eax, ecx |
sub eax, ecx |
mov [.target_delta], eax |
; 2c. [.variant] is the first list from the first group that corresponds |
; to the current variant. |
lea eax, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] |
mov [.variant], eax |
; 2d. [.tthub] identifies TT hub for new pipe, [.new_budget] is FS budget |
; for new pipe. |
mov eax, [edi+usb_pipe.DeviceData] |
mov eax, [eax+usb_device_data.TTHub] |
mov ebx, edi |
mov [.tthub], eax |
call tt_calc_budget |
mov [.new_budget], ax |
; 2e. [.usb_split_info] describes bandwidth used by new pipe on HS bus. |
lea edi, [.info] |
call tt_fill_split_info |
test eax, eax |
jz .no_bandwidth |
; 2f. There is no best variant yet, put maximal possible values, |
; so any variant would be better than the "current". |
or [.best_fs_bandwidth], -1 |
or [.target], -1 |
or [.bandwidth], -1 |
or [.targetsmask], -1 |
; 3. Loop over all variants, for every variant decide whether it is acceptable, |
; select the best variant from all acceptable variants. |
.check_variants: |
tt_calc_statistics_for_one_variant |
xor ecx, ecx |
.check_microframes: |
tt_check_variant_microframe |
inc ecx |
cmp ecx, 6 |
jb .check_microframes |
add [.variant], sizeof.ehci_static_ep |
dec [.interval] |
jnz .check_variants |
; 4. If there is no acceptable variants, return error. |
mov ecx, [.targetsmask] |
mov edx, [.target] |
cmp ecx, -1 |
jz .no_bandwidth |
; 5. Calculate the answer: edx -> selected list, eax = S-Mask and C-Mask. |
mov eax, [.info.microframe_mask] |
add edx, [.target_delta] |
shl eax, cl |
and eax, 0xFFFF |
; 6. Update HS bandwidths in the selected list. |
xor ecx, ecx |
mov ebx, [.info.ssplit_bandwidth] |
.update_ssplit: |
bt eax, ecx |
jnc @f |
add [edx+ehci_static_ep.Bandwidths+ecx*2], bx |
@@: |
inc ecx |
cmp ecx, 8 |
jb .update_ssplit |
mov ebx, [.info.csplit_bandwidth] |
.update_csplit: |
bt eax, ecx |
jnc @f |
add [edx+ehci_static_ep.Bandwidths+(ecx-8)*2], bx |
@@: |
inc ecx |
cmp ecx, 16 |
jb .update_csplit |
; 7. Return. |
add edx, ehci_static_ep.SoftwarePart |
pop edi ebx |
ret |
.no_bandwidth: |
dbgstr 'Periodic bandwidth limit reached' |
xor eax, eax |
xor edx, edx |
pop edi ebx |
ret |
endp |
; Pipe is removing, update the corresponding lists. |
; We do not reorder anything, so just update book-keeping variable |
; in the list header. |
proc ehci_fs_interrupt_list_unlink |
; calculate bandwidth |
push edi |
sub esp, sizeof.usb_split_info |
mov edi, esp |
call tt_fill_split_info |
; get target list |
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe] |
; update bandwidth for Start-Split |
mov eax, [edi+usb_split_info.ssplit_bandwidth] |
xor ecx, ecx |
.dec_bandwidth_1: |
bt [ebx+ehci_pipe.Flags-sizeof.ehci_pipe], ecx |
jnc @f |
sub word [edx+ecx*2+ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart], ax |
@@: |
inc ecx |
cmp ecx, 8 |
jb .dec_bandwidth_1 |
; update bandwidth for Complete-Split |
mov eax, [edi+usb_split_info.csplit_bandwidth] |
.dec_bandwidth_2: |
bt [ebx+ehci_pipe.Flags-sizeof.ehci_pipe], ecx |
jnc @f |
sub word [edx+(ecx-8)*2+ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart], ax |
@@: |
inc ecx |
cmp ecx, 16 |
jb .dec_bandwidth_2 |
add esp, sizeof.usb_split_info |
pop edi |
ret |
endp |
; Helper procedure for ehci_select_tt_interrupt_list. |
; Calculates "best-case budget" according to the core spec, |
; that is, number of bytes (not bits) corresponding to "optimistic" transaction |
; time, including inter-packet delays/bus turn-around time, |
; but without bit stuffing and timers drift. |
; One extra TT-specific delay is added: TT think time from the hub descriptor. |
; Similar to calc_usb1_bandwidth with corresponding changes. |
; eax -> usb_hub with TT, ebx -> usb_pipe |
proc tt_calc_budget |
invoke usbhc_api.usb_get_tt_think_time ; ecx = TT think time in FS-bytes |
mov eax, [ebx+ehci_pipe.Token-sizeof.ehci_pipe] |
shr eax, 16 |
and eax, (1 shl 11) - 1 ; get data length |
bt [ebx+ehci_pipe.Token-sizeof.ehci_pipe], 12 |
jc .low_speed |
; Full-speed interrupt IN/OUT: |
; 33 bits for Token packet (8 for SYNC, 24 for token+address, 3 for EOP), |
; 18 bits for bus turn-around, 11 bits for SYNC+EOP in Data packet, |
; 2 bits for inter-packet delay, 19 bits for Handshake packet, |
; 2 bits for another inter-packet delay. 85 bits total, pad to 11 bytes. |
lea eax, [eax+11+ecx] |
; 1 byte is minimal TT think time in addition to ecx. |
ret |
.low_speed: |
; Low-speed interrupt IN/OUT: |
; multiply by 8 for LS -> FS, |
; add 85 bytes as in full-speed interrupt and extra 5 bytes for two PRE packets |
; and two hub delays. |
; 1 byte is minimal TT think time in addition to ecx. |
lea eax, [eax*8+90+ecx] |
ret |
endp |
; Helper procedure for TT scheduler. |
; Calculates Start-Split/Complete-Split masks and HS bandwidths. |
; ebx -> usb_pipe, edi -> usb_split_info |
proc tt_fill_split_info |
; Interrupt endpoints. |
; The core spec says in 5.7.3 "Interrupt Transfer Packet Size Constraints" that: |
; The maximum allowable interrupt data payload size is 64 bytes or less for full-speed. |
; Low-speed devices are limited to eight bytes or less maximum data payload size. |
; This is important for scheduling, it guarantees that in any case transaction fits |
; in two microframes (usually one, two if transaction has started too late in the first |
; microframe), so check it. |
mov eax, [ebx+ehci_pipe.Token-sizeof.ehci_pipe] |
mov ecx, 8 |
bt eax, 12 |
jc @f |
mov ecx, 64 |
@@: |
shr eax, 16 |
and eax, (1 shl 11) - 1 ; get data length |
cmp eax, ecx |
ja .error |
add eax, 3 ; add 3 bytes for other fields in data packet, PID+CRC16 |
; Multiply by 8 for bytes -> bits and then by 7/6 to accomodate bit stuffing; |
; total 28/3 = 9+1/3 |
mov edx, 55555556h |
lea ecx, [eax*9] |
mul edx |
; One start-split, three complete-splits (unless the last is too far, |
; but this is handled by the caller). |
mov eax, [ebx+usb_pipe.LastTD] |
mov [edi+usb_split_info.microframe_mask], 0x1C01 |
; Structure and HS bandwidth of packets depends on the direction. |
bt [eax+ehci_gtd.Token-sizeof.ehci_gtd], 8 |
jc .interrupt_in |
.interrupt_out: |
; Start-Split phase: |
; 77 bits for SPLIT packet (32 for SYNC, 8 for EOP, 32 for data, 5 for bit stuffing), |
; 88 bits for inter-packet delay, 68 bits for Token packet, |
; 88 bits for inter-packet delay, 40 bits for SYNC+EOP in Data packet, |
; 88 bits for last inter-packet delay, total 449 bits. |
lea eax, [edx+ecx+449] |
mov [edi+usb_split_info.ssplit_bandwidth], eax |
; Complete-Split phase: |
; 77 bits for SPLIT packet, |
; 88 bits for inter-packet delay, 68 bits for Token packet, |
; 736 bits for bus turn-around, 49 bits for Handshake packet, |
; 8 bits for inter-packet delay, total 1026 bits. |
mov [edi+usb_split_info.csplit_bandwidth], 1026 |
ret |
.interrupt_in: |
; Start-Split phase: |
; 77 bits for SPLIT packet, 88 bits for inter-packet delay, |
; 68 bits for Token packet, 88 bits for another inter-packet delay, |
; total 321 bits. |
mov [edi+usb_split_info.ssplit_bandwidth], 321 |
; Complete-Split phase: |
; 77 bits for SPLIT packet, 88 bits for inter-packet delay, |
; 68 bits for Token packet, 736 bits for bus turn-around, |
; 40 bits for SYNC+EOP in Data packet, 8 bits for inter-packet delay, |
; total 1017 bits. |
lea eax, [edx+ecx+1017] |
mov [edi+usb_split_info.csplit_bandwidth], eax |
ret |
.error: |
xor eax, eax |
ret |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/drivers/usb/ohci.asm |
---|
0,0 → 1,1686 |
; Code for OHCI controllers. |
; Standard driver stuff |
format PE DLL native |
entry start |
__DEBUG__ equ 1 |
__DEBUG_LEVEL__ equ 1 |
section '.reloc' data readable discardable fixups |
section '.text' code readable executable |
include '../proc32.inc' |
include '../struct.inc' |
include '../macros.inc' |
include '../fdo.inc' |
include '../../kernel/trunk/bus/usb/common.inc' |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; OHCI register declarations |
; All of the registers should be read and written as Dwords. |
; Partition 1. Control and Status registers. |
OhciRevisionReg = 0 |
OhciControlReg = 4 |
OhciCommandStatusReg = 8 |
OhciInterruptStatusReg = 0Ch |
OhciInterruptEnableReg = 10h |
OhciInterruptDisableReg = 14h |
; Partition 2. Memory Pointer registers. |
OhciHCCAReg = 18h |
OhciPeriodCurrentEDReg = 1Ch |
OhciControlHeadEDReg = 20h |
OhciControlCurrentEDReg = 24h |
OhciBulkHeadEDReg = 28h |
OhciBulkCurrentEDReg = 2Ch |
OhciDoneHeadReg = 30h |
; Partition 3. Frame Counter registers. |
OhciFmIntervalReg = 34h |
OhciFmRemainingReg = 38h |
OhciFmNumberReg = 3Ch |
OhciPeriodicStartReg = 40h |
OhciLSThresholdReg = 44h |
; Partition 4. Root Hub registers. |
OhciRhDescriptorAReg = 48h |
OhciRhDescriptorBReg = 4Ch |
OhciRhStatusReg = 50h |
OhciRhPortStatusReg = 54h |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; OHCI-specific part of a pipe descriptor. |
; * This structure corresponds to the Endpoint Descriptor aka ED from the OHCI |
; specification. |
; * The hardware requires 16-bytes alignment of the hardware part. |
; Since the allocator (usb_allocate_common) allocates memory sequentially |
; from page start (aligned on 0x1000 bytes), block size for the allocator |
; must be divisible by 16; usb1_allocate_endpoint ensures this. |
struct ohci_pipe |
; All addresses are physical. |
Flags dd ? |
; 1. Lower 7 bits (bits 0-6) are FunctionAddress. This is the USB address of |
; the function containing the endpoint that this ED controls. |
; 2. Next 4 bits (bits 7-10) are EndpointNumber. This is the USB address of |
; the endpoint within the function. |
; 3. Next 2 bits (bits 11-12) are Direction. This 2-bit field indicates the |
; direction of data flow: 1 = IN, 2 = OUT. If neither IN nor OUT is |
; specified, then the direction is determined from the PID field of the TD. |
; For CONTROL endpoints, the transfer direction is different |
; for different transfers, so the value of this field is 0 |
; (3 would have the same effect) and the actual direction |
; of one transfer is encoded in the Transfer Descriptor. |
; 4. Next bit (bit 13) is Speed bit. It indicates the speed of the endpoint: |
; full-speed (S = 0) or low-speed (S = 1). |
; 5. Next bit (bit 14) is sKip bit. When this bit is set, the hardware |
; continues on to the next ED on the list without attempting access |
; to the TD queue or issuing any USB token for the endpoint. |
; Always cleared. |
; 6. Next bit (bit 15) is Format bit. It must be 0 for Control, Bulk and |
; Interrupt endpoints and 1 for Isochronous endpoints. |
; 7. Next 11 bits (bits 16-26) are MaximumPacketSize. This field indicates |
; the maximum number of bytes that can be sent to or received from the |
; endpoint in a single data packet. |
TailP dd ? |
; Physical address of the tail descriptor in the TD queue. |
; The descriptor itself is not in the queue. See also HeadP. |
HeadP dd ? |
; 1. First bit (bit 0) is Halted bit. This bit is set by the hardware to |
; indicate that processing of the TD queue on the endpoint is halted. |
; 2. Second bit (bit 1) is toggleCarry bit. Whenever a TD is retired, this |
; bit is written to contain the last data toggle value from the retired TD. |
; 3. Next two bits (bits 2-3) are reserved and always zero. |
; 4. With masked 4 lower bits, this is HeadP itself: physical address of the |
; head descriptor in the TD queue, that is, next TD to be processed for this |
; endpoint. Note that a TD must be 16-bytes aligned. |
; Empty queue is characterized by the condition HeadP == TailP. |
NextED dd ? |
; If nonzero, then this entry is a physical address of the next ED to be |
; processed. See also the description before NextVirt field of the usb_pipe |
; structure. Additionally to that description, the following is specific for |
; the OHCI controller: |
; * n=5, N=32, there are 32 "leaf" periodic lists. |
; * The 1ms periodic list also serves Isochronous endpoints, which should be |
; in the end of the list. |
; * There is no "next" list for Bulk and Control lists, they are processed |
; separately from others. |
; * There is no "next" list for Periodic list for 1ms interval. |
ends |
; This structure describes the static head of every list of pipes. |
; The hardware requires 16-bytes alignment of this structure. |
; All instances of this structure are located sequentially in uhci_controller, |
; uhci_controller is page-aligned, so it is sufficient to make this structure |
; 16-bytes aligned and verify that the first instance is 16-bytes aligned |
; inside uhci_controller. |
struct ohci_static_ep |
Flags dd ? |
; Same as ohci_pipe.Flags. |
; sKip bit is set, so the hardware ignores other fields except NextED. |
dd ? |
; Corresponds to ohci_pipe.TailP. Not used. |
NextList dd ? |
; Virtual address of the next list. |
NextED dd ? |
; Same as ohci_pipe.NextED. |
SoftwarePart rd sizeof.usb_static_ep/4 |
; Software part, common for all controllers. |
dd ? |
; Padding for 16-bytes alignment. |
ends |
if sizeof.ohci_static_ep mod 16 |
.err ohci_static_ep must be 16-bytes aligned |
end if |
; OHCI-specific part of controller data. |
; * The structure describes the memory area used for controller data, |
; additionally to the registers of the controller. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 256 bytes and corresponds to |
; the HCCA from OHCI specification. |
; * The hardware requires 256-bytes alignment of the hardware part, so |
; the entire descriptor must be 256-bytes aligned. |
; This structure is allocated with kernel_alloc (see usb_init_controller), |
; this gives page-aligned data. |
; * The controller is described by both ohci_controller and usb_controller |
; structures, for each controller there is one ohci_controller and one |
; usb_controller structure. These structures are located sequentially |
; in the memory: beginning from some page start, there is ohci_controller |
; structure - this enforces hardware alignment requirements - and then |
; usb_controller structure. |
; * The code keeps pointer to usb_controller structure. The ohci_controller |
; structure is addressed as [ptr + ohci_controller.field - sizeof.ohci_controller]. |
struct ohci_controller |
; ------------------------------ hardware fields ------------------------------ |
InterruptTable rd 32 |
; Pointers to interrupt EDs. The hardware starts processing of periodic lists |
; within the frame N from the ED pointed to by [InterruptTable+(N and 31)*4]. |
; See also the description of periodic lists inside ohci_pipe structure. |
FrameNumber dw ? |
; The current frame number. This field is written by hardware only. |
; This field is read by ohci_process_deferred and ohci_irq to |
; communicate when control/bulk processing needs to be temporarily |
; stopped/restarted. |
dw ? |
; Padding. Written as zero at every update of FrameNumber. |
DoneHead dd ? |
; Physical pointer to the start of Done Queue. |
; When the hardware updates this field, it sets bit 0 to one if there is |
; unmasked interrupt pending. |
rb 120 |
; Reserved for the hardware. |
; ------------------------------ software fields ------------------------------ |
IntEDs ohci_static_ep |
rb 62 * sizeof.ohci_static_ep |
; Heads of 63 Periodic lists, see the description in usb_pipe. |
ControlED ohci_static_ep |
; Head of Control list, see the description in usb_pipe. |
BulkED ohci_static_ep |
; Head of Bulk list, see the description in usb_pipe. |
MMIOBase dd ? |
; Virtual address of memory-mapped area with OHCI registers OhciXxxReg. |
PoweredUp db ? |
; 1 in normal work, 0 during early phases of the initialization. |
; This field is initialized to zero during memory allocation |
; (see usb_init_controller), set to one by ohci_init when ports of the root hub |
; are powered up, so connect/disconnect events can be handled. |
rb 3 ; alignment |
DoneList dd ? |
; List of descriptors which were processed by the controller and now need |
; to be finalized. |
DoneListEndPtr dd ? |
; Pointer to dword which should receive a pointer to the next item in DoneList. |
; If DoneList is empty, this is a pointer to DoneList itself; |
; otherwise, this is a pointer to NextTD field of the last item in DoneList. |
EhciCompanion dd ? |
; Pointer to usb_controller for EHCI companion, if any, or NULL. |
ends |
if ohci_controller.IntEDs mod 16 |
.err Static endpoint descriptors must be 16-bytes aligned inside ohci_controller |
end if |
; OHCI general transfer descriptor. |
; * The structure describes transfers to be performed on Control, Bulk or |
; Interrupt endpoints. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 16 bytes and corresponds to |
; the General Transfer Descriptor aka general TD from OHCI specification. |
; * The hardware requires 16-bytes alignment of the hardware part, so |
; the entire descriptor must be 16-bytes aligned. Since the allocator |
; (usb_allocate_common) allocates memory sequentially from page start |
; (aligned on 0x1000 bytes), block size for the allocator must be |
; divisible by 16; usb1_allocate_generic_td ensures this. |
struct ohci_gtd |
; ------------------------------ hardware fields ------------------------------ |
; All addresses in this part are physical. |
Flags dd ? |
; 1. Lower 18 bits (bits 0-17) are ignored and not modified by the hardware. |
; 2. Next bit (bit 18) is bufferRounding bit. If this bit is 0, then the last |
; data packet must exactly fill the defined data buffer. If this bit is 1, |
; then the last data packet may be smaller than the defined buffer without |
; causing an error condition on the TD. |
; 3. Next 2 bits (bits 19-20) are Direction field. This field indicates the |
; direction of data flow. If the Direction field in the ED is OUT or IN, |
; this field is ignored and the direction from the ED is used instead. |
; Otherwise, 0 = SETUP, 1 = OUT, 2 = IN, 3 is reserved. |
; 4. Next 3 bits (bits 21-23) are DelayInterrupt field. This field contains |
; the interrupt delay count for this TD. When a TD is complete, the hardware |
; may wait up to DelayInterrupt frames before generating an interrupt. |
; If DelayInterrupt is 7 (maximum possible), then there is no interrupt |
; associated with completion of this TD. |
; 5. Next 2 bits (bits 24-25) are DataToggle field. This field is used to |
; generate/compare the data PID value (DATA0 or DATA1). It is updated after |
; each successful transmission/reception of a data packet. The bit 25 |
; is 0 when the data toggle value is acquired from the toggleCarry field in |
; the ED and 1 when the data toggle value is taken from the bit 24. |
; 6. Next 2 bits (bits 26-27) are ErrorCount field. For each transmission |
; error, this value is incremented. If ErrorCount is 2 and another error |
; occurs, the TD is retired with error. When a transaction completes without |
; error, ErrorCount is reset to 0. |
; 7. Upper 4 bits (bits 28-31) are ConditionCode field. This field contains |
; the status of the last attempted transaction, one of USB_STATUS_* values. |
CurBufPtr dd ? |
; Physical address of the next memory location that will be accessed for |
; transfer to/from the endpoint. 0 means zero-length data packet or that all |
; bytes have been transferred. |
NextTD dd ? |
; This field has different meanings depending on the status of the descriptor. |
; When the descriptor is queued for processing, but not yet processed: |
; Physical address of the next TD for the endpoint. |
; When the descriptor is processed by hardware, but not yet by software: |
; Physical address of the previous processed TD. |
; When the descriptor is processed by the IRQ handler, but not yet completed: |
; Virtual pointer to the next processed TD. |
BufEnd dd ? |
; Physical address of the last byte in the buffer for this TD. |
dd ? ; padding to align with uhci_gtd |
ends |
; OHCI isochronous transfer descriptor. |
; * The structure describes transfers to be performed on Isochronous endpoints. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 32 bytes and corresponds to |
; the Isochronous Transfer Descriptor aka isochronous TD from OHCI |
; specification. |
; * The hardware requires 32-bytes alignment of the hardware part, so |
; the entire descriptor must be 32-bytes aligned. |
; * The isochronous endpoints are not supported yet, so only hardware part is |
; defined at the moment. |
struct ohci_itd |
StartingFrame dw ? |
; This field contains the low order 16 bits of the frame number in which the |
; first data packet of the Isochronous TD is to be sent. |
Flags dw ? |
; 1. Lower 5 bits (bits 0-4) are ignored and not modified by the hardware. |
; 2. Next 3 bits (bits 5-7) are DelayInterrupt field. |
; 3. Next 3 bits (bits 8-10) are FrameCount field. The TD describes |
; FrameCount+1 data packets. |
; 4. Next bit (bit 11) is ignored and not modified by the hardware. |
; 5. Upper 4 bits (bits 12-15) are ConditionCode field. This field contains |
; the completion code, one of USB_STATUS_* values, when the TD is moved to |
; the Done Queue. |
BufPage0 dd ? |
; Lower 12 bits are ignored and not modified by the hardware. |
; With masked 12 bits this field is the physical page containing all buffers. |
NextTD dd ? |
; Physical address of the next TD in the transfer queue. |
BufEnd dd ? |
; Physical address of the last byte in the buffer. |
OffsetArray rw 8 |
; Initialized by software, read by hardware: Offset for packet 0..7. |
; Used to determine size and starting address of an isochronous data packet. |
; Written by hardware, read by software: PacketStatusWord for packet 0..7. |
; Contains completion code and, if applicable, size received for an isochronous |
; data packet. |
ends |
; Description of OHCI-specific data and functions for |
; controller-independent code. |
; Implements the structure usb_hardware_func from hccommon.inc for OHCI. |
iglobal |
align 4 |
ohci_hardware_func: |
dd USBHC_VERSION |
dd 'OHCI' |
dd sizeof.ohci_controller |
dd ohci_kickoff_bios |
dd ohci_init |
dd ohci_process_deferred |
dd ohci_set_device_address |
dd ohci_get_device_address |
dd ohci_port_disable |
dd ohci_new_port.reset |
dd ohci_set_endpoint_packet_size |
dd ohci_alloc_pipe |
dd ohci_free_pipe |
dd ohci_init_pipe |
dd ohci_unlink_pipe |
dd ohci_alloc_gtd |
dd ohci_free_gtd |
dd ohci_alloc_transfer |
dd ohci_insert_transfer |
dd ohci_new_device |
ohci_name db 'OHCI',0 |
endg |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Called once when driver is loading and once at shutdown. |
; When loading, must initialize itself, register itself in the system |
; and return eax = value obtained when registering. |
proc start |
virtual at esp |
dd ? ; return address |
.reason dd ? ; DRV_ENTRY or DRV_EXIT |
.cmdline dd ? ; normally NULL |
end virtual |
cmp [.reason], DRV_ENTRY |
jnz .nothing |
mov ecx, ohci_ep_mutex |
and dword [ecx-4], 0 |
invoke MutexInit |
mov ecx, ohci_gtd_mutex |
and dword [ecx-4], 0 |
invoke MutexInit |
push esi edi |
mov esi, [USBHCFunc] |
mov edi, usbhc_api |
movi ecx, sizeof.usbhc_func/4 |
rep movsd |
pop edi esi |
invoke RegUSBDriver, ohci_name, 0, ohci_hardware_func |
.nothing: |
ret |
endp |
; Controller-specific initialization function. |
; Called from usb_init_controller. Initializes the hardware and |
; OHCI-specific parts of software structures. |
; eax = pointer to ohci_controller to be initialized |
; [ebp-4] = pcidevice |
proc ohci_init |
; inherit some variables from the parent (usb_init_controller) |
.devfn equ ebp - 4 |
.bus equ ebp - 3 |
; 1. Store pointer to ohci_controller for further use. |
push eax |
mov edi, eax |
; 2. Initialize hardware fields of ohci_controller. |
; Namely, InterruptTable needs to be initialized with |
; physical addresses of heads of first 32 Periodic lists. |
; Note that all static heads fit in one page, so one call |
; to get_pg_addr is sufficient. |
if (ohci_controller.IntEDs / 0x1000) <> (ohci_controller.BulkED / 0x1000) |
.err assertion failed |
end if |
if ohci_controller.IntEDs >= 0x1000 |
.err assertion failed |
end if |
lea esi, [eax+ohci_controller.IntEDs+32*sizeof.ohci_static_ep] |
invoke GetPgAddr |
add eax, ohci_controller.IntEDs |
movi ecx, 32 |
mov edx, ecx |
@@: |
stosd |
add eax, sizeof.ohci_static_ep |
loop @b |
; 3. Initialize static heads ohci_controller.IntEDs, .ControlED, .BulkED. |
; Use the loop over groups: first group consists of first 32 Periodic |
; descriptors, next group consists of next 16 Periodic descriptors, |
; ..., last group consists of the last Periodic descriptor. |
; 3a. Prepare for the loop. |
; make edi point to start of ohci_controller.IntEDs, |
; other registers are already set. |
; -128 fits in one byte, +128 does not fit. |
sub edi, -128 |
; 3b. Loop over groups. On every iteration: |
; edx = size of group, edi = pointer to the current group, |
; esi = pointer to the next group, eax = physical address of the next group. |
.init_static_eds: |
; 3c. Get the size of the next group. |
shr edx, 1 |
; 3d. Exit the loop if there is no next group. |
jz .init_static_eds_done |
; 3e. Initialize the first half of the current group. |
; Advance edi to the second half. |
push eax esi |
call ohci_init_static_ep_group |
pop esi eax |
; 3f. Initialize the second half of the current group |
; with the same values. |
; Advance edi to the next group, esi/eax to the next of the next group. |
call ohci_init_static_ep_group |
jmp .init_static_eds |
.init_static_eds_done: |
; 3g. Initialize the head of the last Periodic list. |
xor eax, eax |
xor esi, esi |
call ohci_init_static_endpoint |
; 3i. Initialize the heads of Control and Bulk lists. |
call ohci_init_static_endpoint |
call ohci_init_static_endpoint |
; 4. Create a virtual memory area to talk with the controller. |
; 4a. Enable memory & bus master access. |
invoke PciRead16, dword [.bus], dword [.devfn], 4 |
or al, 6 |
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax |
; 4b. Read memory base address. |
invoke PciRead32, dword [.bus], dword [.devfn], 10h |
and al, not 0Fh |
; 4c. Create mapping for physical memory. 256 bytes are sufficient. |
invoke MapIoMem, eax, 100h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .fail |
stosd ; fill ohci_controller.MMIOBase |
xchg eax, edi |
; now edi = MMIOBase |
; 5. Reset the controller if needed. |
; 5a. Check operational state. |
; 0 = reset, 1 = resume, 2 = operational, 3 = suspended |
mov eax, [edi+OhciControlReg] |
and al, 3 shl 6 |
cmp al, 2 shl 6 |
jz .operational |
; 5b. State is not operational, reset is needed. |
.reset: |
; 5c. Save FmInterval register. |
pushd [edi+OhciFmIntervalReg] |
; 5d. Issue software reset and wait up to 10ms, checking status every 1 ms. |
movi ecx, 1 |
movi edx, 10 |
mov [edi+OhciCommandStatusReg], ecx |
@@: |
mov esi, ecx |
invoke Sleep |
test [edi+OhciCommandStatusReg], ecx |
jz .resetdone |
dec edx |
jnz @b |
pop eax |
dbgstr 'controller reset timeout' |
jmp .fail_unmap |
.resetdone: |
; 5e. Restore FmInterval register. |
pop eax |
mov edx, eax |
and edx, 3FFFh |
jz .setfminterval |
cmp dx, 2EDFh ; default value? |
jnz @f ; assume that BIOS has configured the value |
.setfminterval: |
mov eax, 27792EDFh ; default value |
@@: |
mov [edi+OhciFmIntervalReg], eax |
; 5f. Set PeriodicStart to 90% of FmInterval. |
movzx eax, ax |
; Two following lines are equivalent to eax = floor(eax * 0.9) |
; for any 0 <= eax < 1C71C71Dh, which of course is far from maximum 0FFFFh. |
mov edx, 0E6666667h |
mul edx |
mov [edi+OhciPeriodicStartReg], edx |
.operational: |
; 6. Setup controller registers. |
pop esi ; restore pointer to ohci_controller saved in step 1 |
; 6a. Physical address of HCCA. |
mov eax, esi |
invoke GetPgAddr |
mov [edi+OhciHCCAReg], eax |
; 6b. Transition to operational state and clear all Enable bits. |
mov cl, 2 shl 6 |
mov [edi+OhciControlReg], ecx |
; 6c. Physical addresses of head of Control and Bulk lists. |
if ohci_controller.BulkED >= 0x1000 |
.err assertion failed |
end if |
add eax, ohci_controller.ControlED |
mov [edi+OhciControlHeadEDReg], eax |
add eax, ohci_controller.BulkED - ohci_controller.ControlED |
mov [edi+OhciBulkHeadEDReg], eax |
; 6d. Zero Head registers: there are no active Control and Bulk descriptors yet. |
xor eax, eax |
; mov [edi+OhciPeriodCurrentEDReg], eax |
mov [edi+OhciControlCurrentEDReg], eax |
mov [edi+OhciBulkCurrentEDReg], eax |
; mov [edi+OhciDoneHeadReg], eax |
; 6e. Enable processing of all lists with control:bulk ratio = 1:1. |
mov dword [edi+OhciControlReg], 10111100b |
; 7. Find the EHCI companion. |
; Note: this assumes that EHCI is initialized before USB1 companions. |
add esi, sizeof.ohci_controller |
mov ebx, dword [.devfn] |
invoke usbhc_api.usb_find_ehci_companion |
mov [esi+ohci_controller.EhciCompanion-sizeof.ohci_controller], eax |
; 8. Get number of ports. |
mov eax, [edi+OhciRhDescriptorAReg] |
and eax, 0xF |
mov [esi+usb_controller.NumPorts], eax |
; 9. Initialize DoneListEndPtr to point to DoneList. |
lea eax, [esi+ohci_controller.DoneList-sizeof.ohci_controller] |
mov [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], eax |
; 10. Hook interrupt. |
invoke PciRead8, dword [.bus], dword [.devfn], 3Ch |
; al = IRQ |
movzx eax, al |
invoke AttachIntHandler, eax, ohci_irq, esi |
; 11. Enable controller interrupt on HcDoneHead writeback and RootHubStatusChange. |
mov dword [edi+OhciInterruptEnableReg], 80000042h |
DEBUGF 1,'K : OHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] |
; 12. Initialize ports of the controller. |
; 12a. Initiate power up, disable all ports, clear all "changed" bits. |
mov dword [edi+OhciRhStatusReg], 10000h ; SetGlobalPower |
xor ecx, ecx |
@@: |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1F0101h ; SetPortPower+ClearPortEnable+clear "changed" bits |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb @b |
; 12b. Wait for power up. |
; VirtualBox has AReg == 0, delay_ms doesn't like zero value; ignore zero delay |
push esi |
mov esi, [edi+OhciRhDescriptorAReg] |
shr esi, 24 |
add esi, esi |
jz @f |
invoke Sleep |
@@: |
pop esi |
; 12c. Ports are powered up; now it is ok to process connect/disconnect events. |
mov [esi+ohci_controller.PoweredUp-sizeof.ohci_controller], 1 |
; IRQ handler doesn't accept connect/disconnect events before this point |
; 12d. We could miss some events while waiting for powering up; |
; scan all ports manually and check for connected devices. |
xor ecx, ecx |
.port_loop: |
test dword [edi+OhciRhPortStatusReg+ecx*4], 1 |
jz .next_port |
; There is a connected device; mark the port as 'connected' |
; and save the connected time. |
; Note that ConnectedTime must be set before 'connected' mark, |
; otherwise the code in ohci_process_deferred could use incorrect time. |
invoke GetTimerTicks |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
lock bts [esi+usb_controller.NewConnected], ecx |
.next_port: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .port_loop |
; 13. Return pointer to usb_controller. |
xchg eax, esi |
ret |
.fail_unmap: |
; On error after step 5, release the virtual memory area. |
invoke FreeKernelSpace, edi |
.fail: |
; On error, free the ohci_controller structure and return zero. |
; Note that the pointer was placed in the stack at step 1. |
; Note also that there can be no errors after step 6, |
; where that pointer is popped from the stack. |
pop ecx |
.nothing: |
xor eax, eax |
ret |
endp |
; Helper procedure for step 3 of ohci_init. |
; Initializes the static head of one list. |
; eax = physical address of the "next" list, esi = pointer to the "next" list, |
; edi = pointer to head to initialize. |
; Advances edi to the next head, keeps eax/esi. |
proc ohci_init_static_endpoint |
mov byte [edi+ohci_static_ep.Flags+1], 1 shl (14 - 8) ; sKip this endpoint |
mov [edi+ohci_static_ep.NextED], eax |
mov [edi+ohci_static_ep.NextList], esi |
add edi, ohci_static_ep.SoftwarePart |
invoke usbhc_api.usb_init_static_endpoint |
add edi, sizeof.ohci_static_ep - ohci_static_ep.SoftwarePart |
ret |
endp |
; Helper procedure for step 3 of ohci_init. |
; Initializes one half of group of static heads. |
; edx = size of the next group = half of size of the group, |
; edi = pointer to the group, eax = physical address of the next group, |
; esi = pointer to the next group. |
; Advances eax, esi, edi to next group, keeps edx. |
proc ohci_init_static_ep_group |
push edx |
@@: |
call ohci_init_static_endpoint |
add eax, sizeof.ohci_static_ep |
add esi, sizeof.ohci_static_ep |
dec edx |
jnz @b |
pop edx |
ret |
endp |
; Controller-specific pre-initialization function: take ownership from BIOS. |
; Some BIOSes, although not all of them, provide legacy emulation |
; for USB keyboard and/or mice as PS/2-devices. In this case, |
; we must notify the BIOS that we don't need that emulation and know how to |
; deal with USB devices. |
proc ohci_kickoff_bios |
; 1. Get the physical address of MMIO registers. |
invoke PciRead32, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 10h |
and al, not 0Fh |
; 2. Create mapping for physical memory. 256 bytes are sufficient. |
invoke MapIoMem, eax, 100h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .nothing |
; 3. Some BIOSes enable controller interrupts as a result of giving |
; controller away. At this point the system knows nothing about how to serve |
; OHCI interrupts, so such an interrupt will send the system into an infinite |
; loop handling the same IRQ again and again. Thus, we need to block OHCI |
; interrupts. We can't do this at the controller level until step 5, |
; because the controller is currently owned by BIOS, so we block all hardware |
; interrupts on this processor until step 5. |
pushf |
cli |
; 4. Take the ownership over the controller. |
; 4a. Check whether BIOS handles this controller at all. |
mov edx, 100h |
test dword [eax+OhciControlReg], edx |
jz .has_ownership |
; 4b. Send "take ownership" command to the BIOS. |
; (This should generate SMI, BIOS should release its ownership in SMI handler.) |
mov dword [eax+OhciCommandStatusReg], 8 |
; 4c. Wait for result no more than 50 ms, checking for status every 1 ms. |
movi ecx, 50 |
@@: |
test dword [eax+OhciControlReg], edx |
jz .has_ownership |
push esi |
movi esi, 1 |
invoke Sleep |
pop esi |
loop @b |
dbgstr 'warning: taking OHCI ownership from BIOS timeout' |
.has_ownership: |
; 5. Disable all controller interrupts until the system will be ready to |
; process them. |
mov dword [eax+OhciInterruptDisableReg], 0C000007Fh |
; 6. Now we can unblock interrupts in the processor. |
popf |
; 7. Release memory mapping created in step 2 and return. |
invoke FreeKernelSpace, eax |
.nothing: |
ret |
endp |
; IRQ handler for OHCI controllers. |
ohci_irq.noint: |
; Not our interrupt: restore registers and return zero. |
xor eax, eax |
pop edi esi ebx |
ret |
proc ohci_irq |
push ebx esi edi ; save used registers to be cdecl |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.controller dd ? |
end virtual |
; 1. ebx will hold whether some deferred processing is needed, |
; that cannot be done from the interrupt handler. Initialize to zero. |
xor ebx, ebx |
; 2. Get the mask of events which should be processed. |
mov esi, [.controller] |
mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
mov eax, [edi+OhciInterruptStatusReg] |
; 3. Check whether that interrupt has been generated by our controller. |
; (One IRQ can be shared by several devices.) |
and eax, [edi+OhciInterruptEnableReg] |
jz .noint |
; 4. Get the physical pointer to the last processed descriptor. |
; All processed descriptors form single-linked list from last to first |
; with the help of NextTD field. The list is restarted every time when |
; the controller writes to DoneHead, so grab the pointer now (before the next |
; step) or it could be lost (the controller could write new value to DoneHead |
; any time after WorkDone bit is cleared in OhciInterruptStatusReg). |
mov ecx, [esi+ohci_controller.DoneHead-sizeof.ohci_controller] |
and ecx, not 1 |
; 5. Clear the events we know of. |
; Note that this should be done before processing of events: |
; new events could arise while we are processing those, this way we won't lose |
; them (the controller would generate another interrupt |
; after completion of this one). |
mov [edi+OhciInterruptStatusReg], eax |
; 6. Save the mask of events for further reference. |
push eax |
; 7. Handle 'transfer is done' events. |
; 7a. Test whether there are such events. |
test al, 2 |
jz .skip_donehead |
; There are some 'transfer is done' events, processed descriptors are linked |
; through physical addresses in the reverse order. |
; We can't do much in an interrupt handler, since callbacks could require |
; waiting for locks and that can't be done in an interrupt handler. |
; However, we can't also just defer all work to the USB thread, since |
; it is possible that previous lists are not yet processed and it is hard |
; to store unlimited number of list heads. Thus, we reverse the current list, |
; append it to end of the previous list (if there is one) and defer other |
; processing to the USB thread; this way there always is no more than one list |
; (possibly joined from several controller-reported lists). |
; The list traversal requires converting physical addresses to virtual pointers, |
; so we may as well store pointers instead of physical addresses. |
; 7b. Prepare for the reversing loop. |
push ebx |
xor ebx, ebx |
test ecx, ecx |
jz .tddone |
mov eax, [ohci_gtd_first_page] |
invoke usbhc_api.usb_td_to_virt |
test eax, eax |
jz .tddone |
lea edx, [eax+ohci_gtd.NextTD] |
; 7c. Reverse the list, converting physical to virtual. On every iteration: |
; ecx = physical address of the current item |
; eax = virtual pointer to the current item |
; edx = virtual pointer to the last item.NextTD (first in the reverse list) |
; ebx = virtual pointer to the next item (previous in the reverse list) |
.tdloop: |
mov ecx, [eax+ohci_gtd.NextTD] |
mov [eax+ohci_gtd.NextTD], ebx |
lea ebx, [eax+sizeof.ohci_gtd] |
test ecx, ecx |
jz .tddone |
mov eax, [ohci_gtd_first_page] |
invoke usbhc_api.usb_td_to_virt |
test eax, eax |
jnz .tdloop |
.tddone: |
mov ecx, ebx |
pop ebx |
; 7d. The list is reversed, |
; ecx = pointer to the first item, edx = pointer to the last item.NextTD. |
; If the list is empty (unusual case), step 7 is done. |
test ecx, ecx |
jz .skip_donehead |
; 7e. Otherwise, append this list to the end of previous one. |
; Note that in theory the interrupt handler and the USB thread |
; could execute in parallel. |
.append_restart: |
; Atomically get DoneListEndPtr in eax and set it to edx. |
mov eax, [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller] |
lock cmpxchg [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], edx |
jnz .append_restart |
; Store pointer to the new list. |
; Note: we cannot perform any operations with [DoneListEndPtr] |
; until we switch DoneListEndPtr to a new descriptor: |
; it is possible that after first line of .append_restart loop |
; ohci_process_deferred obtains the control, finishes processing |
; of the old list, sets DoneListEndPtr to address of DoneList, |
; frees all old descriptors, so eax would point to invalid location. |
; This way, .append_restart loop would detect that DoneListEndPtr |
; has changed, so eax needs to be re-read. |
mov [eax], ecx |
; 7f. Notify the USB thread that there is new work. |
inc ebx |
.skip_donehead: |
; 8. Handle start-of-frame events. |
; 8a. Test whether there are such events. |
test byte [esp], 4 |
jz .skip_sof |
; We enable SOF interrupt only when some pipes are waiting after changes. |
spin_lock_irqsave [esi+usb_controller.WaitSpinlock] |
; 8b. Make sure that there was at least one frame update |
; since the request. If not, wait for the next SOF. |
movzx eax, [esi+ohci_controller.FrameNumber-sizeof.ohci_controller] |
cmp eax, [esi+usb_controller.StartWaitFrame] |
jz .sof_unlock |
; 8c. Copy WaitPipeRequest* to ReadyPipeHead*. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax |
; 8d. It is possible that pipe change is due to removal and |
; Control/BulkCurrentED registers still point to one of pipes to be removed. |
; The code responsible for disconnect events has temporarily stopped |
; Control/Bulk processing, so it is safe to clear Control/BulkCurrentED. |
; After that, restart processing. |
xor edx, edx |
mov [edi+OhciControlCurrentEDReg], edx |
mov [edi+OhciBulkCurrentEDReg], edx |
mov dword [edi+OhciCommandStatusReg], 6 |
or dword [edi+OhciControlReg], 30h |
; 8e. Disable further interrupts on SOF. |
; Note: OhciInterruptEnableReg/OhciInterruptDisableReg have unusual semantics. |
mov dword [edi+OhciInterruptDisableReg], 4 |
; Notify the USB thread that there is new work (with pipes from ReadyPipeHead*). |
inc ebx |
.sof_unlock: |
spin_unlock_irqrestore [esi+usb_controller.RemoveSpinlock] |
.skip_sof: |
; Handle roothub events. |
; 9. Test whether there are such events. |
test byte [esp], 40h |
jz .skip_roothub |
; 10. Check the status of the roothub itself. |
; 10a. Global overcurrent? |
test dword [edi+OhciRhStatusReg], 2 |
jz @f |
; Note: this needs work. |
dbgstr 'global overcurrent' |
@@: |
; 10b. Clear roothub events. |
mov dword [edi+OhciRhStatusReg], 80020000h |
; 11. Check the status of individual ports. |
; Look for connect/disconnect and reset events. |
; 11a. Prepare for the loop: start from port 0. |
xor ecx, ecx |
.portloop: |
; 11b. Get the port status and changes of it. |
; Accumulate change information. |
; Look to "11.12.3 Port Change Information Processing" of the USB2 spec. |
xor eax, eax |
.accloop: |
mov edx, [edi+OhciRhPortStatusReg+ecx*4] |
xor ax, ax |
or eax, edx |
test edx, 1F0000h |
jz .accdone |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1F0000h |
jmp .accloop |
.accdone: |
; debugging output, not needed for work |
; test eax, 1F0000h |
; jz @f |
; DEBUGF 1,'K : ohci irq [%d] status of port %d is %x\n',[timer_ticks],ecx,eax |
;@@: |
; 11c. Ignore any events until all ports are powered up. |
; They will be processed by ohci_init. |
cmp [esi+ohci_controller.PoweredUp-sizeof.ohci_controller], 0 |
jz .nextport |
; Handle changing of connection status. |
test eax, 10000h |
jz .nocsc |
; There was a connect or disconnect event at this port. |
; 11d. Disconnect the old device on this port, if any. |
; if the port was resetting, indicate fail and signal |
cmp cl, [esi+usb_controller.ResettingPort] |
jnz @f |
mov [esi+usb_controller.ResettingStatus], -1 |
inc ebx |
@@: |
lock bts [esi+usb_controller.NewDisconnected], ecx |
; notify the USB thread that new work is waiting |
inc ebx |
; 11e. Change connected status. For the connection event, also |
; store the connection time; any further processing is permitted only |
; after USB_CONNECT_DELAY ticks. |
test al, 1 |
jz .disconnect |
; Note: ConnectedTime must be stored before setting the 'connected' bit, |
; otherwise ohci_process_deferred could use an old time. |
invoke GetTimerTicks |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
lock bts [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.disconnect: |
lock btr [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.nocsc: |
; 11f. Process 'reset done' events. |
test eax, 100000h |
jz .nextport |
test al, 10h |
jnz .nextport |
invoke GetTimerTicks |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 2 |
inc ebx |
.nextport: |
; 11g. Continue the loop for the next port. |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_roothub: |
; 12. Restore the stack after step 6. |
pop eax |
; 13. Notify the USB thread if some deferred processing is required. |
invoke usbhc_api.usb_wakeup_if_needed |
; 14. Interrupt processed; return something non-zero. |
mov al, 1 |
pop edi esi ebx ; restore used registers to be stdcall |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; and stores USB device address in the ohci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
proc ohci_set_device_address |
mov byte [ebx+ohci_pipe.Flags-sizeof.ohci_pipe], cl |
; Wait until the hardware will forget the old value. |
jmp [usbhc_api.usb_subscribe_control] |
endp |
; This procedure returns USB device address from the usb_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = endpoint address |
proc ohci_get_device_address |
mov eax, [ebx+ohci_pipe.Flags-sizeof.ohci_pipe] |
and eax, 7Fh |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; if the device does not accept SET_ADDRESS command and needs |
; to be disabled at the port level. |
; in: esi -> usb_controller, ecx = port |
proc ohci_port_disable |
mov edx, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
mov dword [edx+OhciRhPortStatusReg+ecx*4], 1 |
ret |
endp |
; This procedure is called from usb_get_descr8_callback when |
; the packet size for zero endpoint becomes known and |
; stores the packet size in ohci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
proc ohci_set_endpoint_packet_size |
mov byte [ebx+ohci_pipe.Flags+2-sizeof.ohci_pipe], cl |
; Wait until the hardware will forget the old value. |
jmp [usbhc_api.usb_subscribe_control] |
endp |
; This procedure is called from API usb_open_pipe and processes |
; the controller-specific part of this API. See docs. |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
proc ohci_init_pipe |
virtual at ebp-12 |
.speed db ? |
rb 3 |
.bandwidth dd ? |
.target dd ? |
rd 2 |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; 1. Initialize the queue of transfer descriptors: empty. |
sub eax, sizeof.ohci_gtd |
invoke GetPhysAddr |
mov [edi+ohci_pipe.TailP-sizeof.ohci_pipe], eax |
mov [edi+ohci_pipe.HeadP-sizeof.ohci_pipe], eax |
; 2. Generate ohci_pipe.Flags, see the description in ohci_pipe. |
mov eax, [ecx+ohci_pipe.Flags-sizeof.ohci_pipe] |
and eax, 0x207F ; keep Speed bit and FunctionAddress |
mov edx, [.endpoint] |
and edx, 15 |
shl edx, 7 |
or eax, edx |
mov [edi+ohci_pipe.Flags-sizeof.ohci_pipe], eax |
bt eax, 13 |
setc [.speed] |
mov eax, [.maxpacket] |
mov word [edi+ohci_pipe.Flags+2-sizeof.ohci_pipe], ax |
cmp [.type], CONTROL_PIPE |
jz @f |
test byte [.endpoint], 80h |
setnz al |
inc eax |
shl al, 3 |
or byte [edi+ohci_pipe.Flags+1-sizeof.ohci_pipe], al |
@@: |
; 3. Insert the new pipe to the corresponding list of endpoints. |
; 3a. Use Control list for control pipes, Bulk list for bulk pipes. |
lea edx, [esi+ohci_controller.ControlED.SoftwarePart-sizeof.ohci_controller] |
cmp [.type], BULK_PIPE |
jb .insert ; control pipe |
lea edx, [esi+ohci_controller.BulkED.SoftwarePart-sizeof.ohci_controller] |
jz .insert ; bulk pipe |
.interrupt_pipe: |
; 3b. For interrupt pipes, let the scheduler select the appropriate list |
; based on the current bandwidth distribution and the requested bandwidth. |
; This could fail if the requested bandwidth is not available; |
; if so, return an error. |
lea edx, [esi + ohci_controller.IntEDs - sizeof.ohci_controller] |
lea eax, [esi + ohci_controller.IntEDs + 32*sizeof.ohci_static_ep - sizeof.ohci_controller] |
movi ecx, 64 |
call usb1_select_interrupt_list |
test edx, edx |
jz .return0 |
; 3c. Insert endpoint at edi to the head of list in edx. |
; Inserting to tail would work as well, |
; but let's be consistent with other controllers. |
.insert: |
mov ecx, [edx+usb_pipe.NextVirt] |
mov [edi+usb_pipe.NextVirt], ecx |
mov [edi+usb_pipe.PrevVirt], edx |
mov [ecx+usb_pipe.PrevVirt], edi |
mov [edx+usb_pipe.NextVirt], edi |
mov ecx, [edx+ohci_pipe.NextED-sizeof.ohci_pipe] |
mov [edi+ohci_pipe.NextED-sizeof.ohci_pipe], ecx |
lea eax, [edi-sizeof.ohci_pipe] |
invoke GetPhysAddr |
mov [edx+ohci_pipe.NextED-sizeof.ohci_pipe], eax |
; 4. Return something non-zero. |
ret |
.return0: |
xor eax, eax |
ret |
endp |
; This function is called from ohci_process_deferred when |
; a new device was connected at least USB_CONNECT_DELAY ticks |
; and therefore is ready to be configured. |
; ecx = port, esi -> usb_controller |
proc ohci_new_port |
; test whether we are configuring another port |
; if so, postpone configuring and return |
bts [esi+usb_controller.PendingPorts], ecx |
cmp [esi+usb_controller.ResettingPort], -1 |
jnz .nothing |
btr [esi+usb_controller.PendingPorts], ecx |
; fall through to ohci_new_port.reset |
; This function is called from usb_test_pending_port. |
; It starts reset signalling for the port. Note that in USB first stages |
; of configuration can not be done for several ports in parallel. |
.reset: |
; reset port |
and [esi+usb_controller.ResettingHub], 0 |
mov [esi+usb_controller.ResettingPort], cl |
; Note: setting status must be the last action: |
; it is possible that the device has been disconnected |
; after timeout of USB_CONNECT_DELAY but before call to ohci_new_port. |
; In this case, ohci_irq would not set reset status to 'failed', |
; because ohci_irq would not know that this port is to be reset. |
; However, the hardware would generate another interrupt |
; in a response to reset a disconnected port, and this time |
; ohci_irq knows that it needs to generate 'reset failed' event |
; (because ResettingPort is now filled). |
push edi |
mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 10h |
pop edi |
.nothing: |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and allocates required packets for the given transfer. |
; ebx = pipe, other parameters are passed through the stack: |
; buffer,size = data to transfer |
; flags = same as in usb_open_pipe: bit 0 = allow short transfer, other bits reserved |
; td = pointer to the current end-of-queue descriptor |
; direction = |
; 0000b for normal transfers, |
; 1000b for control SETUP transfer, |
; 1101b for control OUT transfer, |
; 1110b for control IN transfer |
; returns eax = pointer to the new end-of-queue descriptor |
; (not included in the queue itself) or 0 on error |
proc ohci_alloc_transfer stdcall uses edi, \ |
buffer:dword, size:dword, flags:dword, td:dword, direction:dword |
locals |
origTD dd ? |
packetSize dd ? ; must be the last variable, see usb_init_transfer |
endl |
; 1. Save original value of td: |
; it will be useful for rollback if something would fail. |
mov eax, [td] |
mov [origTD], eax |
; One transfer descriptor can describe up to two pages. |
; In the worst case (when the buffer is something*1000h+0FFFh) |
; this corresponds to 1001h bytes. If the requested size is |
; greater, we should split the transfer into several descriptors. |
; Boundaries to split must be multiples of endpoint transfer size |
; to avoid short packets except in the end of the transfer. |
cmp [size], 1001h |
jbe .lastpacket |
; 2. While the remaining data cannot fit in one packet, |
; allocate full-sized descriptors. |
; 2a. Calculate size of one descriptor: must be a multiple of transfer size |
; and must be not greater than 1001h. |
movzx ecx, word [ebx+ohci_pipe.Flags+2-sizeof.ohci_pipe] |
mov eax, 1001h |
xor edx, edx |
mov edi, eax |
div ecx |
sub edi, edx |
; 2b. Allocate in loop. |
mov [packetSize], edi |
.fullpackets: |
call ohci_alloc_packet |
test eax, eax |
jz .fail |
mov [td], eax |
add [buffer], edi |
sub [size], edi |
cmp [size], 1001h |
ja .fullpackets |
; 3. The remaining data can fit in one descriptor; |
; allocate the last descriptor with size = size of remaining data. |
.lastpacket: |
mov eax, [size] |
mov [packetSize], eax |
call ohci_alloc_packet |
test eax, eax |
jz .fail |
; 4. Enable an immediate interrupt on completion of the last packet. |
and byte [ecx+ohci_gtd.Flags+2-sizeof.ohci_gtd], not (7 shl (21-16)) |
; 5. If a short transfer is ok for a caller, set the corresponding bit in |
; the last descriptor, but not in others. |
; Note: even if the caller says that short transfers are ok, |
; all packets except the last one are marked as 'must be complete': |
; if one of them will be short, the software intervention is needed |
; to skip remaining packets; ohci_process_finalized_td will handle this |
; transparently to the caller. |
test [flags], 1 |
jz @f |
or byte [ecx+ohci_gtd.Flags+2-sizeof.ohci_gtd], 1 shl (18-16) |
@@: |
ret |
.fail: |
mov edi, ohci_hardware_func |
mov eax, [td] |
invoke usbhc_api.usb_undo_tds, [origTD] |
xor eax, eax |
ret |
endp |
; Helper procedure for ohci_alloc_transfer. |
; Allocates and initializes one transfer descriptor. |
; ebx = pipe, other parameters are passed through the stack; |
; fills the current last descriptor and |
; returns eax = next descriptor (not filled). |
proc ohci_alloc_packet |
; inherit some variables from the parent ohci_alloc_transfer |
virtual at ebp-8 |
.origTD dd ? |
.packetSize dd ? |
rd 2 |
.buffer dd ? |
.transferSize dd ? |
.Flags dd ? |
.td dd ? |
.direction dd ? |
end virtual |
; 1. Allocate the next TD. |
call ohci_alloc_gtd |
test eax, eax |
jz .nothing |
; 2. Initialize controller-independent parts of both TDs. |
push eax |
invoke usbhc_api.usb_init_transfer |
pop eax |
; 3. Save the returned value (next descriptor). |
push eax |
; 4. Store the physical address of the next descriptor. |
sub eax, sizeof.ohci_gtd |
invoke GetPhysAddr |
mov [ecx+ohci_gtd.NextTD-sizeof.ohci_gtd], eax |
; 5. For zero-length transfers, store zero in both fields for buffer addresses. |
; Otherwise, fill them with real values. |
xor eax, eax |
mov [ecx+ohci_gtd.CurBufPtr-sizeof.ohci_gtd], eax |
mov [ecx+ohci_gtd.BufEnd-sizeof.ohci_gtd], eax |
cmp [.packetSize], eax |
jz @f |
mov eax, [.buffer] |
invoke GetPhysAddr |
mov [ecx+ohci_gtd.CurBufPtr-sizeof.ohci_gtd], eax |
mov eax, [.buffer] |
add eax, [.packetSize] |
dec eax |
invoke GetPhysAddr |
mov [ecx+ohci_gtd.BufEnd-sizeof.ohci_gtd], eax |
@@: |
; 6. Generate Flags field: |
; - set bufferRounding (bit 18) to zero = disallow short transfers; |
; for the last transfer in a row, ohci_alloc_transfer would set the real value; |
; - set Direction (bits 19-20) to lower 2 bits of [.direction]; |
; - set DelayInterrupt (bits 21-23) to 7 = do not generate interrupt; |
; for the last transfer in a row, ohci_alloc_transfer would set the real value; |
; - set DataToggle (bits 24-25) to next 2 bits of [.direction]; |
; - set ConditionCode (bits 28-31) to 1111b as a indicator that there was no |
; attempts to perform this transfer yet; |
; - zero all other bits. |
mov eax, [.direction] |
mov edx, eax |
and eax, 3 |
shl eax, 19 |
and edx, (3 shl 2) |
shl edx, 24 - 2 |
lea eax, [eax + edx + (7 shl 21) + (15 shl 28)] |
mov [ecx+ohci_gtd.Flags-sizeof.ohci_gtd], eax |
; 7. Restore the returned value saved in step 3. |
pop eax |
.nothing: |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and activates the transfer which was previously allocated by |
; ohci_alloc_transfer. |
; ecx -> last descriptor for the transfer, ebx -> usb_pipe |
proc ohci_insert_transfer |
; 1. Advance the queue of transfer descriptors. |
mov eax, [ecx+ohci_gtd.NextTD-sizeof.ohci_gtd] |
mov [ebx+ohci_pipe.TailP-sizeof.ohci_pipe], eax |
; 2. For control and bulk pipes, notify the controller that |
; there is new work in control/bulk queue respectively. |
ohci_notify_new_work: |
mov edx, [ebx+usb_pipe.Controller] |
mov edx, [edx+ohci_controller.MMIOBase-sizeof.ohci_controller] |
cmp [ebx+usb_pipe.Type], CONTROL_PIPE |
jz .control |
cmp [ebx+usb_pipe.Type], BULK_PIPE |
jnz .nothing |
.bulk: |
mov dword [edx+OhciCommandStatusReg], 4 |
jmp .nothing |
.control: |
mov dword [edx+OhciCommandStatusReg], 2 |
.nothing: |
ret |
endp |
; This function is called from ohci_process_deferred when |
; a new device has been reset and needs to be configured. |
proc ohci_port_after_reset |
; 1. Get the status. |
; If reset has been failed (device disconnected during reset), |
; continue to next device (if there is one). |
xor eax, eax |
xchg al, [esi+usb_controller.ResettingStatus] |
test al, al |
jns @f |
jmp [usbhc_api.usb_test_pending_port] |
@@: |
; If the controller has disabled the port (e.g. overcurrent), |
; continue to next device (if there is one). |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov eax, [edi+OhciRhPortStatusReg+ecx*4] |
test al, 2 |
jnz @f |
DEBUGF 1,'K : USB port disabled after reset, status=%x\n',eax |
jmp [usbhc_api.usb_test_pending_port] |
@@: |
push ecx |
; 2. Get LowSpeed bit to bit 0 of eax and call the worker procedure |
; to notify the protocol layer about new OHCI device. |
mov eax, [edi+OhciRhPortStatusReg+ecx*4] |
DEBUGF 1,'K : port_after_reset, status of port %d is %x\n',ecx,eax |
shr eax, 9 |
call ohci_new_device |
pop ecx |
; 3. If something at the protocol layer has failed |
; (no memory, no bus address), disable the port and stop the initialization. |
test eax, eax |
jnz .nothing |
.disable_exit: |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1 |
jmp [usbhc_api.usb_test_pending_port] |
.nothing: |
ret |
endp |
; This procedure is called from uhci_port_init and from hub support code |
; when a new device is connected and has been reset. |
; It calls usb_new_device at the protocol layer with correct parameters. |
; in: esi -> usb_controller, eax = speed; |
; OHCI is USB1 device, so only low bit of eax (LowSpeed) is used. |
proc ohci_new_device |
; 1. Clear all bits of speed except bit 0. |
and eax, 1 |
; 2. Store the speed for the protocol layer. |
mov [esi+usb_controller.ResettingSpeed], al |
; 3. Create pseudo-pipe in the stack. |
; See ohci_init_pipe: only .Controller and .Flags fields are used. |
shl eax, 13 |
push esi ; .Controller |
mov ecx, esp |
sub esp, 12 ; ignored fields |
push eax ; .Flags |
; 4. Notify the protocol layer. |
invoke usbhc_api.usb_new_device |
; 5. Cleanup the stack after step 3 and return. |
add esp, 20 |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; processes regular actions and those actions which can't be safely done |
; from interrupt handler. |
; Returns maximal time delta before the next call. |
proc ohci_process_deferred |
push ebx edi ; save used registers to be stdcall |
; 1. Initialize the return value. |
push -1 |
; 2. Process disconnect events. |
invoke usbhc_api.usb_disconnect_stage2 |
; 3. Check for connected devices. |
; If there is a connected device which was connected less than |
; USB_CONNECT_DELAY ticks ago, plan to wake up when the delay will be over. |
; Otherwise, call ohci_new_port. |
mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
xor ecx, ecx |
cmp [esi+usb_controller.NewConnected], ecx |
jz .skip_newconnected |
.portloop: |
bt [esi+usb_controller.NewConnected], ecx |
jnc .noconnect |
; If this port is shared with the EHCI companion and we see the connect event, |
; then the device is USB1 dropped by EHCI, |
; so EHCI has already waited for debounce delay, we can proceed immediately. |
cmp [esi+ohci_controller.EhciCompanion-sizeof.ohci_controller], 0 |
jz .portloop.test_time |
dbgstr 'port is shared with EHCI, skipping initial debounce' |
jmp .connected |
.portloop.test_time: |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ConnectedTime+ecx*4] |
sub eax, USB_CONNECT_DELAY |
jge .connected |
neg eax |
cmp [esp], eax |
jb .nextport |
mov [esp], eax |
jmp .nextport |
.connected: |
lock btr [esi+usb_controller.NewConnected], ecx |
jnc .nextport |
call ohci_new_port |
.noconnect: |
.nextport: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_newconnected: |
; 4. Check for end of reset signalling. If so, call ohci_port_after_reset. |
cmp [esi+usb_controller.ResettingStatus], 2 |
jnz .no_reset_recovery |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_done |
neg eax |
cmp [esp], eax |
jb .skip_roothub |
mov [esp], eax |
jmp .skip_roothub |
.no_reset_recovery: |
cmp [esi+usb_controller.ResettingStatus], 0 |
jz .skip_roothub |
.reset_done: |
call ohci_port_after_reset |
.skip_roothub: |
; 5. Finalize transfers processed by hardware. |
; It is better to perform this step after processing disconnect events, |
; although not strictly obligatory. This way, an active transfer aborted |
; due to disconnect would be handled with more specific USB_STATUS_CLOSED, |
; not USB_STATUS_NORESPONSE. |
; Loop over all items in DoneList, call ohci_process_finalized_td for each. |
xor ebx, ebx |
xchg ebx, [esi+ohci_controller.DoneList-sizeof.ohci_controller] |
.tdloop: |
test ebx, ebx |
jz .tddone |
call ohci_process_finalized_td |
jmp .tdloop |
.tddone: |
; 6. Process wait-done notifications, test for new wait requests. |
; Note: that must be done after steps 2 and 5 which could create new requests. |
; 6a. Call the worker function from main USB code. |
invoke usbhc_api.usb_process_wait_lists |
; 6b. If no new requests, skip the rest of this step. |
test eax, eax |
jz @f |
; 6c. OHCI is not allowed to cache anything; we don't know what is |
; processed right now, but we can be sure that the controller will not |
; use any removed structure starting from the next frame. |
; Schedule SOF event. |
spin_lock_irq [esi+usb_controller.RemoveSpinlock] |
mov eax, [esi+usb_controller.WaitPipeListAsync] |
mov [esi+usb_controller.WaitPipeRequestAsync], eax |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [esi+usb_controller.WaitPipeRequestPeriodic], eax |
; temporarily stop bulk and interrupt processing; |
; this is required for handler of SOF event |
and dword [edi+OhciControlReg], not 30h |
; remember the frame number when processing has been stopped |
; (needs to be done after stopping) |
movzx eax, [esi+ohci_controller.FrameNumber-sizeof.ohci_controller] |
mov [esi+usb_controller.StartWaitFrame], eax |
; make sure that the next SOF will happen after the request |
mov dword [edi+OhciInterruptStatusReg], 4 |
; enable interrupt on SOF |
; Note: OhciInterruptEnableReg/OhciInterruptDisableReg have unusual semantics, |
; so there should be 'mov' here, not 'or' |
mov dword [edi+OhciInterruptEnableReg], 4 |
spin_unlock_irq [esi+usb_controller.RemoveSpinlock] |
@@: |
; 7. Restore the return value and return. |
pop eax |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; Helper procedure for ohci_process_deferred. Processes one completed TD. |
; in: esi -> usb_controller, ebx -> usb_gtd, out: ebx -> next usb_gtd. |
proc ohci_process_finalized_td |
; DEBUGF 1,'K : processing %x\n',ebx |
; 1. Check whether the pipe has been closed, either due to API call or due to |
; disconnect; if so, the callback will be called by usb_pipe_closed with |
; correct status, so go to step 6 with ebx = 0 (do not free the TD). |
mov edx, [ebx+usb_gtd.Pipe] |
test [edx+usb_pipe.Flags], USB_FLAG_CLOSED |
jz @f |
lea eax, [ebx+ohci_gtd.NextTD-sizeof.ohci_gtd] |
xor ebx, ebx |
jmp .next_td2 |
@@: |
; 2. Remove the descriptor from the descriptors queue. |
invoke usbhc_api.usb_unlink_td |
; 3. Get number of bytes that remain to be transferred. |
; If CurBufPtr is zero, everything was transferred. |
xor edx, edx |
cmp [ebx+ohci_gtd.CurBufPtr-sizeof.ohci_gtd], edx |
jz .gotlen |
; Otherwise, the remaining length is |
; (BufEnd and 0xFFF) - (CurBufPtr and 0xFFF) + 1, |
; plus 0x1000 if BufEnd and CurBufPtr are in different pages. |
mov edx, [ebx+ohci_gtd.BufEnd-sizeof.ohci_gtd] |
mov eax, [ebx+ohci_gtd.CurBufPtr-sizeof.ohci_gtd] |
mov ecx, edx |
and edx, 0xFFF |
inc edx |
xor ecx, eax |
and ecx, -0x1000 |
jz @f |
add edx, 0x1000 |
@@: |
and eax, 0xFFF |
sub edx, eax |
.gotlen: |
; The actual length is Length - (remaining length). |
sub edx, [ebx+usb_gtd.Length] |
neg edx |
; 4. Check for error. If so, go to 7. |
push ebx |
mov ecx, [ebx+ohci_gtd.Flags-sizeof.ohci_gtd] |
shr ecx, 28 |
jnz .error |
.notify: |
; 5. Successful completion. |
invoke usbhc_api.usb_process_gtd |
.next_td: |
; 6. Free the current descriptor and advance to the next item. |
; If the current item is the last in the list, |
; set DoneListEndPtr to pointer to DoneList. |
cmp ebx, [esp] |
jz @f |
stdcall ohci_free_gtd, ebx |
@@: |
pop ebx |
lea eax, [ebx+ohci_gtd.NextTD-sizeof.ohci_gtd] |
.next_td2: |
push ebx |
mov ebx, eax |
lea edx, [esi+ohci_controller.DoneList-sizeof.ohci_controller] |
xor ecx, ecx ; no next item |
lock cmpxchg [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], edx |
jz .last |
; The current item is not the last. |
; It is possible, although very rare, that ohci_irq has already advanced |
; DoneListEndPtr, but not yet written NextTD. Wait until NextTD is nonzero. |
@@: |
mov ecx, [ebx] |
test ecx, ecx |
jz @b |
.last: |
pop ebx |
; ecx = the next item |
push ecx |
; Free the current item, set ebx to the next item, continue to 5a. |
test ebx, ebx |
jz @f |
stdcall ohci_free_gtd, ebx |
@@: |
pop ebx |
ret |
.error: |
; 7. There was an error while processing this descriptor. |
; The hardware has stopped processing the queue. |
; 7a. Save status and length. |
push ecx |
push edx |
; DEBUGF 1,'K : TD failed:\n' |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-sizeof.ohci_gtd],[ebx-sizeof.ohci_gtd+4],[ebx-sizeof.ohci_gtd+8],[ebx-sizeof.ohci_gtd+12] |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-sizeof.ohci_gtd+16],[ebx-sizeof.ohci_gtd+20],[ebx-sizeof.ohci_gtd+24],[ebx-sizeof.ohci_gtd+28] |
; mov eax, [ebx+usb_gtd.Pipe] |
; DEBUGF 1,'K : pipe: %x %x %x %x\n',[eax-sizeof.ohci_pipe],[eax-sizeof.ohci_pipe+4],[eax-sizeof.ohci_pipe+8],[eax-sizeof.ohci_pipe+12] |
; 7b. Traverse the list of descriptors looking for the final packet |
; for this transfer. |
; Free and unlink non-final descriptors, except the current one. |
; Final descriptor will be freed in step 6. |
invoke usbhc_api.usb_is_final_packet |
jnc .found_final |
mov ebx, [ebx+usb_gtd.NextVirt] |
virtual at esp |
.length dd ? |
.error_code dd ? |
.current_item dd ? |
end virtual |
.look_final: |
invoke usbhc_api.usb_unlink_td |
invoke usbhc_api.usb_is_final_packet |
jnc .found_final |
push [ebx+usb_gtd.NextVirt] |
stdcall ohci_free_gtd, ebx |
pop ebx |
jmp .look_final |
.found_final: |
; 7c. If error code is USB_STATUS_UNDERRUN and the last TD allows short packets, |
; it is not an error. |
; Note: all TDs except the last one in any transfer stage are marked |
; as short-packet-is-error to stop controller from further processing |
; of that stage; we need to restart processing from a TD following the last. |
; After that, go to step 5 with eax = 0 (no error). |
cmp dword [.error_code], USB_STATUS_UNDERRUN |
jnz .no_underrun |
test byte [ebx+ohci_gtd.Flags+2-sizeof.ohci_gtd], 1 shl (18-16) |
jz .no_underrun |
and dword [.error_code], 0 |
mov ecx, [ebx+usb_gtd.Pipe] |
mov edx, [ecx+ohci_pipe.HeadP-sizeof.ohci_pipe] |
and edx, 2 |
.advance_queue: |
mov eax, [ebx+usb_gtd.NextVirt] |
sub eax, sizeof.ohci_gtd |
invoke GetPhysAddr |
or eax, edx |
mov [ecx+ohci_pipe.HeadP-sizeof.ohci_pipe], eax |
push ebx |
mov ebx, ecx |
call ohci_notify_new_work |
pop ebx |
pop edx ecx |
jmp .notify |
; 7d. Abort the entire transfer. |
; There are two cases: either there is only one transfer stage |
; (everything except control transfers), then ebx points to the last TD and |
; all previous TD were unlinked and dismissed (if possible), |
; or there are several stages (a control transfer) and ebx points to the last |
; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, |
; because Setup stage can not produce short packets); for Data stage, we need |
; to unlink and free (if possible) one more TD and advance ebx to the next one. |
.no_underrun: |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .halted |
cmp ebx, [.current_item] |
push [ebx+usb_gtd.NextVirt] |
jz @f |
stdcall ohci_free_gtd, ebx |
@@: |
pop ebx |
invoke usbhc_api.usb_unlink_td |
.halted: |
; 7e. For bulk/interrupt transfers we have no choice but halt the queue, |
; the driver should intercede (through some API which is not written yet). |
; Control pipes normally recover at the next SETUP transaction (first stage |
; of any control transfer), so we hope on the best and just advance the queue |
; to the next transfer. (According to the standard, "A control pipe may also |
; support functional stall as well, but this is not recommended."). |
; Advance the transfer queue to the next descriptor. |
mov ecx, [ebx+usb_gtd.Pipe] |
mov edx, [ecx+ohci_pipe.HeadP-sizeof.ohci_pipe] |
and edx, 2 ; keep toggleCarry bit |
cmp [ecx+usb_pipe.Type], CONTROL_PIPE |
jz @f |
inc edx ; set Halted bit |
@@: |
jmp .advance_queue |
endp |
; This procedure is called when a pipe is closing (either due to API call |
; or due to disconnect); it unlinks the pipe from the corresponding list. |
; esi -> usb_controller, ebx -> usb_pipe |
proc ohci_unlink_pipe |
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE |
jnz @f |
mov eax, [ebx+ohci_pipe.Flags-sizeof.ohci_pipe] |
bt eax, 13 |
setc cl |
bt eax, 11 |
setc ch |
shr eax, 16 |
stdcall usb1_interrupt_list_unlink, eax, ecx |
@@: |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
mov edx, [ebx+ohci_pipe.NextED-sizeof.ohci_pipe] |
mov [eax+ohci_pipe.NextED-sizeof.ohci_pipe], edx |
ret |
endp |
; Allocates one endpoint structure for OHCI. |
; Returns pointer to software part (usb_pipe) in eax. |
proc ohci_alloc_pipe |
push ebx |
mov ebx, ohci_ep_mutex |
invoke usbhc_api.usb_allocate_common, (sizeof.ohci_pipe + sizeof.usb_pipe + 0Fh) and not 0Fh |
test eax, eax |
jz @f |
add eax, sizeof.ohci_pipe |
@@: |
pop ebx |
ret |
endp |
; Free one endpoint structure for OHCI. |
; Stdcall with one argument, pointer to software part (usb_pipe). |
proc ohci_free_pipe |
sub dword [esp+4], sizeof.ohci_pipe |
jmp [usbhc_api.usb_free_common] |
endp |
; Allocates one general transfer descriptor structure for OHCI. |
; Returns pointer to software part (usb_gtd) in eax. |
proc ohci_alloc_gtd |
push ebx |
mov ebx, ohci_gtd_mutex |
invoke usbhc_api.usb_allocate_common, (sizeof.ohci_gtd + sizeof.usb_gtd + 0Fh) and not 0Fh |
test eax, eax |
jz @f |
add eax, sizeof.ohci_gtd |
@@: |
pop ebx |
ret |
endp |
; Free one general transfer descriptor structure for OHCI. |
; Stdcall with one argument, pointer to software part (usb_gtd). |
proc ohci_free_gtd |
sub dword [esp+4], sizeof.ohci_gtd |
jmp [usbhc_api.usb_free_common] |
endp |
include 'usb1_scheduler.inc' |
define_controller_name ohci |
section '.data' readable writable |
include '../peimport.inc' |
include_debug_strings |
IncludeIGlobals |
IncludeUGlobals |
align 4 |
usbhc_api usbhc_func |
ohci_ep_first_page dd ? |
ohci_ep_mutex MUTEX |
ohci_gtd_first_page dd ? |
ohci_gtd_mutex MUTEX |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/drivers/usb/uhci.asm |
---|
0,0 → 1,1862 |
; Code for UHCI controllers. |
; Standard driver stuff |
format PE DLL native |
entry start |
__DEBUG__ equ 1 |
__DEBUG_LEVEL__ equ 1 |
section '.reloc' data readable discardable fixups |
section '.text' code readable executable |
include '../proc32.inc' |
include '../struct.inc' |
include '../macros.inc' |
include '../fdo.inc' |
include '../../kernel/trunk/bus/usb/common.inc' |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; UHCI register declarations |
UhciCommandReg = 0 |
UhciStatusReg = 2 |
UhciInterruptReg = 4 |
UhciFrameNumberReg = 6 |
UhciBaseAddressReg = 8 |
UhciSOFModifyReg = 0Ch |
UhciPort1StatusReg = 10h |
; possible PIDs for USB data transfers |
USB_PID_SETUP = 2Dh |
USB_PID_IN = 69h |
USB_PID_OUT = 0E1h |
; UHCI does not support an interrupt on root hub status change. We must poll |
; the controller periodically. This is the period in timer ticks (10ms). |
; We use the value 100 ticks: it is small enough to be responsive to connect |
; events and large enough to not load CPU too often. |
UHCI_POLL_INTERVAL = 100 |
; the following constant is an invalid encoding for length fields in |
; uhci_gtd; it is used to check whether an inactive TD has been |
; completed (actual length of the transfer is valid) or not processed at all |
; (actual length of the transfer is UHCI_INVALID_LENGTH). |
; Valid values are 0-4FFh and 7FFh. We use 700h as an invalid value. |
UHCI_INVALID_LENGTH = 700h |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; UHCI-specific part of a pipe descriptor. |
; * The structure corresponds to the Queue Head aka QH from the UHCI |
; specification with some additional fields. |
; * The hardware uses first two fields (8 bytes). Next two fields are used for |
; software book-keeping. |
; * The hardware requires 16-bytes alignment of the hardware part. |
; Since the allocator (usb_allocate_common) allocates memory sequentially |
; from page start (aligned on 0x1000 bytes), block size for the allocator |
; must be divisible by 16; usb1_allocate_endpoint ensures this. |
struct uhci_pipe |
NextQH dd ? |
; 1. First bit (bit 0) is Terminate bit. 1 = there is no next QH. |
; 2. Next bit (bit 1) is QH/TD select bit. 1 = NextQH points to QH. |
; 3. Next two bits (bits 2-3) are reserved. |
; 4. With masked 4 lower bits, this is the physical address of the next QH in |
; the QH list. |
; See also the description before NextVirt field of the usb_pipe |
; structure. Additionally to that description, the following is specific for |
; the UHCI controller: |
; * n=10, N=1024. However, this number is quite large. |
; * 1024 lists are used only for individual transfer descriptors for |
; Isochronous endpoints. This means that the software can sleep up to 1024 ms |
; before initiating the next portion of a large isochronous transfer, which |
; is a sufficiently large value. |
; * We use the 32ms upper limit for interrupt endpoint polling interval. |
; This seems to be a reasonable value. |
; * The "next" list for last Periodic list is the Control list. |
; * The "next" list for Control list is Bulk list and the "next" |
; list for Bulk list is Control list. This loop is used for bandwidth |
; reclamation: the hardware traverses lists until end-of-frame. |
HeadTD dd ? |
; 1. First bit (bit 0) is Terminate bit. 1 = there is no TDs in this QH. |
; 2. Next bit (bit 1) is QH/TD select bit. 1 = HeadTD points to QH. |
; 3. Next two bits (bits 2-3) are reserved. |
; 4. With masked 4 lower bits, this is the physical address of the first TD in |
; the TD queue for this QH. |
Token dd ? |
; This field is a template for uhci_gtd.Token field in transfer |
; descriptors. The meaning of individual bits is the same as for |
; uhci_gtd.Token, except that PID bitfield is always |
; USB_PID_SETUP/IN/OUT for control/in/out pipes, |
; the MaximumLength bitfield encodes maximum packet size, |
; the Reserved bit 20 is LowSpeedDevice bit. |
ErrorTD dd ? |
; Usually NULL. If nonzero, it is a pointer to descriptor which was error'd |
; and should be freed sometime in the future (the hardware could still use it). |
ends |
; This structure describes the static head of every list of pipes. |
; The hardware requires 16-bytes alignment of this structure. |
; All instances of this structure are located sequentially in uhci_controller, |
; uhci_controller is page-aligned, so it is sufficient to make this structure |
; 16-bytes aligned and verify that the first instance is 16-bytes aligned |
; inside uhci_controller. |
struct uhci_static_ep |
NextQH dd ? |
; Same as uhci_pipe.NextQH. |
HeadTD dd ? |
; Same as uhci_pipe.HeadTD. |
NextList dd ? |
; Virtual address of the next list. |
dd ? |
; Not used. |
SoftwarePart rd sizeof.usb_static_ep/4 |
; Common part for all controllers, described by usb_static_ep structure. |
dd ? |
; Padding for 16-byte alignment. |
ends |
if sizeof.uhci_static_ep mod 16 |
.err uhci_static_ep must be 16-bytes aligned |
end if |
; UHCI-specific part of controller data. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 4096 bytes and corresponds to |
; the Frame List from UHCI specification. |
; * The hardware requires page-alignment of the hardware part, so |
; the entire descriptor must be page-aligned. |
; This structure is allocated with kernel_alloc (see usb_init_controller), |
; this gives page-aligned data. |
struct uhci_controller |
; ------------------------------ hardware fields ------------------------------ |
FrameList rd 1024 |
; Entry n corresponds to the head of the frame list to be executed in |
; the frames n,n+1024,n+2048,n+3072,... |
; The first bit of each entry is Terminate bit, 1 = the frame is empty. |
; The second bit of each entry is QH/TD select bit, 1 = the entry points to |
; QH, 0 = to TD. |
; With masked 2 lower bits, the entry is a physical address of the first QH/TD |
; to be executed. |
; ------------------------------ software fields ------------------------------ |
; Every list has the static head, which is an always empty QH. |
; The following fields are static heads, one per list: |
; 32+16+8+4+2+1 = 63 for Periodic lists, 1 for Control list and 1 for Bulk list. |
IntEDs uhci_static_ep |
rb 62 * sizeof.uhci_static_ep |
ControlED uhci_static_ep |
BulkED uhci_static_ep |
IOBase dd ? |
; Base port in I/O space for UHCI controller. |
; UHCI register UhciXxx is addressed as in/out to IOBase + UhciXxx, |
; see declarations in the beginning of this source. |
DeferredActions dd ? |
; Bitmask of bits from UhciStatusReg which need to be processed |
; by uhci_process_deferred. Bit 0 = a transaction with IOC bit |
; has completed. Bit 1 = a transaction has failed. Set by uhci_irq, |
; cleared by uhci_process_deferred. |
LastPollTime dd ? |
; See the comment before UHCI_POLL_INTERVAL. This variable keeps the |
; last time, in timer ticks, when the polling was done. |
EhciCompanion dd ? |
; Pointer to usb_controller for EHCI companion, if any, or NULL. |
ends |
if uhci_controller.IntEDs mod 16 |
.err Static endpoint descriptors must be 16-bytes aligned inside uhci_controller |
end if |
; UHCI general transfer descriptor. |
; * The structure describes non-Isochronous data transfers |
; for the UHCI controller. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 16 bytes and corresponds to the |
; Transfer Descriptor aka TD from UHCI specification. |
; * The hardware requires 16-bytes alignment of the hardware part, so |
; the entire descriptor must be 16-bytes aligned. Since the allocator |
; (uhci_allocate_common) allocates memory sequentially from page start |
; (aligned on 0x1000 bytes), block size for the allocator must be |
; divisible by 16; usb1_allocate_general_td ensures this. |
struct uhci_gtd |
NextTD dd ? |
; 1. First bit (bit 0) is Terminate bit. 1 = there is no next TD. |
; 2. Next bit (bit 1) is QH/TD select bit. 1 = NextTD points to QH. |
; This bit is always set to 0 in the implementation. |
; 3. Next bit (bit 2) is Depth/Breadth select bit. 1 = the controller should |
; proceed to the NextTD after this TD is complete. 0 = the controller |
; should proceed to the next endpoint after this TD is complete. |
; The implementation sets this bit to 0 for final stages of all transactions |
; and to 1 for other stages. |
; 4. Next bit (bit 3) is reserved and must be zero. |
; 5. With masked 4 lower bits, this is the physical address of the next TD |
; in the TD list. |
ControlStatus dd ? |
; 1. Lower 11 bits (bits 0-10) are ActLen. This is written by the controller |
; at the conclusion of a USB transaction to indicate the actual number of |
; bytes that were transferred minus 1. |
; 2. Next 6 bits (bits 11-16) are reserved. |
; 3. Next bit (bit 17) signals Bitstuff error. |
; 4. Next bit (bit 18) signals CRC/Timeout error. |
; 5. Next bit (bit 19) signals NAK receive. |
; 6. Next bit (bit 20) signals Babble error. |
; 7. Next bit (bit 21) signals Data Buffer error. |
; 8. Next bit (bit 22) signals Stall error. |
; 9. Next bit (bit 23) is Active field. 1 = this TD should be processed. |
; 10. Next bit (bit 24) is InterruptOnComplete bit. 1 = the controller should |
; issue an interrupt on completion of the frame in which this TD is |
; executed. |
; 11. Next bit (bit 25) is IsochronousSelect bit. 1 = this TD is isochronous. |
; 12. Next bit (bit 26) is LowSpeedDevice bit. 1 = this TD is for low-speed. |
; 13. Next two bits (bits 27-28) are ErrorCounter field. This field is |
; decremented by the controller on every non-fatal error with this TD. |
; Babble and Stall are considered fatal errors and immediately deactivate |
; the TD without decrementing this field. 0 = no error limit, |
; n = deactivate the TD after n errors. |
; 14. Next bit (bit 29) is ShortPacketDetect bit. 1 = short packet is an error. |
; Note: the specification defines this bit as input for the controller, |
; but does not specify the value written by controller. |
; Some controllers (e.g. Intel) keep the value, some controllers (e.g. VIA) |
; set the value to whether a short packet was actually detected |
; (or something like that). |
; Thus, we duplicate this bit as bit 0 of OrigBufferInfo. |
; 15. Upper two bits (bits 30-31) are reserved. |
Token dd ? |
; 1. Lower 8 bits (bits 0-7) are PID, one of USB_PID_*. |
; 2. Next 7 bits (bits 8-14) are DeviceAddress field. This is the address of |
; the target device on the USB bus. |
; 3. Next 4 bits (bits 15-18) are Endpoint field. This is the target endpoint |
; number. |
; 4. Next bit (bit 19) is DataToggle bit. n = issue/expect DATAn token. |
; 5. Next bit (bit 20) is reserved. |
; 6. Upper 11 bits (bits 21-31) are MaximumLength field. This field specifies |
; the maximum number of data bytes for the transfer minus 1 byte. Null data |
; packet is encoded as 0x7FF, maximum possible non-null data packet is 1280 |
; bytes, encoded as 0x4FF. |
Buffer dd ? |
; Physical address of the data buffer for this TD. |
OrigBufferInfo dd ? |
; Usually NULL. If the original buffer crosses a page boundary, this is a |
; pointer to the structure uhci_original_buffer for this request. |
; bit 0: 1 = short packet is NOT allowed |
; (before the TD is processed, it is the copy of bit 29 of ControlStatus; |
; some controllers modify that bit, so we need a copy in a safe place) |
ends |
; UHCI requires that the entire transfer buffer should be on one page. |
; If the actual buffer crosses page boundary, uhci_alloc_packet |
; allocates additional memory for buffer for hardware. |
; This structure describes correspondence between two buffers. |
struct uhci_original_buffer |
OrigBuffer dd ? |
UsedBuffer dd ? |
ends |
; Description of UHCI-specific data and functions for |
; controller-independent code. |
; Implements the structure usb_hardware_func from hccommon.inc for UHCI. |
iglobal |
align 4 |
uhci_hardware_func: |
dd USBHC_VERSION |
dd 'UHCI' |
dd sizeof.uhci_controller |
dd uhci_kickoff_bios |
dd uhci_init |
dd uhci_process_deferred |
dd uhci_set_device_address |
dd uhci_get_device_address |
dd uhci_port_disable |
dd uhci_new_port.reset |
dd uhci_set_endpoint_packet_size |
dd uhci_alloc_pipe |
dd uhci_free_pipe |
dd uhci_init_pipe |
dd uhci_unlink_pipe |
dd uhci_alloc_td |
dd uhci_free_td |
dd uhci_alloc_transfer |
dd uhci_insert_transfer |
dd uhci_new_device |
uhci_name db 'UHCI',0 |
endg |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Called once when driver is loading and once at shutdown. |
; When loading, must initialize itself, register itself in the system |
; and return eax = value obtained when registering. |
proc start |
virtual at esp |
dd ? ; return address |
.reason dd ? ; DRV_ENTRY or DRV_EXIT |
.cmdline dd ? ; normally NULL |
end virtual |
cmp [.reason], DRV_ENTRY |
jnz .nothing |
mov ecx, uhci_ep_mutex |
and dword [ecx-4], 0 |
invoke MutexInit |
mov ecx, uhci_gtd_mutex |
and dword [ecx-4], 0 |
invoke MutexInit |
push esi edi |
mov esi, [USBHCFunc] |
mov edi, usbhc_api |
movi ecx, sizeof.usbhc_func/4 |
rep movsd |
pop edi esi |
invoke RegUSBDriver, uhci_name, 0, uhci_hardware_func |
.nothing: |
ret |
endp |
; Controller-specific initialization function. |
; Called from usb_init_controller. Initializes the hardware and |
; UHCI-specific parts of software structures. |
; eax = pointer to uhci_controller to be initialized |
; [ebp-4] = pcidevice |
proc uhci_init |
; inherit some variables from the parent (usb_init_controller) |
.devfn equ ebp - 4 |
.bus equ ebp - 3 |
; 1. Store pointer to uhci_controller for further use. |
push eax |
mov edi, eax |
mov esi, eax |
; 2. Initialize uhci_controller.FrameList. |
; Note that FrameList is located in the beginning of uhci_controller, |
; so esi and edi now point to uhci_controller.FrameList. |
; First 32 entries of FrameList contain physical addresses |
; of first 32 Periodic static heads, further entries duplicate these. |
; See the description of structures for full info. |
; Note that all static heads fit in one page, so one call to |
; get_phys_addr is sufficient. |
if (uhci_controller.IntEDs / 0x1000) <> (uhci_controller.BulkED / 0x1000) |
.err assertion failed |
end if |
; 2a. Get physical address of first static head. |
; Note that 1) it is located in the beginning of a page |
; and 2) all other static heads fit in the same page, |
; so one call to get_phys_addr without correction of lower 12 bits |
; is sufficient. |
if (uhci_controller.IntEDs mod 0x1000) <> 0 |
.err assertion failed |
end if |
add eax, uhci_controller.IntEDs |
invoke GetPhysAddr |
; 2b. Fill first 32 entries. |
inc eax |
inc eax ; set QH bit for uhci_pipe.NextQH |
movi ecx, 32 |
mov edx, ecx |
@@: |
stosd |
add eax, sizeof.uhci_static_ep |
loop @b |
; 2c. Fill the rest entries. |
mov ecx, 1024 - 32 |
rep movsd |
; 3. Initialize static heads uhci_controller.*ED. |
; Use the loop over groups: first group consists of first 32 Periodic |
; descriptors, next group consists of next 16 Periodic descriptors, |
; ..., last group consists of the last Periodic descriptor. |
; 3a. Prepare for the loop. |
; make esi point to the second group, other registers are already set. |
add esi, 32*4 + 32*sizeof.uhci_static_ep |
; 3b. Loop over groups. On every iteration: |
; edx = size of group, edi = pointer to the current group, |
; esi = pointer to the next group, eax = physical address of the next group. |
.init_static_eds: |
; 3c. Get the size of next group. |
shr edx, 1 |
; 3d. Exit the loop if there is no next group. |
jz .init_static_eds_done |
; 3e. Initialize the first half of the current group. |
; Advance edi to the second half. |
push eax esi |
call uhci_init_static_ep_group |
pop esi eax |
; 3f. Initialize the second half of the current group |
; with the same values. |
; Advance edi to the next group, esi/eax to the next of the next group. |
call uhci_init_static_ep_group |
jmp .init_static_eds |
.init_static_eds_done: |
; 3g. Initialize the last static head. |
xor esi, esi |
call uhci_init_static_endpoint |
; 3i. Initialize the head of Control list. |
add eax, sizeof.uhci_static_ep |
call uhci_init_static_endpoint |
; 3j. Initialize the head of Bulk list. |
sub eax, sizeof.uhci_static_ep |
call uhci_init_static_endpoint |
; 4. Get I/O base address and size from PCI bus. |
; 4a. Read&save PCI command state. |
invoke PciRead16, dword [.bus], dword [.devfn], 4 |
push eax |
; 4b. Disable IO access. |
and al, not 1 |
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax |
; 4c. Read&save IO base address. |
invoke PciRead16, dword [.bus], dword [.devfn], 20h |
and al, not 3 |
xchg eax, edi |
; now edi = IO base |
; 4d. Write 0xffff to IO base address. |
invoke PciWrite16, dword [.bus], dword [.devfn], 20h, -1 |
; 4e. Read IO base address. |
invoke PciRead16, dword [.bus], dword [.devfn], 20h |
and al, not 3 |
cwde |
not eax |
inc eax |
xchg eax, esi |
; now esi = IO size |
; 4f. Restore IO base address. |
invoke PciWrite16, dword [.bus], dword [.devfn], 20h, edi |
; 4g. Restore PCI command state and enable io & bus master access. |
pop ecx |
or ecx, 5 |
invoke PciWrite16, dword [.bus], dword [.devfn], 4, ecx |
; 5. Reset the controller. |
; 5e. Host reset. |
mov edx, edi |
mov ax, 2 |
out dx, ax |
; 5f. Wait up to 10ms. |
movi ecx, 10 |
@@: |
push esi |
movi esi, 1 |
invoke Sleep |
pop esi |
in ax, dx |
test al, 2 |
loopnz @b |
jz @f |
dbgstr 'UHCI controller reset timeout' |
jmp .fail |
@@: |
if 0 |
; emergency variant for tests - always wait 10 ms |
; wait 10 ms |
push esi |
movi esi, 10 |
invoke Sleep |
pop esi |
; clear reset signal |
xor eax, eax |
out dx, ax |
end if |
.resetok: |
; 6. Get number of ports & disable all ports. |
add esi, edi |
lea edx, [edi+UhciPort1StatusReg] |
.scanports: |
cmp edx, esi |
jae .doneports |
in ax, dx |
cmp ax, 0xFFFF |
jz .doneports |
test al, al |
jns .doneports |
xor eax, eax |
out dx, ax |
inc edx |
inc edx |
jmp .scanports |
.doneports: |
lea esi, [edx-UhciPort1StatusReg] |
sub esi, edi |
shr esi, 1 ; esi = number of ports |
jnz @f |
dbgstr 'error: no ports on UHCI controller' |
jmp .fail |
@@: |
; 7. Setup the rest of uhci_controller. |
xchg esi, [esp] ; restore the pointer to uhci_controller from the step 1 |
add esi, sizeof.uhci_controller |
pop [esi+usb_controller.NumPorts] |
DEBUGF 1,'K : UHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] |
mov [esi+uhci_controller.IOBase-sizeof.uhci_controller], edi |
invoke GetTimerTicks |
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax |
; 8. Find the EHCI companion. |
; If there is one, check whether all ports are covered by that companion. |
; Note: this assumes that EHCI is initialized before USB1 companions. |
mov ebx, dword [.devfn] |
invoke usbhc_api.usb_find_ehci_companion |
mov [esi+uhci_controller.EhciCompanion-sizeof.uhci_controller], eax |
; 9. Hook interrupt. |
invoke PciRead8, dword [.bus], dword [.devfn], 3Ch |
; al = IRQ |
; DEBUGF 1,'K : UHCI %x: io=%x, irq=%x\n',esi,edi,al |
movzx eax, al |
invoke AttachIntHandler, eax, uhci_irq, esi |
; 10. Setup controller registers. |
xor eax, eax |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
; 10a. UhciStatusReg := 3Fh: clear all status bits |
; (for this register 1 clears the corresponding bit, 0 does not change it). |
inc edx |
inc edx ; UhciStatusReg == 2 |
mov al, 3Fh |
out dx, ax |
; 10b. UhciInterruptReg := 0Dh. |
inc edx |
inc edx ; UhciInterruptReg == 4 |
mov al, 0Dh |
out dx, ax |
; 10c. UhciFrameNumberReg := 0. |
inc edx |
inc edx ; UhciFrameNumberReg == 6 |
mov al, 0 |
out dx, ax |
; 10d. UhciBaseAddressReg := physical address of uhci_controller. |
inc edx |
inc edx ; UhciBaseAddressReg == 8 |
lea eax, [esi-sizeof.uhci_controller] |
invoke GetPhysAddr |
out dx, eax |
; 10e. UhciCommandReg := Run + Configured + (MaxPacket is 64 bytes) |
sub edx, UhciBaseAddressReg ; UhciCommandReg == 0 |
mov ax, 0C1h ; Run, Configured, MaxPacket = 64b |
out dx, ax |
; 11. Do initial scan of existing devices. |
call uhci_poll_roothub |
; 12. Return pointer to usb_controller. |
xchg eax, esi |
ret |
.fail: |
; On error, pop the pointer saved at step 1 and return zero. |
; Note that the main code branch restores the stack at step 8 and never fails |
; after step 8. |
pop ecx |
xor eax, eax |
ret |
endp |
; Controller-specific pre-initialization function: take ownership from BIOS. |
; UHCI has no mechanism to ask the owner politely to release ownership, |
; so do it in inpolite way, preventing controller from any SMI activity. |
proc uhci_kickoff_bios |
; 1. Get the I/O address. |
invoke PciRead16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 20h |
and eax, 0xFFFC |
xchg eax, edx |
; 2. Stop the controller and disable all interrupts. |
in ax, dx |
and al, not 1 |
out dx, ax |
add edx, UhciInterruptReg |
xor eax, eax |
out dx, ax |
; 3. Disable all bits for SMI routing, clear SMI routing status, |
; enable master interrupt bit. |
invoke PciWrite16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 0xC0, 0AF00h |
ret |
endp |
; Helper procedure for step 3 of uhci_init. |
; Initializes the static head of one list. |
; eax = physical address of the "next" list, esi = pointer to the "next" list, |
; edi = pointer to head to initialize. |
; Advances edi to the next head, keeps eax/esi. |
proc uhci_init_static_endpoint |
mov [edi+uhci_static_ep.NextQH], eax |
mov byte [edi+uhci_static_ep.HeadTD], 1 |
mov [edi+uhci_static_ep.NextList], esi |
add edi, uhci_static_ep.SoftwarePart |
invoke usbhc_api.usb_init_static_endpoint |
add edi, sizeof.uhci_static_ep - uhci_static_ep.SoftwarePart |
ret |
endp |
; Helper procedure for step 3 of uhci_init, see comments there. |
; Initializes one half of group of static heads. |
; edx = size of the next group = half of size of the group, |
; edi = pointer to the group, eax = physical address of the next group, |
; esi = pointer to the next group. |
; Advances eax, esi, edi to next group, keeps edx. |
proc uhci_init_static_ep_group |
push edx |
@@: |
call uhci_init_static_endpoint |
add eax, sizeof.uhci_static_ep |
add esi, sizeof.uhci_static_ep |
dec edx |
jnz @b |
pop edx |
ret |
endp |
; IRQ handler for UHCI controllers. |
uhci_irq.noint: |
; Not our interrupt: restore esi and return zero. |
pop esi |
xor eax, eax |
ret |
proc uhci_irq |
push esi ; save used register to be cdecl |
virtual at esp |
dd ? ; saved esi |
dd ? ; return address |
.controller dd ? |
end virtual |
mov esi, [.controller] |
; 1. Read UhciStatusReg. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
inc edx |
inc edx ; UhciStatusReg == 2 |
in ax, dx |
; 2. Test whether it is our interrupt; if so, at least one status bit is set. |
test al, 0x1F |
jz .noint |
; 3. Clear all status bits. |
out dx, ax |
; 4. Sanity check. |
test al, 0x3C |
jz @f |
DEBUGF 1,'K : something terrible happened with UHCI (%x)\n',al |
@@: |
; 5. We can't do too much from an interrupt handler, e.g. we can't take |
; any mutex locks since our code could be called when another code holds the |
; lock and has no chance to release it. Thus, only inform the processing thread |
; that it should scan the queue and wake it if needed. |
lock or byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], al |
push ebx |
xor ebx, ebx |
inc ebx |
invoke usbhc_api.usb_wakeup_if_needed |
pop ebx |
; 6. This is our interrupt; return 1. |
mov al, 1 |
pop esi ; restore used register to be stdcall |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; processes regular actions and those actions which can't be safely done |
; from interrupt handler. |
; Returns maximal time delta before the next call. |
proc uhci_process_deferred |
push ebx edi ; save used registers to be stdcall |
; 1. Initialize the return value. |
push -1 |
; 2. Poll the root hub every UHCI_POLL_INTERVAL ticks. |
; Also force polling if some transaction has completed with errors; |
; the error can be caused by disconnect, try to detect it. |
test byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], 2 |
jnz .force_poll |
invoke GetTimerTicks |
sub eax, [esi+uhci_controller.LastPollTime-sizeof.uhci_controller] |
sub eax, UHCI_POLL_INTERVAL |
jl .nopoll |
.force_poll: |
invoke GetTimerTicks |
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax |
call uhci_poll_roothub |
mov eax, -UHCI_POLL_INTERVAL |
.nopoll: |
neg eax |
cmp [esp], eax |
jb @f |
mov [esp], eax |
@@: |
; 3. Process wait lists. |
; 3a. Test whether there is a wait request. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
cmp eax, [esi+usb_controller.ReadyPipeHeadAsync] |
jnz .check_removed |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
cmp eax, [esi+usb_controller.ReadyPipeHeadPeriodic] |
jz @f |
.check_removed: |
; 3b. Yep. Find frame and compare it with the saved one. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
add edx, UhciFrameNumberReg |
in ax, dx |
cmp word [esi+usb_controller.StartWaitFrame], ax |
jnz .removed |
; 3c. The same frame; wake up in 0.01 sec. |
mov dword [esp], 1 |
jmp @f |
.removed: |
; 3d. The frame is changed, old contents is guaranteed to be forgotten. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax |
@@: |
; 4. Process disconnect events. This should be done after step 2 |
; (which includes the first stage of disconnect processing). |
invoke usbhc_api.usb_disconnect_stage2 |
; 5. Test whether USB_CONNECT_DELAY for a connected device is over. |
; Call uhci_new_port for all such devices. |
xor ecx, ecx |
cmp [esi+usb_controller.NewConnected], ecx |
jz .skip_newconnected |
.portloop: |
bt [esi+usb_controller.NewConnected], ecx |
jnc .noconnect |
; If this port is shared with the EHCI companion and we see the connect event, |
; then the device is USB1 dropped by EHCI, |
; so EHCI has already waited for debounce delay, we can proceed immediately. |
cmp [esi+uhci_controller.EhciCompanion-sizeof.uhci_controller], 0 |
jz .portloop.test_time |
dbgstr 'port is shared with EHCI, skipping initial debounce' |
jmp .connected |
.portloop.test_time: |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ConnectedTime+ecx*4] |
sub eax, USB_CONNECT_DELAY |
jge .connected |
neg eax |
cmp [esp], eax |
jb .nextport |
mov [esp], eax |
jmp .nextport |
.connected: |
btr [esi+usb_controller.NewConnected], ecx |
call uhci_new_port |
.noconnect: |
.nextport: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_newconnected: |
; 6. Test for processed packets. |
; This should be done after step 4, so transfers which were failed due |
; to disconnect are marked with the exact reason, not just |
; 'device not responding'. |
xor eax, eax |
xchg byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], al |
test al, 3 |
jz .noioc |
call uhci_process_updated_schedule |
.noioc: |
; 7. Test whether reset signalling has been started. If so, |
; either should be stopped now (if time is over) or schedule wakeup (otherwise). |
; This should be done after step 6, because a completed SET_ADDRESS command |
; could result in reset of a new port. |
.test_reset: |
; 7a. Test whether reset signalling is active. |
cmp [esi+usb_controller.ResettingStatus], 1 |
jnz .no_reset_in_progress |
; 7b. Yep. Test whether it should be stopped. |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_TIME |
jge .reset_done |
; 7c. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_done: |
; 7d. Yep, call the worker function and proceed to 7e. |
call uhci_port_reset_done |
.no_reset_in_progress: |
; 7e. Test whether reset process is done, either successful or failed. |
cmp [esi+usb_controller.ResettingStatus], 0 |
jz .skip_reset |
; 7f. Yep. Test whether it should be stopped. |
invoke GetTimerTicks |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_recovery_done |
; 7g. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_recovery_done: |
; 7h. Yep, call the worker function. This could initiate another reset, |
; so return to the beginning of this step. |
call uhci_port_init |
jmp .test_reset |
.skip_reset: |
; 8. Process wait-done notifications, test for new wait requests. |
; Note: that must be done after steps 4 and 6 which could create new requests. |
; 8a. Call the worker function. |
invoke usbhc_api.usb_process_wait_lists |
; 8b. If no new requests, skip the rest of this step. |
test eax, eax |
jz @f |
; 8c. UHCI is not allowed to cache anything; we don't know what is |
; processed right now, but we can be sure that the controller will not |
; use any removed structure starting from the next frame. |
; Request removal of everything disconnected until now, |
; schedule wakeup in 0.01 sec. |
mov eax, [esi+usb_controller.WaitPipeListAsync] |
mov [esi+usb_controller.WaitPipeRequestAsync], eax |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [esi+usb_controller.WaitPipeRequestPeriodic], eax |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
add edx, UhciFrameNumberReg |
in ax, dx |
mov word [esi+usb_controller.StartWaitFrame], ax |
mov dword [esp], 1 |
@@: |
; 9. Return the value from the top of stack. |
pop eax |
pop edi ebx ; restore used registers to be stdcall. |
ret |
endp |
; This procedure is called in the USB thread from uhci_process_deferred |
; when UHCI IRQ handler has signalled that new IOC-packet was processed. |
; It scans all lists for completed packets and calls uhci_process_finalized_td |
; for those packets. |
; in: esi -> usb_controller |
proc uhci_process_updated_schedule |
; Important note: we cannot hold the list lock during callbacks, |
; because callbacks sometimes open and/or close pipes and thus acquire/release |
; the corresponding lock itself. |
; Fortunately, pipes can be finally freed only by another step of |
; uhci_process_deferred, so all pipes existing at the start of this function |
; will be valid while this function is running. Some pipes can be removed |
; from the corresponding list, some pipes can be inserted; insert/remove |
; functions guarantee that traversing one list yields all pipes that were in |
; that list at the beginning of the traversing (possibly with some new pipes, |
; possibly without some new pipes, that doesn't matter). |
; 1. Process all Periodic lists. |
lea edi, [esi+uhci_controller.IntEDs.SoftwarePart-sizeof.uhci_controller] |
lea ebx, [esi+uhci_controller.IntEDs.SoftwarePart+63*sizeof.uhci_static_ep-sizeof.uhci_controller] |
@@: |
call uhci_process_updated_list |
cmp edi, ebx |
jnz @b |
; 2. Process the Control list. |
call uhci_process_updated_list |
; 3. Process the Bulk list. |
call uhci_process_updated_list |
; 4. Return. |
ret |
endp |
; This procedure is called from uhci_process_updated_schedule, |
; see comments there. |
; It processes one list, esi -> usb_controller, edi -> usb_static_ep, |
; and advances edi to the next head. |
proc uhci_process_updated_list |
push ebx ; save used register to be stdcall |
; 1. Perform the external loop over all pipes. |
mov ebx, [edi+usb_static_ep.NextVirt] |
.loop: |
cmp ebx, edi |
jz .done |
; store pointer to the next pipe in the stack |
push [ebx+usb_static_ep.NextVirt] |
; 2. For every pipe, perform the internal loop over all descriptors. |
; All descriptors are organized in the queue; we process items from the start |
; of the queue until a) the last descriptor (not the part of the queue itself) |
; or b) an active (not yet processed by the hardware) descriptor is reached. |
lea ecx, [ebx+usb_pipe.Lock] |
invoke MutexLock |
mov ebx, [ebx+usb_pipe.LastTD] |
push ebx |
mov ebx, [ebx+usb_gtd.NextVirt] |
.tdloop: |
; 3. For every descriptor, test active flag and check for end-of-queue; |
; if either of conditions holds, exit from the internal loop. |
cmp ebx, [esp] |
jz .tddone |
mov eax, [ebx+uhci_gtd.ControlStatus-sizeof.uhci_gtd] |
test eax, 1 shl 23 ; active? |
jnz .tddone |
; Release the queue lock while processing one descriptor: |
; callback function could (and often would) schedule another transfer. |
push ecx |
invoke MutexUnlock |
call uhci_process_finalized_td |
pop ecx |
invoke MutexLock |
jmp .tdloop |
.tddone: |
invoke MutexUnlock |
pop ebx |
; End of internal loop, restore pointer to the next pipe |
; and continue the external loop. |
pop ebx |
jmp .loop |
.done: |
pop ebx ; restore used register to be stdcall |
add edi, sizeof.uhci_static_ep |
ret |
endp |
; This procedure is called from uhci_process_updated_list, which is itself |
; called from uhci_process_updated_schedule, see comments there. |
; It processes one completed descriptor. |
; in: esi -> usb_controller, ebx -> usb_gtd, out: ebx -> next usb_gtd. |
proc uhci_process_finalized_td |
; 1. Remove this descriptor from the list of descriptors for this pipe. |
invoke usbhc_api.usb_unlink_td |
; DEBUGF 1,'K : finalized TD:\n' |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8] |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8] |
; 2. If this is IN transfer into special buffer, copy the data |
; to target location. |
mov edx, [ebx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd] |
and edx, not 1 ; clear lsb (used for another goal) |
jz .nocopy |
cmp byte [ebx+uhci_gtd.Token-sizeof.uhci_gtd], USB_PID_IN |
jnz .nocopy |
; Note: we assume that pointer to buffer is valid in the memory space of |
; the USB thread. This means that buffer must reside in kernel memory |
; (shared by all processes). |
push esi edi |
mov esi, [edx+uhci_original_buffer.UsedBuffer] |
mov edi, [edx+uhci_original_buffer.OrigBuffer] |
mov ecx, [ebx+uhci_gtd.ControlStatus-sizeof.uhci_gtd] |
inc ecx |
and ecx, 7FFh |
mov edx, ecx |
shr ecx, 2 |
and edx, 3 |
rep movsd |
mov ecx, edx |
rep movsb |
pop edi esi |
.nocopy: |
; 3. Calculate actual number of bytes transferred. |
; 3a. Read the state. |
mov eax, [ebx+uhci_gtd.ControlStatus-sizeof.uhci_gtd] |
mov ecx, [ebx+uhci_gtd.Token-sizeof.uhci_gtd] |
; 3b. Get number of bytes processed. |
lea edx, [eax+1] |
and edx, 7FFh |
; 3c. Subtract number of bytes in this packet. |
add ecx, 1 shl 21 |
shr ecx, 21 |
sub edx, ecx |
; 3d. Add total length transferred so far. |
add edx, [ebx+usb_gtd.Length] |
; Actions on error and on success are slightly different. |
; 4. Test for error. On error, proceed to step 5, otherwise go to step 6 |
; with ecx = 0 (no error). |
; USB transaction error is always considered as such. |
; If short packets are not allowed, UHCI controllers do not set an error bit, |
; but stop (clear Active bit and do not advance) the queue. |
; Short packet is considered as an error if the packet is actually short |
; (actual length is less than maximal one) and the code creating the packet |
; requested that behaviour (so bit 0 of OrigBufferInfo is set; this could be |
; because the caller disallowed short packets or because the packet is not |
; the last one in the corresponding transfer). |
xor ecx, ecx |
test eax, 1 shl 22 |
jnz .error |
test byte [ebx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 1 |
jz .notify |
cmp edx, [ebx+usb_gtd.Length] |
jz .notify |
.error: |
; 5. There was an error while processing this packet. |
; The hardware has stopped processing the queue. |
DEBUGF 1,'K : TD failed:\n' |
if sizeof.uhci_gtd <> 20 |
.err modify offsets for debug output |
end if |
DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8] |
DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8] |
; 5a. Save the status and length. |
push edx |
push eax |
mov eax, [ebx+usb_gtd.Pipe] |
DEBUGF 1,'K : pipe: %x %x\n',[eax+0-sizeof.uhci_pipe],[eax+4-sizeof.uhci_pipe] |
; 5b. Store the current TD as an error packet. |
; If an error packet is already stored for this pipe, |
; it is definitely not used already, so free the old packet. |
mov eax, [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe] |
test eax, eax |
jz @f |
stdcall uhci_free_td, eax |
@@: |
mov eax, [ebx+usb_gtd.Pipe] |
mov [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe], ebx |
; 5c. Traverse the list of descriptors looking for the final packet |
; for this transfer. |
; Free and unlink non-final descriptors, except the current one. |
; Final descriptor will be freed in step 7. |
invoke usbhc_api.usb_is_final_packet |
jnc .found_final |
mov ebx, [ebx+usb_gtd.NextVirt] |
.look_final: |
invoke usbhc_api.usb_unlink_td |
invoke usbhc_api.usb_is_final_packet |
jnc .found_final |
push [ebx+usb_gtd.NextVirt] |
stdcall uhci_free_td, ebx |
pop ebx |
jmp .look_final |
.found_final: |
; 5d. Restore the status saved in 5a and transform it to the error code. |
pop eax ; error code |
shr eax, 16 |
; Notes: |
; * any USB transaction error results in Stalled bit; if it is not set, |
; but we are here, it must be due to short packet; |
; * babble is considered a fatal USB transaction error, |
; other errors just lead to retrying the transaction; |
; if babble is detected, return the corresponding error; |
; * if several non-fatal errors have occured during transaction retries, |
; all corresponding bits are set. In this case, return some error code, |
; the order is quite arbitrary. |
movi ecx, USB_STATUS_UNDERRUN |
test al, 1 shl (22-16) ; not Stalled? |
jz .know_error |
mov cl, USB_STATUS_OVERRUN |
test al, 1 shl (20-16) ; Babble detected? |
jnz .know_error |
mov cl, USB_STATUS_BITSTUFF |
test al, 1 shl (17-16) ; Bitstuff error? |
jnz .know_error |
mov cl, USB_STATUS_NORESPONSE |
test al, 1 shl (18-16) ; CRC/TimeOut error? |
jnz .know_error |
mov cl, USB_STATUS_BUFOVERRUN |
test al, 1 shl (21-16) ; Data Buffer error? |
jnz .know_error |
mov cl, USB_STATUS_STALL |
.know_error: |
; 5e. If error code is USB_STATUS_UNDERRUN |
; and the last TD allows short packets, it is not an error. |
; Note: all TDs except the last one in any transfer stage are marked |
; as short-packet-is-error to stop controller from further processing |
; of that stage; we need to restart processing from a TD following the last. |
; After that, go to step 6 with ecx = 0 (no error). |
cmp ecx, USB_STATUS_UNDERRUN |
jnz @f |
test byte [ebx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 1 |
jnz @f |
; The controller has stopped this queue on the error packet. |
; Update uhci_pipe.HeadTD to point to the next packet in the queue. |
call uhci_fix_toggle |
xor ecx, ecx |
.control: |
mov eax, [ebx+uhci_gtd.NextTD-sizeof.uhci_gtd] |
and al, not 0xF |
mov edx, [ebx+usb_gtd.Pipe] |
mov [edx+uhci_pipe.HeadTD-sizeof.uhci_pipe], eax |
pop edx ; length |
jmp .notify |
@@: |
; 5f. Abort the entire transfer. |
; There are two cases: either there is only one transfer stage |
; (everything except control transfers), then ebx points to the last TD and |
; all previous TD were unlinked and dismissed (if possible), |
; or there are several stages (a control transfer) and ebx points to the last |
; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, |
; because Setup stage can not produce short packets); for Data stage, we need |
; to unlink and free (if possible) one more TD and advance ebx to the next one. |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .normal |
; We cannot free ErrorTD yet, it could still be used by the hardware. |
push ecx |
mov eax, [ebx+usb_gtd.Pipe] |
push [ebx+usb_gtd.NextVirt] |
cmp ebx, [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe] |
jz @f |
stdcall uhci_free_td, ebx |
@@: |
pop ebx |
invoke usbhc_api.usb_unlink_td |
pop ecx |
.normal: |
; 5g. For bulk/interrupt transfers we have no choice but halt the queue, |
; the driver should intercede (through some API which is not written yet). |
; Control pipes normally recover at the next SETUP transaction (first stage |
; of any control transfer), so we hope on the best and just advance the queue |
; to the next transfer. (According to the standard, "A control pipe may also |
; support functional stall as well, but this is not recommended."). |
mov edx, [ebx+usb_gtd.Pipe] |
cmp [edx+usb_pipe.Type], CONTROL_PIPE |
jz .control |
; Bulk/interrupt transfer; halt the queue. |
mov eax, [ebx+uhci_gtd.NextTD-sizeof.uhci_gtd] |
and al, not 0xF |
inc eax ; set Halted bit |
mov [edx+uhci_pipe.HeadTD-sizeof.uhci_pipe], eax |
pop edx ; restore length saved in step 5a |
.notify: |
; 6. Either the descriptor in ebx was processed without errors, |
; or all necessary error actions were taken and ebx points to the last |
; related descriptor. |
invoke usbhc_api.usb_process_gtd |
; 7. Free the current descriptor (if allowed) and return the next one. |
; 7a. Save pointer to the next descriptor. |
push [ebx+usb_gtd.NextVirt] |
; 7b. Free the descriptor, unless it is saved as ErrorTD. |
mov eax, [ebx+usb_gtd.Pipe] |
cmp [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe], ebx |
jz @f |
stdcall uhci_free_td, ebx |
@@: |
; 7c. Restore pointer to the next descriptor and return. |
pop ebx |
ret |
endp |
; Helper procedure for restarting transfer queue. |
; When transfers are queued, their toggle bit is filled assuming that |
; everything will go without errors. On error, some packets needs to be |
; skipped, so toggle bits may become incorrect. |
; This procedure fixes toggle bits. |
; in: ebx -> last packet to be skipped, ErrorTD -> last processed packet |
proc uhci_fix_toggle |
; 1. Nothing to do for control pipes: in that case, |
; toggle bits for different transfer stages are independent. |
mov ecx, [ebx+usb_gtd.Pipe] |
cmp [ecx+usb_pipe.Type], CONTROL_PIPE |
jz .nothing |
; 2. The hardware expects next packet with toggle = (ErrorTD.toggle xor 1), |
; the current value in next packet is (ebx.toggle xor 1). |
; Nothing to do if ErrorTD.toggle == ebx.toggle. |
mov eax, [ecx+uhci_pipe.ErrorTD-sizeof.uhci_pipe] |
mov eax, [eax+uhci_gtd.Token-sizeof.uhci_gtd] |
xor eax, [ebx+uhci_gtd.Token-sizeof.uhci_gtd] |
test eax, 1 shl 19 |
jz .nothing |
; 3. Lock the transfer queue. |
add ecx, usb_pipe.Lock |
invoke MutexLock |
; 4. Flip the toggle bit in all packets from ebx.NextVirt to ecx.LastTD |
; (inclusive). |
mov eax, [ebx+usb_gtd.NextVirt] |
.loop: |
xor byte [eax+uhci_gtd.Token-sizeof.uhci_gtd+2], 1 shl (19-16) |
cmp eax, [ecx+usb_pipe.LastTD-usb_pipe.Lock] |
mov eax, [eax+usb_gtd.NextVirt] |
jnz .loop |
; 5. Flip the toggle bit in uhci_pipe structure. |
xor byte [ecx+uhci_pipe.Token-sizeof.uhci_pipe-usb_pipe.Lock+2], 1 shl (19-16) |
or dword [ecx+uhci_pipe.Token-sizeof.uhci_pipe-usb_pipe.Lock], eax |
; 6. Unlock the transfer queue. |
invoke MutexUnlock |
.nothing: |
ret |
endp |
; This procedure is called in the USB thread from uhci_process_deferred |
; every UHCI_POLL_INTERVAL ticks. It polls the controller for |
; connect/disconnect events. |
; in: esi -> usb_controller |
proc uhci_poll_roothub |
push ebx ; save used register to be stdcall |
; 1. Prepare for the loop for every port. |
xor ecx, ecx |
.portloop: |
; 2. Some implementations of UHCI set ConnectStatusChange bit in a response to |
; PortReset. Thus, we must ignore this change for port which is resetting. |
cmp cl, [esi+usb_controller.ResettingPort] |
jz .nextport |
; 3. Read port status. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
; 4. If no change bits are set, continue to the next port. |
test al, 0Ah |
jz .nextport |
; 5. Clear change bits and read the status again. |
; (It is possible, although quite unlikely, that some event occurs between |
; the first read and the clearing, invalidating the old status. If an event |
; occurs after the clearing, we will not miss it, looking in the next scan. |
out dx, ax |
mov ebx, eax |
in ax, dx |
; 6. Process connect change notifications. |
; Note: if connect status has changed, ignore enable status change; |
; it is normal to disable a port at disconnect event. |
; Some controllers set enable status change bit, some don't. |
test bl, 2 |
jz .noconnectchange |
DEBUGF 1,'K : UHCI %x connect status changed, %x/%x\n',esi,bx,ax |
; yep. Regardless of the current status, note disconnect event; |
; if there is something connected, store the connect time and note connect event. |
; In any way, do not process |
bts [esi+usb_controller.NewDisconnected], ecx |
test al, 1 |
jz .disconnect |
invoke GetTimerTicks |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
bts [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.disconnect: |
btr [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.noconnectchange: |
; 7. Process enable change notifications. |
; Note: that needs work. |
test bl, 8 |
jz .nextport |
test al, 4 |
jnz .nextport |
dbgstr 'Port disabled' |
.nextport: |
; 8. Continue the loop for every port. |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
pop ebx ; restore used register to be stdcall |
ret |
endp |
; This procedure is called from uhci_process_deferred when |
; a new device was connected at least USB_CONNECT_DELAY ticks |
; and therefore is ready to be configured. |
; in: esi -> usb_controller, ecx = port (zero-based) |
proc uhci_new_port |
; test whether we are configuring another port |
; if so, postpone configuring and return |
bts [esi+usb_controller.PendingPorts], ecx |
cmp [esi+usb_controller.ResettingPort], -1 |
jnz .nothing |
btr [esi+usb_controller.PendingPorts], ecx |
; fall through to uhci_new_port.reset |
; This function is called from uhci_new_port and uhci_test_pending_port. |
; It starts reset signalling for the port. Note that in USB first stages |
; of configuration can not be done for several ports in parallel. |
.reset: |
; 1. Store information about resetting hub (roothub) and port. |
and [esi+usb_controller.ResettingHub], 0 |
mov [esi+usb_controller.ResettingPort], cl |
; 2. Initiate reset signalling. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
or ah, 2 |
out dx, ax |
; 3. Store the current time and set status to 1 = reset signalling active. |
invoke GetTimerTicks |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 1 |
.nothing: |
ret |
endp |
; This procedure is called from uhci_process_deferred when |
; reset signalling for a port needs to be finished. |
proc uhci_port_reset_done |
; 1. Stop reset signalling. |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
DEBUGF 1,'K : UHCI %x status %x/',esi,ax |
and ah, not 2 |
out dx, ax |
; 2. Status bits in UHCI are invalid during reset signalling. |
; Wait a millisecond while status bits become valid again. |
push esi |
movi esi, 1 |
invoke Sleep |
pop esi |
; 3. ConnectStatus bit is zero during reset and becomes 1 during step 2; |
; some controllers interpret this as a (fake) connect event. |
; Enable port and clear status change notification. |
in ax, dx |
DEBUGF 1,'%x\n',ax |
or al, 6 ; enable port, clear status change |
out dx, ax |
; 4. Store the current time and set status to 2 = reset recovery active. |
invoke GetTimerTicks |
DEBUGF 1,'K : reset done\n' |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 2 |
ret |
endp |
; This procedure is called from uhci_process_deferred when |
; a new device has been reset, recovered after reset and |
; needs to be configured. |
; in: esi -> usb_controller |
proc uhci_port_init |
; 1. Read port status. |
mov [esi+usb_controller.ResettingStatus], 0 |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
DEBUGF 1,'K : UHCI %x status %x\n',esi,ax |
; 2. If the device has been disconnected, stop the initialization. |
test al, 1 |
jnz @f |
dbgstr 'USB port disabled after reset' |
jmp [usbhc_api.usb_test_pending_port] |
@@: |
; 3. Copy LowSpeed bit to bit 0 of eax and call the worker procedure |
; to notify the protocol layer about new UHCI device. |
push edx |
mov al, ah |
call uhci_new_device |
pop edx |
test eax, eax |
jnz .nothing |
; 4. If something at the protocol layer has failed |
; (no memory, no bus address), disable the port and stop the initialization. |
.disable_exit: |
in ax, dx |
and al, not 4 |
out dx, ax ; disable the port |
jmp [usbhc_api.usb_test_pending_port] |
.nothing: |
ret |
endp |
; This procedure is called from uhci_port_init and from hub support code |
; when a new device is connected and has been reset. |
; It calls usb_new_device at the protocol layer with correct parameters. |
; in: esi -> usb_controller, eax = speed; |
; UHCI is USB1 device, so only low bit of eax (LowSpeed) is used. |
proc uhci_new_device |
; 1. Clear all bits of speed except bit 0. |
and eax, 1 |
; 2. Store the speed for the protocol layer. |
mov [esi+usb_controller.ResettingSpeed], al |
; 3. Create pseudo-pipe in the stack. |
; See uhci_init_pipe: only .Controller and .Token fields are used. |
push esi ; fill .Controller field |
mov ecx, esp |
shl eax, 20 ; bit 20 = LowSpeedDevice |
push eax ; ignored (ErrorTD) |
push eax ; .Token field: DeviceAddress is zero, bit 20 = LowSpeedDevice |
; 4. Notify the protocol layer. |
invoke usbhc_api.usb_new_device |
; 5. Cleanup the stack after step 3 and return. |
add esp, 12 |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; and stores USB device address in the uhci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
proc uhci_set_device_address |
mov byte [ebx+uhci_pipe.Token+1-sizeof.uhci_pipe], cl |
jmp [usbhc_api.usb_subscription_done] |
endp |
; This procedure returns USB device address from the uhci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = endpoint address |
proc uhci_get_device_address |
mov al, byte [ebx+uhci_pipe.Token+1-sizeof.uhci_pipe] |
and eax, 7Fh |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; if the device does not accept SET_ADDRESS command and needs |
; to be disabled at the port level. |
; in: esi -> usb_controller, ecx = port (zero-based) |
proc uhci_port_disable |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+UhciPort1StatusReg+ecx*2] |
in ax, dx |
and al, not 4 |
out dx, ax |
ret |
endp |
; This procedure is called from usb_get_descr8_callback when |
; the packet size for zero endpoint becomes known and |
; stores the packet size in uhci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
proc uhci_set_endpoint_packet_size |
dec ecx |
shl ecx, 21 |
and [ebx+uhci_pipe.Token-sizeof.uhci_pipe], (1 shl 21) - 1 |
or [ebx+uhci_pipe.Token-sizeof.uhci_pipe], ecx |
; uhci_pipe.Token field is purely for software bookkeeping and does not affect |
; the hardware; thus, we can continue initialization immediately. |
jmp [usbhc_api.usb_subscription_done] |
endp |
; This procedure is called from API usb_open_pipe and processes |
; the controller-specific part of this API. See docs. |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
proc uhci_init_pipe |
; inherit some variables from the parent usb_open_pipe |
virtual at ebp-12 |
.speed db ? |
rb 3 |
.bandwidth dd ? |
.target dd ? |
rd 2 |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; 1. Initialize ErrorTD to zero. |
and [edi+uhci_pipe.ErrorTD-sizeof.uhci_pipe], 0 |
; 2. Initialize HeadTD to the physical address of the first TD. |
push eax ; store pointer to the first TD for step 4 |
sub eax, sizeof.uhci_gtd |
invoke GetPhysAddr |
mov [edi+uhci_pipe.HeadTD-sizeof.uhci_pipe], eax |
; 3. Initialize Token field: |
; take DeviceAddress and LowSpeedDevice from the parent pipe, |
; take Endpoint and MaximumLength fields from API arguments, |
; set PID depending on pipe type and provided pipe direction, |
; set DataToggle to zero. |
mov eax, [ecx+uhci_pipe.Token-sizeof.uhci_pipe] |
and eax, 0x107F00 ; keep DeviceAddress and LowSpeedDevice |
mov edx, [.endpoint] |
and edx, 15 |
shl edx, 15 |
or eax, edx |
mov edx, [.maxpacket] |
dec edx |
shl edx, 21 |
or eax, edx |
mov al, USB_PID_SETUP |
cmp [.type], CONTROL_PIPE |
jz @f |
mov al, USB_PID_OUT |
test byte [.endpoint], 80h |
jz @f |
mov al, USB_PID_IN |
@@: |
mov [edi+uhci_pipe.Token-sizeof.uhci_pipe], eax |
bt eax, 20 |
setc [.speed] |
; 4. Initialize the first TD: |
; copy Token from uhci_pipe.Token zeroing reserved bit 20, |
; set ControlStatus for future transfers, bit make it inactive, |
; set bit 0 in NextTD = "no next TD", |
; zero OrigBufferInfo. |
pop edx ; restore pointer saved in step 2 |
mov [edx+uhci_gtd.Token-sizeof.uhci_gtd], eax |
and byte [edx+uhci_gtd.Token+2-sizeof.uhci_gtd], not (1 shl (20-16)) |
and eax, 1 shl 20 |
shl eax, 6 |
or eax, UHCI_INVALID_LENGTH + (3 shl 27) |
; not processed, inactive, allow 3 errors |
and [edx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 0 |
mov [edx+uhci_gtd.ControlStatus-sizeof.uhci_gtd], eax |
mov [edx+uhci_gtd.NextTD-sizeof.uhci_gtd], 1 |
; 5. Select the corresponding list and insert to the list. |
; 5a. Use Control list for control pipes, Bulk list for bulk pipes. |
lea edx, [esi+uhci_controller.ControlED.SoftwarePart-sizeof.uhci_controller] |
cmp [.type], BULK_PIPE |
jb .insert ; control pipe |
lea edx, [esi+uhci_controller.BulkED.SoftwarePart-sizeof.uhci_controller] |
jz .insert ; bulk pipe |
.interrupt_pipe: |
; 5b. For interrupt pipes, let the scheduler select the appropriate list |
; based on the current bandwidth distribution and the requested bandwidth. |
; This could fail if the requested bandwidth is not available; |
; if so, return an error. |
lea edx, [esi + uhci_controller.IntEDs - sizeof.uhci_controller] |
lea eax, [esi + uhci_controller.IntEDs + 32*sizeof.uhci_static_ep - sizeof.uhci_controller] |
movi ecx, 64 |
call usb1_select_interrupt_list |
test edx, edx |
jz .return0 |
.insert: |
; Insert to the head of the corresponding list. |
; Note: inserting to the head guarantees that the list traverse in |
; uhci_process_updated_schedule, once started, will not interact with new pipes. |
; However, we still need to ensure that links in the new pipe (edi.NextVirt) |
; are initialized before links to the new pipe (edx.NextVirt). |
; 5c. Insert in the list of virtual addresses. |
mov ecx, [edx+usb_pipe.NextVirt] |
mov [edi+usb_pipe.NextVirt], ecx |
mov [edi+usb_pipe.PrevVirt], edx |
mov [ecx+usb_pipe.PrevVirt], edi |
mov [edx+usb_pipe.NextVirt], edi |
; 5d. Insert in the hardware list: copy previous NextQH to the new pipe, |
; store the physical address of the new pipe to previous NextQH. |
mov ecx, [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart] |
mov [edi+uhci_pipe.NextQH-sizeof.uhci_pipe], ecx |
lea eax, [edi-sizeof.uhci_pipe] |
invoke GetPhysAddr |
inc eax |
inc eax |
mov [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart], eax |
; 6. Return with nonzero eax. |
ret |
.return0: |
xor eax, eax |
ret |
endp |
; This procedure is called when a pipe is closing (either due to API call |
; or due to disconnect); it unlinks a pipe from the corresponding list. |
if uhci_static_ep.SoftwarePart <> sizeof.uhci_pipe |
.err uhci_unlink_pipe assumes that uhci_static_ep.SoftwarePart == sizeof.uhci_pipe |
end if |
proc uhci_unlink_pipe |
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE |
jnz @f |
mov eax, [ebx+uhci_pipe.Token-sizeof.uhci_pipe] |
cmp al, USB_PID_IN |
setz ch |
bt eax, 20 |
setc cl |
add eax, 1 shl 21 |
shr eax, 21 |
stdcall usb1_interrupt_list_unlink, eax, ecx |
@@: |
; Note: we need to ensure that NextVirt field of the pipe is not modified; |
; this procedure can be called while uhci_process_updated_schedule processes |
; the same pipe, and it needs a correct NextVirt field to continue. |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
; Note: eax could be either usb_pipe or usb_static_ep; |
; fortunately, NextQH and SoftwarePart have same offsets in both. |
mov edx, [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe] |
mov [eax+uhci_pipe.NextQH-sizeof.uhci_pipe], edx |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and allocates required packets for the given transfer stage. |
; ebx = pipe, other parameters are passed through the stack |
proc uhci_alloc_transfer stdcall uses edi, buffer:dword, size:dword, flags:dword, td:dword, direction:dword |
locals |
token dd ? |
origTD dd ? |
packetSize dd ? ; must be the last variable, see usb_init_transfer |
endl |
; 1. [td] will be the first packet in the transfer. |
; Save it to allow unrolling if something will fail. |
mov eax, [td] |
mov [origTD], eax |
; In UHCI one TD describes one packet, transfers should be split into parts |
; with size <= endpoint max packet size. |
; 2. Get the maximum packet size for endpoint from uhci_pipe.Token |
; and generate Token field for TDs. |
mov edi, [ebx+uhci_pipe.Token-sizeof.uhci_pipe] |
mov eax, edi |
shr edi, 21 |
inc edi |
; zero packet size (it will be set for every packet individually), |
; zero reserved bit 20, |
and eax, (1 shl 20) - 1 |
mov [packetSize], edi |
; set the correct PID if it is different from the pipe-wide PID |
; (Data and Status stages of control transfers), |
mov ecx, [direction] |
and ecx, 3 |
jz @f |
mov al, USB_PID_OUT |
dec ecx |
jz @f |
mov al, USB_PID_IN |
@@: |
; set the toggle bit for control transfers, |
mov ecx, [direction] |
test cl, 1 shl 3 |
jz @f |
and ecx, 1 shl 2 |
and eax, not (1 shl 19) |
shl ecx, 19-2 |
or eax, ecx |
@@: |
; store the resulting Token in the stack variable. |
mov [token], eax |
; 3. While the remaining data cannot fit in one packet, |
; allocate full packets (of maximal possible size). |
.fullpackets: |
cmp [size], edi |
jbe .lastpacket |
call uhci_alloc_packet |
test eax, eax |
jz .fail |
mov [td], eax |
add [buffer], edi |
sub [size], edi |
jmp .fullpackets |
.lastpacket: |
; 4. The remaining data can fit in one packet; |
; allocate the last packet with size = size of remaining data. |
mov eax, [size] |
mov [packetSize], eax |
call uhci_alloc_packet |
test eax, eax |
jz .fail |
; 5. Clear 'short packets are not allowed' bit for the last packet, |
; if the caller requested this. |
; Note: even if the caller says that short transfers are ok, |
; all packets except the last one are marked as 'must be complete': |
; if one of them will be short, the software intervention is needed |
; to skip remaining packets; uhci_process_finalized_td will handle this |
; transparently to the caller. |
test [flags], 1 |
jz @f |
and byte [ecx+uhci_gtd.ControlStatus+3-sizeof.uhci_gtd], not (1 shl (29-24)) |
and byte [ecx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], not 1 |
@@: |
; 6. Update toggle bit in uhci_pipe structure from current value of [token]. |
mov edx, [token] |
xor edx, [ebx+uhci_pipe.Token-sizeof.uhci_pipe] |
and edx, 1 shl 19 |
xor [ebx+uhci_pipe.Token-sizeof.uhci_pipe], edx |
.nothing: |
ret |
.fail: |
mov edi, uhci_hardware_func |
mov eax, [td] |
invoke usbhc_api.usb_undo_tds, [origTD] |
xor eax, eax |
jmp .nothing |
endp |
; Helper procedure for uhci_alloc_transfer. Allocates one packet. |
proc uhci_alloc_packet |
; inherit some variables from the parent uhci_alloc_transfer |
virtual at ebp-12 |
.token dd ? |
.origTD dd ? |
.packetSize dd ? |
rd 2 |
.buffer dd ? |
.transferSize dd ? |
.Flags dd ? |
.td dd ? |
.direction dd ? |
end virtual |
; 1. In UHCI all data for one packet must be on the same page. |
; Thus, if the given buffer splits page boundary, we need a temporary buffer |
; and code that transfers data between the given buffer and the temporary one. |
; 1a. There is no buffer for zero-length packets. |
xor eax, eax |
cmp [.packetSize], eax |
jz .notempbuf |
; 1b. A temporary buffer is not required if the first and the last bytes |
; of the given buffer are the same except lower 12 bits. |
mov edx, [.buffer] |
add edx, [.packetSize] |
dec edx |
xor edx, [.buffer] |
test edx, -0x1000 |
jz .notempbuf |
; 1c. We need a temporary buffer. Allocate [packetSize]*2 bytes, so that |
; there must be [packetSize] bytes on one page, |
; plus space for a header uhci_original_buffer. |
mov eax, [.packetSize] |
add eax, eax |
add eax, sizeof.uhci_original_buffer |
invoke Kmalloc |
; 1d. If failed, return zero. |
test eax, eax |
jz .nothing |
; 1e. Test whether [.packetSize] bytes starting from |
; eax + sizeof.uhci_original_buffer are in the same page. |
; If so, use eax + sizeof.uhci_original_buffer as a temporary buffer. |
; Otherwise, use the beginning of the next page as a temporary buffer |
; (since we have overallocated, sufficient space must remain). |
lea ecx, [eax+sizeof.uhci_original_buffer] |
mov edx, ecx |
add edx, [.packetSize] |
dec edx |
xor edx, ecx |
test edx, -0x1000 |
jz @f |
mov ecx, eax |
or ecx, 0xFFF |
inc ecx |
@@: |
mov [eax+uhci_original_buffer.UsedBuffer], ecx |
mov ecx, [.buffer] |
mov [eax+uhci_original_buffer.OrigBuffer], ecx |
; 1f. For SETUP and OUT packets, copy data from the given buffer |
; to the temporary buffer now. For IN packets, data go in other direction |
; when the transaction completes. |
cmp byte [.token], USB_PID_IN |
jz .nocopy |
push esi edi |
mov esi, ecx |
mov edi, [eax+uhci_original_buffer.UsedBuffer] |
mov ecx, [.packetSize] |
mov edx, ecx |
shr ecx, 2 |
and edx, 3 |
rep movsd |
mov ecx, edx |
rep movsb |
pop edi esi |
.nocopy: |
.notempbuf: |
; 2. Allocate the next TD. |
push eax |
call uhci_alloc_td |
pop edx |
; If failed, free the temporary buffer (if it was allocated) and return zero. |
test eax, eax |
jz .fail |
; 3. Initialize controller-independent parts of both TDs. |
push edx |
invoke usbhc_api.usb_init_transfer |
; 4. Initialize the next TD: |
; mark it as last one (this will be changed when further packets will be |
; allocated), copy Token field from uhci_pipe.Token zeroing bit 20, |
; generate ControlStatus field, mark as Active |
; (for last descriptor, this will be changed by uhci_insert_transfer), |
; zero OrigBufferInfo (otherwise uhci_free_td would try to free it). |
and [eax+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 0 |
mov [eax+uhci_gtd.NextTD-sizeof.uhci_gtd], 1 ; no next TD |
mov edx, [ebx+uhci_pipe.Token-sizeof.uhci_pipe] |
mov [eax+uhci_gtd.Token-sizeof.uhci_gtd], edx |
and byte [eax+uhci_gtd.Token+2-sizeof.uhci_gtd], not (1 shl (20-16)) |
and edx, 1 shl 20 |
shl edx, 6 |
or edx, UHCI_INVALID_LENGTH + (1 shl 23) + (3 shl 27) |
; not processed, active, allow 3 errors |
mov [eax+uhci_gtd.ControlStatus-sizeof.uhci_gtd], edx |
; 5. Initialize remaining fields of the current TD. |
; 5a. Store pointer to the buffer allocated in step 1 (or zero). |
pop [ecx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd] |
; 5b. Store physical address of the next TD. |
push eax |
sub eax, sizeof.uhci_gtd |
invoke GetPhysAddr |
; for Control/Bulk pipes, use Depth traversal unless this is the first TD |
; in the transfer stage; |
; uhci_insert_transfer will set Depth traversal for the first TD and clear |
; it in the last TD |
test [ebx+usb_pipe.Type], 1 |
jnz @f |
cmp ecx, [ebx+usb_pipe.LastTD] |
jz @f |
or eax, 4 |
@@: |
mov [ecx+uhci_gtd.NextTD-sizeof.uhci_gtd], eax |
; 5c. Store physical address of the buffer: zero if no data present, |
; the temporary buffer if it was allocated, the given buffer otherwise. |
xor eax, eax |
cmp [.packetSize], eax |
jz .hasphysbuf |
mov eax, [.buffer] |
mov edx, [ecx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd] |
test edx, edx |
jz @f |
mov eax, [edx+uhci_original_buffer.UsedBuffer] |
@@: |
invoke GetPhysAddr |
.hasphysbuf: |
mov [ecx+uhci_gtd.Buffer-sizeof.uhci_gtd], eax |
; 5d. For IN transfers, disallow short packets. |
; This will be overridden, if needed, by uhci_alloc_transfer. |
mov eax, [.token] |
mov edx, [.packetSize] |
dec edx |
cmp al, USB_PID_IN |
jnz @f |
or byte [ecx+uhci_gtd.ControlStatus+3-sizeof.uhci_gtd], 1 shl (29-24) ; disallow short packets |
or byte [ecx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 1 |
@@: |
; 5e. Get Token field: combine [.token] with [.packetSize]. |
shl edx, 21 |
or edx, eax |
mov [ecx+uhci_gtd.Token-sizeof.uhci_gtd], edx |
; 6. Flip toggle bit in [.token]. |
xor eax, 1 shl 19 |
mov [.token], eax |
; 7. Return pointer to the next TD. |
pop eax |
.nothing: |
ret |
.fail: |
xchg eax, edx |
invoke Kfree |
xor eax, eax |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and activates the transfer which was previously allocated by |
; uhci_alloc_transfer. |
; ecx -> last descriptor for the transfer, ebx -> usb_pipe |
proc uhci_insert_transfer |
; DEBUGF 1,'K : uhci_insert_transfer: eax=%x, ecx=%x, [esp+4]=%x\n',eax,ecx,[esp+4] |
and byte [eax+uhci_gtd.ControlStatus+2-sizeof.uhci_gtd], not (1 shl (23-16)) ; clear Active bit |
or byte [ecx+uhci_gtd.ControlStatus+3-sizeof.uhci_gtd], 1 shl (24-24) ; set InterruptOnComplete bit |
mov eax, [esp+4] |
or byte [eax+uhci_gtd.ControlStatus+2-sizeof.uhci_gtd], 1 shl (23-16) ; set Active bit |
test [ebx+usb_pipe.Type], 1 |
jnz @f |
or byte [eax+uhci_gtd.NextTD-sizeof.uhci_gtd], 4 ; set Depth bit |
@@: |
ret |
endp |
; Allocates one endpoint structure for OHCI. |
; Returns pointer to software part (usb_pipe) in eax. |
proc uhci_alloc_pipe |
push ebx |
mov ebx, uhci_ep_mutex |
invoke usbhc_api.usb_allocate_common, (sizeof.uhci_pipe + sizeof.usb_pipe + 0Fh) and not 0Fh |
test eax, eax |
jz @f |
add eax, sizeof.uhci_pipe |
@@: |
pop ebx |
ret |
endp |
; Free memory associated with pipe. |
; For UHCI, this includes usb_pipe structure and ErrorTD, if present. |
proc uhci_free_pipe |
mov eax, [esp+4] |
mov eax, [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe] |
test eax, eax |
jz @f |
stdcall uhci_free_td, eax |
@@: |
sub dword [esp+4], sizeof.uhci_pipe |
jmp [usbhc_api.usb_free_common] |
endp |
; Allocates one general transfer descriptor structure for UHCI. |
; Returns pointer to software part (usb_gtd) in eax. |
proc uhci_alloc_td |
push ebx |
mov ebx, uhci_gtd_mutex |
invoke usbhc_api.usb_allocate_common, (sizeof.uhci_gtd + sizeof.usb_gtd + 0Fh) and not 0Fh |
test eax, eax |
jz @f |
add eax, sizeof.uhci_gtd |
@@: |
pop ebx |
ret |
endp |
; Free all memory associated with one TD. |
; For UHCI, this includes memory for uhci_gtd itself |
; and the temporary buffer, if present. |
proc uhci_free_td |
mov eax, [esp+4] |
mov eax, [eax+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd] |
and eax, not 1 |
jz .nobuf |
invoke Kfree |
.nobuf: |
sub dword [esp+4], sizeof.uhci_gtd |
jmp [usbhc_api.usb_free_common] |
endp |
include 'usb1_scheduler.inc' |
define_controller_name uhci |
section '.data' readable writable |
include '../peimport.inc' |
include_debug_strings |
IncludeIGlobals |
IncludeUGlobals |
align 4 |
usbhc_api usbhc_func |
uhci_ep_first_page dd ? |
uhci_ep_mutex MUTEX |
uhci_gtd_first_page dd ? |
uhci_gtd_mutex MUTEX |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/drivers/usb/usb1_scheduler.inc |
---|
0,0 → 1,232 |
; Implementation of periodic transaction scheduler for USB. |
; Bandwidth dedicated to periodic transactions is limited, so |
; different pipes should be scheduled as uniformly as possible. |
; USB1 scheduler. |
; Algorithm is simple: |
; when adding a pipe, optimize the following quantity: |
; * for every millisecond, take all bandwidth scheduled to periodic transfers, |
; * calculate maximum over all milliseconds, |
; * select a variant which minimizes that maximum; |
; when removing a pipe, do nothing (except for bookkeeping). |
; The caller must provide CONTROLLER_NAME define. |
macro define_controller_name name |
{ |
_hci_static_ep.SoftwarePart = name # _static_ep.SoftwarePart |
_hci_static_ep.NextList = name # _static_ep.NextList |
sizeof._hci_static_ep = sizeof. # name # _static_ep |
} |
; Select a list for a new pipe. |
; in: esi -> usb_controller, maxpacket, type, interval can be found in the stack |
; in: ecx = 2 * maximal interval = total number of periodic lists + 1 |
; in: edx -> {u|o}hci_static_ep for the first list |
; in: eax -> byte past {u|o}hci_static_ep for the last list in the first group |
; out: edx -> usb_static_ep for the selected list or zero if failed |
proc usb1_select_interrupt_list |
; inherit some variables from usb_open_pipe |
virtual at ebp-12 |
.speed db ? |
rb 3 |
.bandwidth dd ? |
.target dd ? |
dd ? |
dd ? |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
push ebx edi ; save used registers to be stdcall |
push eax ; save eax for checks in step 3 |
; 1. Only intervals 2^k ms can be supported. |
; The core specification says that the real interval should not be greater |
; than the interval given by the endpoint descriptor, but can be less. |
; Determine the actual interval as 2^k ms. |
mov eax, ecx |
; 1a. Set [.interval] to 1 if it was zero; leave it as is otherwise |
cmp [.interval], 1 |
adc [.interval], 0 |
; 1b. Divide ecx by two while it is strictly greater than [.interval]. |
@@: |
shr ecx, 1 |
cmp [.interval], ecx |
jb @b |
; ecx = the actual interval |
; |
; For example, let ecx = 8, eax = 64. |
; The scheduler space is 32 milliseconds, |
; we need to schedule something every 8 ms; |
; there are 8 variants: schedule at times 0,8,16,24, |
; schedule at times 1,9,17,25,..., schedule at times 7,15,23,31. |
; Now concentrate: there are three nested loops, |
; * the innermost loop calculates the total periodic bandwidth scheduled |
; in the given millisecond, |
; * the intermediate loop calculates the maximum over all milliseconds |
; in the given variant, that is the quantity we're trying to minimize, |
; * the outermost loop checks all variants. |
; 2. Calculate offset between the first list and the first list for the |
; selected interval, in bytes; save in the stack for step 4. |
sub eax, ecx |
sub eax, ecx |
imul eax, sizeof._hci_static_ep |
push eax |
imul ebx, ecx, sizeof._hci_static_ep |
; 3. Select the best variant. |
; 3a. The outermost loop. |
; Prepare for the loop: set the current optimal bandwidth to maximum |
; possible value (so that any variant will pass the first comparison), |
; calculate delta for the intermediate loop. |
or [.bandwidth], -1 |
.varloop: |
; 3b. The intermediate loop. |
; Prepare for the loop: set the maximum to be calculated to zero, |
; save counter of the outermost loop. |
xor edi, edi |
push edx |
virtual at esp |
.cur_variant dd ? ; step 3b |
.result_delta dd ? ; step 2 |
.group1_limit dd ? ; function prolog |
end virtual |
.calc_max_bandwidth: |
; 3c. The innermost loop. Sum over all lists. |
xor eax, eax |
push edx |
.calc_bandwidth: |
add eax, [edx+_hci_static_ep.SoftwarePart+usb_static_ep.Bandwidth] |
mov edx, [edx+_hci_static_ep.NextList] |
test edx, edx |
jnz .calc_bandwidth |
pop edx |
; 3d. The intermediate loop continued: update maximum. |
cmp eax, edi |
jb @f |
mov edi, eax |
@@: |
; 3e. The intermediate loop continued: advance counter. |
add edx, ebx |
cmp edx, [.group1_limit] |
jb .calc_max_bandwidth |
; 3e. The intermediate loop done: restore counter of the outermost loop. |
pop edx |
; 3f. The outermost loop continued: if the current variant is |
; better (maybe not strictly) then the previous optimum, update |
; the optimal bandwidth and resulting list. |
cmp edi, [.bandwidth] |
ja @f |
mov [.bandwidth], edi |
mov [.target], edx |
@@: |
; 3g. The outermost loop continued: advance counter. |
add edx, sizeof._hci_static_ep |
dec ecx |
jnz .varloop |
; 4. Calculate bandwidth for the new pipe. |
mov eax, [.maxpacket] |
mov cl, [.speed] |
mov ch, byte [.endpoint] |
and ch, 80h |
call calc_usb1_bandwidth |
; 5. Get the pointer to the best list. |
pop edx ; restore value from step 2 |
pop ecx ; purge stack var from prolog |
add edx, [.target] |
; 6. Check that bandwidth for the new pipe plus old bandwidth |
; still fits to maximum allowed by the core specification, 90% of 12000 bits. |
mov ecx, eax |
add ecx, [.bandwidth] |
cmp ecx, 10800 |
ja .no_bandwidth |
; 7. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return. |
add edx, _hci_static_ep.SoftwarePart |
add [edx+usb_static_ep.Bandwidth], eax |
pop edi ebx ; restore used registers to be stdcall |
ret |
.no_bandwidth: |
dbgstr 'Periodic bandwidth limit reached' |
xor edx, edx |
pop edi ebx |
ret |
endp |
; Pipe is removing, update the corresponding lists. |
; We do not reorder anything, so just update book-keeping variable |
; in the list header. |
proc usb1_interrupt_list_unlink |
virtual at esp |
dd ? ; return address |
.maxpacket dd ? |
.lowspeed db ? |
.direction db ? |
rb 2 |
end virtual |
; calculate bandwidth on the bus |
mov eax, [.maxpacket] |
mov ecx, dword [.lowspeed] |
call calc_usb1_bandwidth |
; find list header |
mov edx, ebx |
@@: |
mov edx, [edx+usb_pipe.NextVirt] |
cmp [edx+usb_pipe.Controller], esi |
jz @b |
; subtract pipe bandwidth |
sub [edx+usb_static_ep.Bandwidth], eax |
ret 8 |
endp |
; Helper procedure for USB1 scheduler: calculate bandwidth on the bus. |
; in: low 11 bits of eax = payload size in bytes |
; in: cl = 0 - full-speed, nonzero - high-speed |
; in: ch = 0 - OUT, nonzero - IN |
; out: eax = maximal bandwidth in FS-bits |
proc calc_usb1_bandwidth |
and eax, (1 shl 11) - 1 ; get payload for one transaction |
add eax, 3 ; add 3 bytes for other fields in data packet, PID+CRC16 |
test cl, cl |
jnz .low_speed |
; Multiply by 8 for bytes -> bits, by 7/6 to accomodate bit stuffing |
; and by 401/400 for IN transfers to accomodate timers difference |
; 9+107/300 for IN transfers, 9+1/3 for OUT transfers |
; For 0 <= eax < 09249355h, floor(eax * 107/300) = floor(eax * 5B4E81B5h / 2^32). |
; For 0 <= eax < 80000000h, floor(eax / 3) = floor(eax * 55555556h / 2^32). |
mov edx, 55555556h |
test ch, ch |
jz @f |
mov edx, 5B4E81B5h |
@@: |
lea ecx, [eax*9] |
mul edx |
; Add 93 extra bits: 39 bits for Token packet (8 for SYNC, 24 for token+address, |
; 4 extra bits for possible bit stuffing in token+address, 3 for EOP), |
; 18 bits for bus turn-around, 11 bits for SYNC+EOP in Data packet plus 1 bit |
; for possible timers difference, 2 bits for inter-packet delay, 20 bits for |
; Handshake packet, 2 bits for another inter-packet delay. |
lea eax, [ecx+edx+93] |
ret |
.low_speed: |
; Multiply by 8 for bytes -> bits, by 7/6 to accomodate bit stuffing, |
; by 8 for LS -> FS and by 406/50 for IN transfers to accomodate timers difference. |
; 75+59/75 for IN transfers, 74+2/3 for OUT transfers. |
mov edx, 0AAAAAABh |
test ch, ch |
mov ecx, 74 |
jz @f |
mov edx, 0C962FC97h |
inc ecx |
@@: |
imul ecx, eax |
mul edx |
; Add 778 extra bits: |
; 16 bits for PRE packet, 4 bits for hub delay, 8*39 bits for Token packet |
; 8*18 bits for bus turn-around |
; (406/50)*11 bits for SYNC+EOP in Data packet, |
; 8*2 bits for inter-packet delay, |
; 16 bits for PRE packet, 4 bits for hub delay, 8*20 bits for Handshake packet, |
; 8*2 bits for another inter-packet delay. |
lea eax, [ecx+edx+778] |
ret |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |