Subversion Repositories Kolibri OS

Compare Revisions

No changes between revisions

Regard whitespace Rev 4417 → Rev 4418

/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