separate USB host controller code into external drivers

git-svn-id: svn://kolibrios.org@4418 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
CleverMouse 2013-12-30 11:18:33 +00:00
parent bcb5772288
commit a10422fbce
23 changed files with 1037 additions and 1171 deletions

View File

@ -147,6 +147,9 @@ FASM_PROGRAMS:=\
drivers/rtl8139.obj:DRIVERS/RTL8139.obj:$(REPOSITORY)/drivers/ethernet/RTL8139.asm \
drivers/rtl8169.obj:DRIVERS/RTL8169.obj:$(REPOSITORY)/drivers/ethernet/RTL8169.asm \
drivers/sis900.obj:DRIVERS/SIS900.obj:$(REPOSITORY)/drivers/ethernet/sis900.asm \
drivers/uhci.sys:DRIVERS/UHCI.SYS:$(REPOSITORY)/drivers/usb/uhci.asm \
drivers/ohci.sys:DRIVERS/OHCI.SYS:$(REPOSITORY)/drivers/usb/ohci.asm \
drivers/ehci.sys:DRIVERS/EHCI.SYS:$(REPOSITORY)/drivers/usb/ehci.asm \
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
File|Managers/kfm:File|Managers/KFM:$(PROGS)/fs/kfm/trunk/kfm.asm \
File|Managers/opendial:File|Managers/OPENDIAL:$(PROGS)/fs/opendial/opendial.asm \

View File

@ -147,6 +147,9 @@ FASM_PROGRAMS:=\
drivers/rtl8139.obj:DRIVERS/RTL8139.obj:$(REPOSITORY)/drivers/ethernet/RTL8139.asm \
drivers/rtl8169.obj:DRIVERS/RTL8169.obj:$(REPOSITORY)/drivers/ethernet/RTL8169.asm \
drivers/sis900.obj:DRIVERS/SIS900.obj:$(REPOSITORY)/drivers/ethernet/sis900.asm \
drivers/uhci.sys:DRIVERS/UHCI.SYS:$(REPOSITORY)/drivers/usb/uhci.asm \
drivers/ohci.sys:DRIVERS/OHCI.SYS:$(REPOSITORY)/drivers/usb/ohci.asm \
drivers/ehci.sys:DRIVERS/EHCI.SYS:$(REPOSITORY)/drivers/usb/ehci.asm \
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
File|Managers/kfm:File|Managers/KFM:$(PROGS)/fs/kfm/trunk/kfm.asm \
File|Managers/opendial:File|Managers/OPENDIAL:$(PROGS)/fs/opendial/opendial.asm \

View File

@ -147,6 +147,9 @@ FASM_PROGRAMS:=\
drivers/rtl8139.obj:DRIVERS/RTL8139.obj:$(REPOSITORY)/drivers/ethernet/RTL8139.asm \
drivers/rtl8169.obj:DRIVERS/RTL8169.obj:$(REPOSITORY)/drivers/ethernet/RTL8169.asm \
drivers/sis900.obj:DRIVERS/SIS900.obj:$(REPOSITORY)/drivers/ethernet/sis900.asm \
drivers/uhci.sys:DRIVERS/UHCI.SYS:$(REPOSITORY)/drivers/usb/uhci.asm \
drivers/ohci.sys:DRIVERS/OHCI.SYS:$(REPOSITORY)/drivers/usb/ohci.asm \
drivers/ehci.sys:DRIVERS/EHCI.SYS:$(REPOSITORY)/drivers/usb/ehci.asm \
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
File|Managers/kfm:File|Managers/KFM:$(PROGS)/fs/kfm/trunk/kfm.asm \
File|Managers/opendial:File|Managers/OPENDIAL:$(PROGS)/fs/opendial/opendial.asm \

View File

@ -148,6 +148,9 @@ FASM_PROGRAMS:=\
drivers/rtl8139.obj:DRIVERS/RTL8139.obj:$(REPOSITORY)/drivers/ethernet/RTL8139.asm \
drivers/rtl8169.obj:DRIVERS/RTL8169.obj:$(REPOSITORY)/drivers/ethernet/RTL8169.asm \
drivers/sis900.obj:DRIVERS/SIS900.obj:$(REPOSITORY)/drivers/ethernet/sis900.asm \
drivers/uhci.sys:DRIVERS/UHCI.SYS:$(REPOSITORY)/drivers/usb/uhci.asm \
drivers/ohci.sys:DRIVERS/OHCI.SYS:$(REPOSITORY)/drivers/usb/ohci.asm \
drivers/ehci.sys:DRIVERS/EHCI.SYS:$(REPOSITORY)/drivers/usb/ehci.asm \
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
File|Managers/kfm:File|Managers/KFM:$(PROGS)/fs/kfm/trunk/kfm.asm \
File|Managers/opendial:File|Managers/OPENDIAL:$(PROGS)/fs/opendial/opendial.asm \

View File

@ -147,6 +147,9 @@ FASM_PROGRAMS:=\
drivers/rtl8139.obj:DRIVERS/RTL8139.obj:$(REPOSITORY)/drivers/ethernet/RTL8139.asm \
drivers/rtl8169.obj:DRIVERS/RTL8169.obj:$(REPOSITORY)/drivers/ethernet/RTL8169.asm \
drivers/sis900.obj:DRIVERS/SIS900.obj:$(REPOSITORY)/drivers/ethernet/sis900.asm \
drivers/uhci.sys:DRIVERS/UHCI.SYS:$(REPOSITORY)/drivers/usb/uhci.asm \
drivers/ohci.sys:DRIVERS/OHCI.SYS:$(REPOSITORY)/drivers/usb/ohci.asm \
drivers/ehci.sys:DRIVERS/EHCI.SYS:$(REPOSITORY)/drivers/usb/ehci.asm \
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
File|Managers/kfm:File|Managers/KFM:$(PROGS)/fs/kfm/trunk/kfm.asm \
File|Managers/opendial:File|Managers/OPENDIAL:$(PROGS)/fs/opendial/opendial.asm \

View File

@ -236,9 +236,11 @@ debug_beginf
pushad
movzx ecx,al
mov ebx,1
; mov ecx,sys_msg_board
; call ecx ; sys_msg_board
stdcall SysMsgBoard
if defined SysMsgBoard._pe_import
invoke SysMsgBoard
else
stdcall SysMsgBoard
end if
popad
ret
debug_endf
@ -251,9 +253,11 @@ debug_beginf
movzx ecx,byte[edx]
or cl,cl
jz .l2
; mov ecx,sys_msg_board
; call ecx ; sys_msg_board
stdcall SysMsgBoard
if defined SysMsgBoard._pe_import
invoke SysMsgBoard
else
stdcall SysMsgBoard
end if
inc edx
jmp .l1
.l2: ret

View File

@ -1,7 +1,17 @@
; Code for EHCI controllers.
; Note: it should be moved to an external driver,
; it was convenient to have this code compiled into the kernel during initial
; development, but there are no reasons to keep it here.
; 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 =================================
@ -246,6 +256,11 @@ CapabilityParams dd ?
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
@ -258,8 +273,10 @@ end if
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
@ -276,12 +293,39 @@ ehci_hardware_func:
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.
@ -313,7 +357,7 @@ if (ehci_controller.IntEDs mod 0x1000) <> 0
.err assertion failed
end if
add eax, ehci_controller.IntEDs
call get_phys_addr
call [GetPhysAddr]
; 2b. Fill first 32 entries.
inc eax
inc eax ; set Type to EHCI_TYPE_QH
@ -372,24 +416,15 @@ end if
call ehci_init_static_endpoint
; 4. Create a virtual memory area to talk with the controller.
; 4a. Enable memory & bus master access.
mov ch, [.bus]
mov cl, 1
mov eax, ecx
mov bh, [.devfn]
mov bl, 4
call pci_read_reg
invoke PciRead16, dword [.bus], dword [.devfn], 4
or al, 6
xchg eax, ecx
call pci_write_reg
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax
; 4b. Read memory base address.
mov ah, [.bus]
mov al, 2
mov bl, 10h
call pci_read_reg
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.
stdcall map_io_mem, eax, 200h, PG_SW+PG_NOCACHE
invoke MapIoMem, eax, 200h, PG_SW+PG_NOCACHE
test eax, eax
jz .fail
; DEBUGF 1,'K : MMIO %x\n',eax
@ -397,9 +432,71 @@ 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]
mov ebx, [eax+EhciStructParamsReg]
add eax, ecx
if ehci_controller.MMIOBase2 <> ehci_controller.MMIOBase1 + 4
.err assertion failed
@ -426,7 +523,7 @@ end if
and dword [edi+EhciCommandReg], not 1
@@:
movi esi, 1
call delay_ms
invoke Sleep
test dword [edi+EhciStatusReg], 1 shl 12
jnz .stopped
loop @b
@ -438,7 +535,7 @@ end if
movi ecx, 50
@@:
movi esi, 1
call delay_ms
invoke Sleep
test dword [edi+EhciCommandReg], 2
jz .reset_ok
loop @b
@ -455,25 +552,21 @@ end if
mov dword [edi+EhciCtrlDataSegReg], 0
@@:
; 7b. Hook interrupt and enable appropriate interrupt sources.
mov ah, [.bus]
mov al, 0
mov bh, [.devfn]
mov bl, 3Ch
call pci_read_reg
invoke PciRead8, dword [.bus], dword [.devfn], 3Ch
; al = IRQ
DEBUGF 1,'K : attaching to IRQ %x\n',al
; DEBUGF 1,'K : attaching to IRQ %x\n',al
movzx eax, al
stdcall attach_int_handler, eax, ehci_irq, esi
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]
call get_phys_addr
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]
call get_phys_addr
invoke GetPhysAddr
mov dword [edi+EhciAsyncListReg], eax
; 7e. Configure operational details and run the controller.
mov dword [edi+EhciCommandReg], \
@ -498,7 +591,7 @@ end if
jz @f
push esi
movi esi, 20
call delay_ms
invoke Sleep
pop esi
@@:
; 9. Return pointer to usb_controller.
@ -510,7 +603,7 @@ end if
.fail_unmap:
pop eax
push eax
stdcall free_kernel_space, [eax+ehci_controller.MMIOBase1]
invoke FreeKernelSpace, [eax+ehci_controller.MMIOBase1]
.fail:
pop ecx
xor eax, eax
@ -529,7 +622,7 @@ proc ehci_init_static_endpoint
test esi, esi
jz @f
mov eax, esi
call get_phys_addr
invoke GetPhysAddr
inc eax
inc eax ; set Type to EHCI_TYPE_QH
@@:
@ -537,7 +630,8 @@ proc ehci_init_static_endpoint
mov [edi+ehci_static_ep.NextList], esi
mov byte [edi+ehci_static_ep.OverlayToken], 1 shl 6 ; halted
add edi, ehci_static_ep.SoftwarePart
call usb_init_static_endpoint
mov eax, [USBHCFunc]
call [eax+usbhc_func.usb_init_static_endpoint]
add edi, sizeof.ehci_static_ep - ehci_static_ep.SoftwarePart
ret
endp
@ -565,14 +659,10 @@ endp
; deal with USB devices.
proc ehci_kickoff_bios
; 1. Get the physical address of MMIO registers.
mov ah, [esi+PCIDEV.bus]
mov bh, [esi+PCIDEV.devfn]
mov al, 2
mov bl, 10h
call pci_read_reg
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.
stdcall map_io_mem, eax, 200h, PG_SW+PG_NOCACHE
invoke MapIoMem, eax, 200h, PG_SW+PG_NOCACHE
test eax, eax
jz .nothing
push eax ; push argument for step 8
@ -604,10 +694,7 @@ proc ehci_kickoff_bios
test bl, 3
jnz .no_capability
; In each iteration, read the current dword,
mov ah, [esi+PCIDEV.bus]
mov al, 2
mov bh, [esi+PCIDEV.devfn]
call pci_read_reg
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
@ -632,16 +719,11 @@ proc ehci_kickoff_bios
jz .has_ownership
; 4c. Request ownership.
inc ebx
mov cl, 1
mov ah, [esi+PCIDEV.bus]
mov al, 0
call pci_write_reg
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
mov ah, [esi+PCIDEV.bus]
mov al, 2
call pci_read_reg
invoke PciRead32, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx
dec ebx
dec ebx
test ah, 20h
@ -650,14 +732,12 @@ proc ehci_kickoff_bios
; If successful, go to 5.
mov dword [esp], 1000
@@:
mov ah, [esi+PCIDEV.bus]
mov al, 0
call pci_read_reg
invoke PciRead8, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx
test al, 1
jz .has_ownership
push esi
movi esi, 1
call delay_ms
invoke Sleep
pop esi
dec dword [esp]
jnz @b
@ -665,22 +745,15 @@ proc ehci_kickoff_bios
.force_ownership:
; 4f. BIOS has not responded within the timeout.
; Let's just clear BIOS ownership flag and hope that everything will be ok.
mov ah, [esi+PCIDEV.bus]
mov al, 0
mov cl, 0
call pci_write_reg
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
mov ah, [esi+PCIDEV.bus]
mov al, 2
mov ecx, eax
call pci_read_reg
invoke PciRead16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], ebx
and ax, 2000h
xchg eax, ecx
call pci_write_reg
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
@ -689,7 +762,7 @@ proc ehci_kickoff_bios
; 7. Now we can unblock interrupts in the processor.
popf
; 8. Release memory mapping created in step 2 and return.
call free_kernel_space
invoke FreeKernelSpace
.nothing:
ret
endp
@ -717,7 +790,6 @@ end virtual
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller]
spin_lock_irqsave [esi+usb_controller.WaitSpinlock]
mov eax, [edi+EhciStatusReg]
; DEBUGF 1,'K : [%d] EHCI status %x\n',[timer_ticks],eax
; 3. Check whether that interrupt has been generated by our controller.
; (One IRQ can be shared by several devices.)
and eax, [edi+EhciInterruptReg]
@ -739,7 +811,7 @@ end virtual
or [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], eax
spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock]
inc ebx
call usb_wakeup_if_needed
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
@ -751,8 +823,7 @@ endp
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address
proc ehci_set_device_address
mov byte [ebx+ehci_pipe.Token-sizeof.ehci_pipe], cl
call usb_subscribe_control
ret
jmp [usbhc_api.usb_subscribe_control]
endp
; This procedure returns USB device address from the ehci_pipe structure.
@ -785,8 +856,7 @@ proc ehci_set_endpoint_packet_size
or eax, ecx
mov [ebx+ehci_pipe.Token-sizeof.ehci_pipe], eax
; Wait until hardware cache is evicted.
call usb_subscribe_control
ret
jmp [usbhc_api.usb_subscribe_control]
endp
uglobal
@ -804,7 +874,7 @@ endg
proc ehci_alloc_pipe
push ebx
mov ebx, ehci_ep_mutex
stdcall usb_allocate_common, (sizeof.ehci_pipe + sizeof.usb_pipe + 1Fh) and not 1Fh
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
@ -821,7 +891,7 @@ virtual at esp
.ptr dd ?
end virtual
sub [.ptr], sizeof.ehci_pipe
jmp usb_free_common
jmp [usbhc_api.usb_free_common]
endp
; This procedure is called from API usb_open_pipe and processes
@ -853,7 +923,7 @@ end virtual
mov [eax+ehci_gtd.AlternateNextTD-sizeof.ehci_gtd], 1
; 3. Store physical address of the first TD.
sub eax, sizeof.ehci_gtd
call get_phys_addr
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.
@ -945,7 +1015,7 @@ end virtual
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 get_phys_addr
call [GetPhysAddr]
inc eax
inc eax
mov [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], eax
@ -963,7 +1033,7 @@ endp
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 : [%d] EHCI %x port %d state is %x\n',[timer_ticks],esi,ecx,eax
DEBUGF 1,'K : EHCI %x port %d state is %x\n',esi,ecx,eax
mov edx, eax
and ah, 0Ch
cmp ah, 4
@ -996,17 +1066,17 @@ proc ehci_new_port
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.
mov eax, [timer_ticks]
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 : [%d] EHCI %x: port %d has HS or FS device, resetting\n',[timer_ticks],esi,ecx
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 : [%d] EHCI %x: port %d has LS device, releasing\n',[timer_ticks],esi,ecx
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
@ -1051,7 +1121,8 @@ endl
; 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+ohci_pipe.Flags+2-sizeof.ohci_pipe]
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
@ -1092,7 +1163,7 @@ endl
mov eax, [ebx+usb_pipe.Controller]
add eax, ehci_controller.StopQueueTD - sizeof.ehci_controller
@@:
call get_phys_addr
call [GetPhysAddr]
mov edx, [origTD]
@@:
cmp edx, [esp]
@ -1106,7 +1177,7 @@ endl
.fail:
mov edi, ehci_hardware_func
mov eax, [td]
stdcall usb_undo_tds, [origTD]
invoke usbhc_api.usb_undo_tds, [origTD]
xor eax, eax
ret
endp
@ -1134,7 +1205,7 @@ end virtual
jz .nothing
; 2. Initialize controller-independent parts of both TDs.
push eax
call usb_init_transfer
invoke usbhc_api.usb_init_transfer
pop eax
; 3. Copy PID to the new descriptor.
mov edx, [ecx+ehci_gtd.Token-sizeof.ehci_gtd]
@ -1145,7 +1216,7 @@ end virtual
push eax
; 5. Store the physical address of the next descriptor.
sub eax, sizeof.ehci_gtd
call get_phys_addr
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.
@ -1157,7 +1228,7 @@ end repeat
cmp [.packetSize], eax
jz @f
mov eax, [.buffer]
call get_phys_addr
call [GetPhysAddr]
mov [ecx+ehci_gtd.BufferPointers-sizeof.ehci_gtd], eax
and eax, 0xFFF
mov edx, [.packetSize]
@ -1166,25 +1237,25 @@ end repeat
jbe @f
mov eax, [.buffer]
add eax, 0x1000
call get_pg_addr
call [GetPgAddr]
mov [ecx+ehci_gtd.BufferPointers+4-sizeof.ehci_gtd], eax
sub edx, 0x1000
jbe @f
mov eax, [.buffer]
add eax, 0x2000
call get_pg_addr
call [GetPgAddr]
mov [ecx+ehci_gtd.BufferPointers+8-sizeof.ehci_gtd], eax
sub edx, 0x1000
jbe @f
mov eax, [.buffer]
add eax, 0x3000
call get_pg_addr
call [GetPgAddr]
mov [ecx+ehci_gtd.BufferPointers+12-sizeof.ehci_gtd], eax
sub edx, 0x1000
jbe @f
mov eax, [.buffer]
add eax, 0x4000
call get_pg_addr
call [GetPgAddr]
mov [ecx+ehci_gtd.BufferPointers+16-sizeof.ehci_gtd], eax
@@:
; 7. Fill Token field:
@ -1242,10 +1313,10 @@ endp
proc ehci_port_reset_done
movzx ecx, [esi+usb_controller.ResettingPort]
and dword [edi+EhciPortsReg+ecx*4], not 12Ah
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+usb_controller.ResetTime], eax
mov [esi+usb_controller.ResettingStatus], 2
DEBUGF 1,'K : [%d] EHCI %x: reset port %d done\n',[timer_ticks],esi,ecx
; DEBUGF 1,'K : EHCI %x: reset port %d done\n',esi,ecx
ret
endp
@ -1258,21 +1329,23 @@ proc ehci_port_init
xor eax, eax
xchg al, [esi+usb_controller.ResettingStatus]
test al, al
js usb_test_pending_port
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 : [%d] EHCI %x status of port %d is %x\n',[timer_ticks],esi,ecx,eax
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 usb_test_pending_port
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.
@ -1284,7 +1357,7 @@ proc ehci_port_init
; (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 usb_test_pending_port
jmp [usbhc_api.usb_test_pending_port]
.nothing:
ret
endp
@ -1307,20 +1380,10 @@ proc ehci_new_device
; 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]
; If the parent hub is high-speed, it is TT for the device.
; Otherwise, the parent hub itself is behind TT, and the device
; has the same TT hub+port as the parent hub.
push eax
mov eax, [edx+usb_hub.ConfigPipe]
mov eax, [eax+usb_pipe.DeviceData]
cmp [eax+usb_device_data.Speed], USB_SPEED_HS
jz @f
movzx ecx, [eax+usb_device_data.TTPort]
mov edx, [eax+usb_device_data.TTHub]
@@:
mov edx, [edx+usb_hub.ConfigPipe]
invoke usbhc_api.usb_get_tt
inc ecx
mov edx, [edx+ehci_pipe.Token-sizeof.ehci_pipe]
shl ecx, 23
@ -1338,7 +1401,7 @@ proc ehci_new_device
push edx ; ehci_pipe.Flags
push eax ; ehci_pipe.Token
; 6. Notify the protocol layer.
call usb_new_device
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
@ -1378,7 +1441,7 @@ proc ehci_process_deferred
mov [edi+EhciPortsReg+ecx*4], eax
mov ebx, eax
mov eax, [edi+EhciPortsReg+ecx*4]
DEBUGF 1,'K : [%d] EHCI %x: status of port %d changed to %x\n',[timer_ticks],esi,ecx,ebx
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
@ -1411,7 +1474,7 @@ proc ehci_process_deferred
; USB_CONNECT_DELAY ticks.
test al, 1
jz .disconnect
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+usb_controller.ConnectedTime+ecx*4], eax
bts [esi+usb_controller.NewConnected], ecx
jmp .nextport
@ -1435,7 +1498,7 @@ proc ehci_process_deferred
.skip_roothub:
; 4. Process disconnect events. This should be done after step 3
; (which includes the first stage of disconnect processing).
call usb_disconnect_stage2
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.
@ -1447,7 +1510,7 @@ proc ehci_process_deferred
.portloop2:
bt [esi+usb_controller.NewConnected], ecx
jnc .noconnect
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+usb_controller.ConnectedTime+ecx*4]
sub eax, USB_CONNECT_DELAY
jge .connected
@ -1488,7 +1551,7 @@ proc ehci_process_deferred
; Satisfy a request when InterruptOnAsyncAdvance fired.
test byte [esp+4], 20h
jz @f
dbgstr 'async advance int'
; dbgstr 'async advance int'
mov eax, [esi+usb_controller.WaitPipeRequestAsync]
mov [esi+usb_controller.ReadyPipeHeadAsync], eax
@@:
@ -1531,7 +1594,7 @@ proc ehci_process_deferred
cmp [esi+usb_controller.ResettingStatus], 1
jnz .no_reset_in_progress
; 8b. Yep. Test whether it should be stopped.
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+usb_controller.ResetTime]
sub eax, USB_RESET_TIME
jge .reset_done
@ -1549,7 +1612,7 @@ proc ehci_process_deferred
cmp [esi+usb_controller.ResettingStatus], 0
jz .skip_reset
; 8f. Yep. Test whether it should be stopped.
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+usb_controller.ResetTime]
sub eax, USB_RESET_RECOVERY_TIME
jge .reset_recovery_done
@ -1568,7 +1631,7 @@ proc ehci_process_deferred
; 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.
call usb_process_wait_lists
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).
@ -1577,7 +1640,7 @@ proc ehci_process_deferred
mov edx, [esi+usb_controller.WaitPipeListAsync]
mov [esi+usb_controller.WaitPipeRequestAsync], edx
or dword [edi+EhciCommandReg], 1 shl 6
dbgstr 'async advance doorbell'
; dbgstr 'async advance doorbell'
cmp dword [esp], 100
jb @f
mov dword [esp], 100
@ -1649,7 +1712,7 @@ proc ehci_process_updated_list
; 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]
call mutex_lock
invoke MutexLock
mov ebx, [ebx+usb_pipe.LastTD]
push ebx
mov ebx, [ebx+usb_gtd.NextVirt]
@ -1663,13 +1726,13 @@ proc ehci_process_updated_list
; Release the queue lock while processing one descriptor:
; callback function could (and often would) schedule another transfer.
push ecx
call mutex_unlock
invoke MutexUnlock
call ehci_process_updated_td
pop ecx
call mutex_lock
invoke MutexLock
jmp .tdloop
.tddone:
call mutex_unlock
invoke MutexUnlock
pop ebx
; End of internal loop, restore pointer to the next pipe
; and continue the external loop.
@ -1695,7 +1758,7 @@ proc ehci_process_updated_td
; 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.
call usb_unlink_td
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]
@ -1723,21 +1786,7 @@ proc ehci_process_updated_td
; 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.
; 4a. Test whether it is the last descriptor in the transfer
; <=> it has an associated callback.
mov eax, [ebx+usb_gtd.Callback]
test eax, eax
jz .nocallback
; 4b. It has an associated callback; call it with corresponding parameters.
stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData]
jmp .callback
.nocallback:
; 4c. It is an intermediate descriptor. Add its length to the length
; in the following descriptor.
mov eax, [ebx+usb_gtd.NextVirt]
add [eax+usb_gtd.Length], edx
.callback:
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
@ -1765,12 +1814,12 @@ proc ehci_process_updated_td
; for this transfer. Free and unlink non-final descriptors.
; Final descriptor will be freed in step 5.
.look_final:
call usb_is_final_packet
invoke usbhc_api.usb_is_final_packet
jnc .found_final
push [ebx+usb_gtd.NextVirt]
stdcall ehci_free_td, ebx
pop ebx
call usb_unlink_td
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.
@ -1822,7 +1871,7 @@ proc ehci_process_updated_td
push [ebx+usb_gtd.NextVirt]
stdcall ehci_free_td, ebx
pop ebx
call usb_unlink_td
invoke usbhc_api.usb_unlink_td
pop ecx
.normal:
; 6f. For bulk/interrupt transfers we have no choice but halt the queue,
@ -1882,7 +1931,7 @@ endp
proc ehci_alloc_td
push ebx
mov ebx, ehci_gtd_mutex
stdcall usb_allocate_common, (sizeof.ehci_gtd + sizeof.usb_gtd + 1Fh) and not 1Fh
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
@ -1896,5 +1945,15 @@ endp
; EHCI has no additional data, so just free ehci_gtd structure.
proc ehci_free_td
sub dword [esp+4], sizeof.ehci_gtd
jmp usb_free_common
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

View File

@ -2,233 +2,6 @@
; 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).
; sanity check: structures in UHCI and OHCI should be the same
if (sizeof.ohci_static_ep=sizeof.uhci_static_ep)&(ohci_static_ep.SoftwarePart=uhci_static_ep.SoftwarePart)&(ohci_static_ep.NextList=uhci_static_ep.NextList)
; 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.ohci_static_ep
push eax
imul ebx, ecx, sizeof.ohci_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+ohci_static_ep.SoftwarePart+usb_static_ep.Bandwidth]
mov edx, [edx+ohci_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.ohci_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, ohci_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
; sanity check, part 2
else
.err select_interrupt_list must be different for UHCI and OHCI
end if
; 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
; USB2 scheduler.
; There are two parts: high-speed pipes and split-transaction pipes.
;
@ -830,6 +603,9 @@ virtual at ebp-12-.local_vars_size
.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 ?
@ -992,9 +768,7 @@ endp
; Similar to calc_usb1_bandwidth with corresponding changes.
; eax -> usb_hub with TT, ebx -> usb_pipe
proc tt_calc_budget
movzx ecx, [eax+usb_hub.HubCharacteristics]
shr ecx, 5
and ecx, 3 ; 1+ecx = TT think time in FS-bytes
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
@ -1005,7 +779,7 @@ proc tt_calc_budget
; 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]
lea eax, [eax+11+ecx]
; 1 byte is minimal TT think time in addition to ecx.
ret
.low_speed:
@ -1014,7 +788,7 @@ proc tt_calc_budget
; 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+1]
lea eax, [eax*8+90+ecx]
ret
endp

View File

@ -1,7 +1,17 @@
; Code for OHCI controllers.
; Note: it should be moved to an external driver,
; it was convenient to have this code compiled into the kernel during initial
; development, but there are no reasons to keep it here.
; 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 =================================
@ -183,6 +193,8 @@ 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
@ -290,8 +302,10 @@ ends
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
@ -299,21 +313,50 @@ ohci_hardware_func:
dd ohci_port_disable
dd ohci_new_port.reset
dd ohci_set_endpoint_packet_size
dd usb1_allocate_endpoint
dd usb1_free_endpoint
dd ohci_alloc_pipe
dd ohci_free_pipe
dd ohci_init_pipe
dd ohci_unlink_pipe
dd usb1_allocate_general_td
dd usb1_free_general_td
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.
@ -338,7 +381,7 @@ if ohci_controller.IntEDs >= 0x1000
.err assertion failed
end if
lea esi, [eax+ohci_controller.IntEDs+32*sizeof.ohci_static_ep]
call get_pg_addr
invoke GetPgAddr
add eax, ohci_controller.IntEDs
movi ecx, 32
mov edx, ecx
@ -383,23 +426,14 @@ end if
call ohci_init_static_endpoint
; 4. Create a virtual memory area to talk with the controller.
; 4a. Enable memory & bus master access.
mov ch, [.bus]
mov cl, 0
mov eax, ecx
mov bh, [.devfn]
mov bl, 4
call pci_read_reg
invoke PciRead16, dword [.bus], dword [.devfn], 4
or al, 6
xchg eax, ecx
call pci_write_reg
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax
; 4b. Read memory base address.
mov ah, [.bus]
mov al, 2
mov bl, 10h
call pci_read_reg
invoke PciRead32, dword [.bus], dword [.devfn], 10h
and al, not 0Fh
; 4c. Create mapping for physical memory. 256 bytes are sufficient.
stdcall map_io_mem, eax, 100h, PG_SW+PG_NOCACHE
invoke MapIoMem, eax, 100h, PG_SW+PG_NOCACHE
test eax, eax
jz .fail
stosd ; fill ohci_controller.MMIOBase
@ -422,7 +456,7 @@ end if
mov [edi+OhciCommandStatusReg], ecx
@@:
mov esi, ecx
call delay_ms
invoke Sleep
test [edi+OhciCommandStatusReg], ecx
jz .resetdone
dec edx
@ -454,7 +488,7 @@ end if
pop esi ; restore pointer to ohci_controller saved in step 1
; 6a. Physical address of HCCA.
mov eax, esi
call get_pg_addr
invoke GetPgAddr
mov [edi+OhciHCCAReg], eax
; 6b. Transition to operational state and clear all Enable bits.
mov cl, 2 shl 6
@ -475,28 +509,29 @@ end if
; mov [edi+OhciDoneHeadReg], eax
; 6e. Enable processing of all lists with control:bulk ratio = 1:1.
mov dword [edi+OhciControlReg], 10111100b
; 7. Get number of ports.
; 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
; 8. Initialize DoneListEndPtr to point to DoneList.
; 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
; 9. Hook interrupt.
mov ah, [.bus]
mov al, 0
mov bh, [.devfn]
mov bl, 3Ch
call pci_read_reg
; 10. Hook interrupt.
invoke PciRead8, dword [.bus], dword [.devfn], 3Ch
; al = IRQ
movzx eax, al
stdcall attach_int_handler, eax, ohci_irq, esi
; 10. Enable controller interrupt on HcDoneHead writeback and RootHubStatusChange.
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]
; 11. Initialize ports of the controller.
; 11a. Initiate power up, disable all ports, clear all "changed" bits.
; 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
@@:
@ -504,20 +539,20 @@ end if
inc ecx
cmp ecx, [esi+usb_controller.NumPorts]
jb @b
; 11b. Wait for power up.
; 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
call delay_ms
invoke Sleep
@@:
pop esi
; 11c. Ports are powered up; now it is ok to process connect/disconnect events.
; 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
; 11d. We could miss some events while waiting for powering up;
; 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:
@ -527,23 +562,23 @@ end if
; 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.
mov eax, [timer_ticks]
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
; 12. Return pointer to usb_controller.
; 13. Return pointer to usb_controller.
xchg eax, esi
ret
.fail_unmap:
; On error after step 4, release the virtual memory area.
stdcall free_kernel_space, edi
; 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 8,
; Note also that there can be no errors after step 6,
; where that pointer is popped from the stack.
pop ecx
.nothing:
@ -561,7 +596,7 @@ proc ohci_init_static_endpoint
mov [edi+ohci_static_ep.NextED], eax
mov [edi+ohci_static_ep.NextList], esi
add edi, ohci_static_ep.SoftwarePart
call usb_init_static_endpoint
invoke usbhc_api.usb_init_static_endpoint
add edi, sizeof.ohci_static_ep - ohci_static_ep.SoftwarePart
ret
endp
@ -591,14 +626,10 @@ endp
; deal with USB devices.
proc ohci_kickoff_bios
; 1. Get the physical address of MMIO registers.
mov ah, [esi+PCIDEV.bus]
mov bh, [esi+PCIDEV.devfn]
mov al, 2
mov bl, 10h
call pci_read_reg
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.
stdcall map_io_mem, eax, 100h, PG_SW+PG_NOCACHE
invoke MapIoMem, eax, 100h, PG_SW+PG_NOCACHE
test eax, eax
jz .nothing
; 3. Some BIOSes enable controller interrupts as a result of giving
@ -625,7 +656,7 @@ proc ohci_kickoff_bios
jz .has_ownership
push esi
movi esi, 1
call delay_ms
invoke Sleep
pop esi
loop @b
dbgstr 'warning: taking OHCI ownership from BIOS timeout'
@ -636,7 +667,7 @@ proc ohci_kickoff_bios
; 6. Now we can unblock interrupts in the processor.
popf
; 7. Release memory mapping created in step 2 and return.
stdcall free_kernel_space, eax
invoke FreeKernelSpace, eax
.nothing:
ret
endp
@ -703,7 +734,8 @@ end virtual
xor ebx, ebx
test ecx, ecx
jz .tddone
call usb_td_to_virt
mov eax, [ohci_gtd_first_page]
invoke usbhc_api.usb_td_to_virt
test eax, eax
jz .tddone
lea edx, [eax+ohci_gtd.NextTD]
@ -718,7 +750,8 @@ end virtual
lea ebx, [eax+sizeof.ohci_gtd]
test ecx, ecx
jz .tddone
call usb_td_to_virt
mov eax, [ohci_gtd_first_page]
invoke usbhc_api.usb_td_to_virt
test eax, eax
jnz .tdloop
.tddone:
@ -845,7 +878,7 @@ end virtual
jz .disconnect
; Note: ConnectedTime must be stored before setting the 'connected' bit,
; otherwise ohci_process_deferred could use an old time.
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+usb_controller.ConnectedTime+ecx*4], eax
lock bts [esi+usb_controller.NewConnected], ecx
jmp .nextport
@ -858,8 +891,8 @@ end virtual
jz .nextport
test al, 10h
jnz .nextport
mov edx, [timer_ticks]
mov [esi+usb_controller.ResetTime], edx
invoke GetTimerTicks
mov [esi+usb_controller.ResetTime], eax
mov [esi+usb_controller.ResettingStatus], 2
inc ebx
.nextport:
@ -871,7 +904,7 @@ end virtual
; 12. Restore the stack after step 6.
pop eax
; 13. Notify the USB thread if some deferred processing is required.
call usb_wakeup_if_needed
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
@ -884,8 +917,7 @@ endp
proc ohci_set_device_address
mov byte [ebx+ohci_pipe.Flags-sizeof.ohci_pipe], cl
; Wait until the hardware will forget the old value.
call usb_subscribe_control
ret
jmp [usbhc_api.usb_subscribe_control]
endp
; This procedure returns USB device address from the usb_pipe structure.
@ -914,8 +946,7 @@ endp
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.
call usb_subscribe_control
ret
jmp [usbhc_api.usb_subscribe_control]
endp
; This procedure is called from API usb_open_pipe and processes
@ -938,7 +969,7 @@ virtual at ebp-12
end virtual
; 1. Initialize the queue of transfer descriptors: empty.
sub eax, sizeof.ohci_gtd
call get_phys_addr
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.
@ -991,7 +1022,7 @@ end virtual
mov ecx, [edx+ohci_pipe.NextED-sizeof.ohci_pipe]
mov [edi+ohci_pipe.NextED-sizeof.ohci_pipe], ecx
lea eax, [edi-sizeof.ohci_pipe]
call get_phys_addr
invoke GetPhysAddr
mov [edx+ohci_pipe.NextED-sizeof.ohci_pipe], eax
; 4. Return something non-zero.
ret
@ -1114,7 +1145,7 @@ endl
.fail:
mov edi, ohci_hardware_func
mov eax, [td]
stdcall usb_undo_tds, [origTD]
invoke usbhc_api.usb_undo_tds, [origTD]
xor eax, eax
ret
endp
@ -1137,18 +1168,18 @@ virtual at ebp-8
.direction dd ?
end virtual
; 1. Allocate the next TD.
call usb1_allocate_general_td
call ohci_alloc_gtd
test eax, eax
jz .nothing
; 2. Initialize controller-independent parts of both TDs.
push eax
call usb_init_transfer
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
call get_phys_addr
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.
@ -1158,12 +1189,12 @@ end virtual
cmp [.packetSize], eax
jz @f
mov eax, [.buffer]
call get_phys_addr
invoke GetPhysAddr
mov [ecx+ohci_gtd.CurBufPtr-sizeof.ohci_gtd], eax
mov eax, [.buffer]
add eax, [.packetSize]
dec eax
call get_phys_addr
invoke GetPhysAddr
mov [ecx+ohci_gtd.BufEnd-sizeof.ohci_gtd], eax
@@:
; 6. Generate Flags field:
@ -1225,7 +1256,9 @@ proc ohci_port_after_reset
xor eax, eax
xchg al, [esi+usb_controller.ResettingStatus]
test al, al
js usb_test_pending_port
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]
@ -1233,13 +1266,13 @@ proc ohci_port_after_reset
test al, 2
jnz @f
DEBUGF 1,'K : USB port disabled after reset, status=%x\n',eax
jmp usb_test_pending_port
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 [%d] status of port %d is %x\n',[timer_ticks],ecx,eax
DEBUGF 1,'K : port_after_reset, status of port %d is %x\n',ecx,eax
shr eax, 9
call ohci_new_device
pop ecx
@ -1249,7 +1282,7 @@ proc ohci_port_after_reset
jnz .nothing
.disable_exit:
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1
jmp usb_test_pending_port
jmp [usbhc_api.usb_test_pending_port]
.nothing:
ret
endp
@ -1272,7 +1305,7 @@ proc ohci_new_device
sub esp, 12 ; ignored fields
push eax ; .Flags
; 4. Notify the protocol layer.
call usb_new_device
invoke usbhc_api.usb_new_device
; 5. Cleanup the stack after step 3 and return.
add esp, 20
ret
@ -1287,7 +1320,7 @@ proc ohci_process_deferred
; 1. Initialize the return value.
push -1
; 2. Process disconnect events.
call usb_disconnect_stage2
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.
@ -1299,7 +1332,15 @@ proc ohci_process_deferred
.portloop:
bt [esi+usb_controller.NewConnected], ecx
jnc .noconnect
mov eax, [timer_ticks]
; 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
@ -1321,7 +1362,7 @@ proc ohci_process_deferred
; 4. Check for end of reset signalling. If so, call ohci_port_after_reset.
cmp [esi+usb_controller.ResettingStatus], 2
jnz .no_reset_recovery
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+usb_controller.ResetTime]
sub eax, USB_RESET_RECOVERY_TIME
jge .reset_done
@ -1353,7 +1394,7 @@ proc ohci_process_deferred
; 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.
call usb_process_wait_lists
invoke usbhc_api.usb_process_wait_lists
; 6b. If no new requests, skip the rest of this step.
test eax, eax
jz @f
@ -1402,7 +1443,7 @@ proc ohci_process_finalized_td
jmp .next_td2
@@:
; 2. Remove the descriptor from the descriptors queue.
call usb_unlink_td
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
@ -1429,30 +1470,19 @@ proc ohci_process_finalized_td
neg edx
; 4. Check for error. If so, go to 7.
push ebx
mov eax, [ebx+ohci_gtd.Flags-sizeof.ohci_gtd]
shr eax, 28
mov ecx, [ebx+ohci_gtd.Flags-sizeof.ohci_gtd]
shr ecx, 28
jnz .error
.notify:
; 5. Successful completion.
; 5a. Check whether this descriptor has an associated callback.
mov ecx, [ebx+usb_gtd.Callback]
test ecx, ecx
jz .ok_nocallback
; 5b. If so, call the callback.
stdcall_verify ecx, [ebx+usb_gtd.Pipe], eax, \
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData]
jmp .next_td
.ok_nocallback:
; 5c. Otherwise, add length of the current descriptor to the next descriptor.
mov eax, [ebx+usb_gtd.NextVirt]
add [eax+usb_gtd.Length], edx
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 usb1_free_general_td, ebx
stdcall ohci_free_gtd, ebx
@@:
pop ebx
lea eax, [ebx+ohci_gtd.NextTD-sizeof.ohci_gtd]
@ -1477,7 +1507,7 @@ proc ohci_process_finalized_td
; Free the current item, set ebx to the next item, continue to 5a.
test ebx, ebx
jz @f
stdcall usb1_free_general_td, ebx
stdcall ohci_free_gtd, ebx
@@:
pop ebx
ret
@ -1485,7 +1515,7 @@ proc ohci_process_finalized_td
; 7. There was an error while processing this descriptor.
; The hardware has stopped processing the queue.
; 7a. Save status and length.
push eax
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]
@ -1496,7 +1526,7 @@ proc ohci_process_finalized_td
; for this transfer.
; Free and unlink non-final descriptors, except the current one.
; Final descriptor will be freed in step 6.
call usb_is_final_packet
invoke usbhc_api.usb_is_final_packet
jnc .found_final
mov ebx, [ebx+usb_gtd.NextVirt]
virtual at esp
@ -1505,11 +1535,11 @@ virtual at esp
.current_item dd ?
end virtual
.look_final:
call usb_unlink_td
call usb_is_final_packet
invoke usbhc_api.usb_unlink_td
invoke usbhc_api.usb_is_final_packet
jnc .found_final
push [ebx+usb_gtd.NextVirt]
stdcall usb1_free_general_td, ebx
stdcall ohci_free_gtd, ebx
pop ebx
jmp .look_final
.found_final:
@ -1530,14 +1560,14 @@ end virtual
.advance_queue:
mov eax, [ebx+usb_gtd.NextVirt]
sub eax, sizeof.ohci_gtd
call get_phys_addr
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 eax
pop edx ecx
jmp .notify
; 7d. Abort the entire transfer.
; There are two cases: either there is only one transfer stage
@ -1553,10 +1583,10 @@ end virtual
cmp ebx, [.current_item]
push [ebx+usb_gtd.NextVirt]
jz @f
stdcall usb1_free_general_td, ebx
stdcall ohci_free_gtd, ebx
@@:
pop ebx
call usb_unlink_td
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).
@ -1597,3 +1627,60 @@ proc ohci_unlink_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

View File

@ -1,7 +1,17 @@
; Code for UHCI controllers.
; Note: it should be moved to an external driver,
; it was convenient to have this code compiled into the kernel during initial
; development, but there are no reasons to keep it here.
; 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 =================================
@ -145,6 +155,8 @@ DeferredActions dd ?
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
@ -242,8 +254,10 @@ ends
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
@ -251,21 +265,50 @@ uhci_hardware_func:
dd uhci_port_disable
dd uhci_new_port.reset
dd uhci_set_endpoint_packet_size
dd usb1_allocate_endpoint
dd uhci_alloc_pipe
dd uhci_free_pipe
dd uhci_init_pipe
dd uhci_unlink_pipe
dd usb1_allocate_general_td
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.
@ -299,7 +342,7 @@ if (uhci_controller.IntEDs mod 0x1000) <> 0
.err assertion failed
end if
add eax, uhci_controller.IntEDs
call get_phys_addr
invoke GetPhysAddr
; 2b. Fill first 32 entries.
inc eax
inc eax ; set QH bit for uhci_pipe.NextQH
@ -349,35 +392,20 @@ end if
call uhci_init_static_endpoint
; 4. Get I/O base address and size from PCI bus.
; 4a. Read&save PCI command state.
mov bh, [.devfn]
mov ch, [.bus]
mov cl, 1
mov eax, ecx
mov bl, 4
call pci_read_reg
invoke PciRead16, dword [.bus], dword [.devfn], 4
push eax
; 4b. Disable IO access.
and al, not 1
push ecx
xchg eax, ecx
call pci_write_reg
pop ecx
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax
; 4c. Read&save IO base address.
mov eax, ecx
mov bl, 20h
call pci_read_reg
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.
push ecx
xchg eax, ecx
or ecx, -1
call pci_write_reg
pop ecx
invoke PciWrite16, dword [.bus], dword [.devfn], 20h, -1
; 4e. Read IO base address.
mov eax, ecx
call pci_read_reg
invoke PciRead16, dword [.bus], dword [.devfn], 20h
and al, not 3
cwde
not eax
@ -385,18 +413,11 @@ end if
xchg eax, esi
; now esi = IO size
; 4f. Restore IO base address.
xchg eax, ecx
mov ecx, edi
push eax
call pci_write_reg
pop eax
invoke PciWrite16, dword [.bus], dword [.devfn], 20h, edi
; 4g. Restore PCI command state and enable io & bus master access.
pop ecx
or ecx, 5
mov bl, 4
push eax
call pci_write_reg
pop eax
invoke PciWrite16, dword [.bus], dword [.devfn], 4, ecx
; 5. Reset the controller.
; 5e. Host reset.
mov edx, edi
@ -407,7 +428,7 @@ end if
@@:
push esi
movi esi, 1
call delay_ms
invoke Sleep
pop esi
in ax, dx
test al, 2
@ -421,7 +442,7 @@ if 0
; wait 10 ms
push esi
movi esi, 10
call delay_ms
invoke Sleep
pop esi
; clear reset signal
xor eax, eax
@ -458,56 +479,58 @@ end if
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
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax
; 8. Hook interrupt.
mov ah, [.bus]
mov al, 0
mov bh, [.devfn]
mov bl, 3Ch
call pci_read_reg
; 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
stdcall attach_int_handler, eax, uhci_irq, esi
; 9. Setup controller registers.
invoke AttachIntHandler, eax, uhci_irq, esi
; 10. Setup controller registers.
xor eax, eax
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller]
; 9a. UhciStatusReg := 3Fh: clear all status bits
; 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
; 9b. UhciInterruptReg := 0Dh.
; 10b. UhciInterruptReg := 0Dh.
inc edx
inc edx ; UhciInterruptReg == 4
mov al, 0Dh
out dx, ax
; 9c. UhciFrameNumberReg := 0.
; 10c. UhciFrameNumberReg := 0.
inc edx
inc edx ; UhciFrameNumberReg == 6
mov al, 0
out dx, ax
; 9d. UhciBaseAddressReg := physical address of uhci_controller.
; 10d. UhciBaseAddressReg := physical address of uhci_controller.
inc edx
inc edx ; UhciBaseAddressReg == 8
lea eax, [esi-sizeof.uhci_controller]
call get_phys_addr
invoke GetPhysAddr
out dx, eax
; 9e. UhciCommandReg := Run + Configured + (MaxPacket is 64 bytes)
; 10e. UhciCommandReg := Run + Configured + (MaxPacket is 64 bytes)
sub edx, UhciBaseAddressReg ; UhciCommandReg == 0
mov ax, 0C1h ; Run, Configured, MaxPacket = 64b
out dx, ax
; 10. Do initial scan of existing devices.
; 11. Do initial scan of existing devices.
call uhci_poll_roothub
; 11. Return pointer to usb_controller.
; 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 7 and never fails
; after step 7.
; Note that the main code branch restores the stack at step 8 and never fails
; after step 8.
pop ecx
xor eax, eax
ret
@ -518,11 +541,7 @@ endp
; so do it in inpolite way, preventing controller from any SMI activity.
proc uhci_kickoff_bios
; 1. Get the I/O address.
mov ah, [esi+PCIDEV.bus]
mov al, 1
mov bh, [esi+PCIDEV.devfn]
mov bl, 20h
call pci_read_reg
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.
@ -534,11 +553,7 @@ proc uhci_kickoff_bios
out dx, ax
; 3. Disable all bits for SMI routing, clear SMI routing status,
; enable master interrupt bit.
mov ah, [esi+PCIDEV.bus]
mov al, 1
mov bl, 0xC0
mov ecx, 0AF00h
call pci_write_reg
invoke PciWrite16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 0xC0, 0AF00h
ret
endp
@ -552,7 +567,7 @@ proc uhci_init_static_endpoint
mov byte [edi+uhci_static_ep.HeadTD], 1
mov [edi+uhci_static_ep.NextList], esi
add edi, uhci_static_ep.SoftwarePart
call usb_init_static_endpoint
invoke usbhc_api.usb_init_static_endpoint
add edi, sizeof.uhci_static_ep - uhci_static_ep.SoftwarePart
ret
endp
@ -612,7 +627,7 @@ end virtual
push ebx
xor ebx, ebx
inc ebx
call usb_wakeup_if_needed
invoke usbhc_api.usb_wakeup_if_needed
pop ebx
; 6. This is our interrupt; return 1.
mov al, 1
@ -633,12 +648,12 @@ proc uhci_process_deferred
; the error can be caused by disconnect, try to detect it.
test byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], 2
jnz .force_poll
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+uhci_controller.LastPollTime-sizeof.uhci_controller]
sub eax, UHCI_POLL_INTERVAL
jl .nopoll
.force_poll:
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax
call uhci_poll_roothub
mov eax, -UHCI_POLL_INTERVAL
@ -675,7 +690,7 @@ proc uhci_process_deferred
@@:
; 4. Process disconnect events. This should be done after step 2
; (which includes the first stage of disconnect processing).
call usb_disconnect_stage2
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
@ -684,7 +699,15 @@ proc uhci_process_deferred
.portloop:
bt [esi+usb_controller.NewConnected], ecx
jnc .noconnect
mov eax, [timer_ticks]
; 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
@ -721,7 +744,7 @@ proc uhci_process_deferred
cmp [esi+usb_controller.ResettingStatus], 1
jnz .no_reset_in_progress
; 7b. Yep. Test whether it should be stopped.
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+usb_controller.ResetTime]
sub eax, USB_RESET_TIME
jge .reset_done
@ -739,7 +762,7 @@ proc uhci_process_deferred
cmp [esi+usb_controller.ResettingStatus], 0
jz .skip_reset
; 7f. Yep. Test whether it should be stopped.
mov eax, [timer_ticks]
invoke GetTimerTicks
sub eax, [esi+usb_controller.ResetTime]
sub eax, USB_RESET_RECOVERY_TIME
jge .reset_recovery_done
@ -758,7 +781,7 @@ proc uhci_process_deferred
; 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.
call usb_process_wait_lists
invoke usbhc_api.usb_process_wait_lists
; 8b. If no new requests, skip the rest of this step.
test eax, eax
jz @f
@ -832,7 +855,7 @@ proc uhci_process_updated_list
; 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]
call mutex_lock
invoke MutexLock
mov ebx, [ebx+usb_pipe.LastTD]
push ebx
mov ebx, [ebx+usb_gtd.NextVirt]
@ -847,13 +870,13 @@ proc uhci_process_updated_list
; Release the queue lock while processing one descriptor:
; callback function could (and often would) schedule another transfer.
push ecx
call mutex_unlock
invoke MutexUnlock
call uhci_process_finalized_td
pop ecx
call mutex_lock
invoke MutexLock
jmp .tdloop
.tddone:
call mutex_unlock
invoke MutexUnlock
pop ebx
; End of internal loop, restore pointer to the next pipe
; and continue the external loop.
@ -871,7 +894,7 @@ endp
; 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.
call usb_unlink_td
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]
@ -958,12 +981,12 @@ end if
; for this transfer.
; Free and unlink non-final descriptors, except the current one.
; Final descriptor will be freed in step 7.
call usb_is_final_packet
invoke usbhc_api.usb_is_final_packet
jnc .found_final
mov ebx, [ebx+usb_gtd.NextVirt]
.look_final:
call usb_unlink_td
call usb_is_final_packet
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
@ -1040,7 +1063,7 @@ end if
stdcall uhci_free_td, ebx
@@:
pop ebx
call usb_unlink_td
invoke usbhc_api.usb_unlink_td
pop ecx
.normal:
; 5g. For bulk/interrupt transfers we have no choice but halt the queue,
@ -1062,21 +1085,7 @@ end if
; 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.
; 6a. Test whether it is the last packet in the transfer
; <=> it has an associated callback.
mov eax, [ebx+usb_gtd.Callback]
test eax, eax
jz .nocallback
; 6b. It has an associated callback; call it with corresponding parameters.
stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData]
jmp .callback
.nocallback:
; 6c. It is an intermediate packet. Add its length to the length
; in the following packet.
mov eax, [ebx+usb_gtd.NextVirt]
add [eax+usb_gtd.Length], edx
.callback:
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]
@ -1113,7 +1122,7 @@ proc uhci_fix_toggle
jz .nothing
; 3. Lock the transfer queue.
add ecx, usb_pipe.Lock
call mutex_lock
invoke MutexLock
; 4. Flip the toggle bit in all packets from ebx.NextVirt to ecx.LastTD
; (inclusive).
mov eax, [ebx+usb_gtd.NextVirt]
@ -1126,7 +1135,7 @@ proc uhci_fix_toggle
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.
call mutex_unlock
invoke MutexUnlock
.nothing:
ret
endp
@ -1164,14 +1173,14 @@ proc uhci_poll_roothub
; Some controllers set enable status change bit, some don't.
test bl, 2
jz .noconnectchange
DEBUGF 1,'K : [%d] UHCI %x connect status changed, %x/%x\n',[timer_ticks],esi,bx,ax
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
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+usb_controller.ConnectedTime+ecx*4], eax
bts [esi+usb_controller.NewConnected], ecx
jmp .nextport
@ -1222,7 +1231,7 @@ proc uhci_new_port
or ah, 2
out dx, ax
; 3. Store the current time and set status to 1 = reset signalling active.
mov eax, [timer_ticks]
invoke GetTimerTicks
mov [esi+usb_controller.ResetTime], eax
mov [esi+usb_controller.ResettingStatus], 1
.nothing:
@ -1237,14 +1246,14 @@ proc uhci_port_reset_done
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller]
lea edx, [edx+ecx*2+UhciPort1StatusReg]
in ax, dx
DEBUGF 1,'K : [%d] UHCI %x status %x/',[timer_ticks],esi,ax
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
call delay_ms
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.
@ -1254,8 +1263,8 @@ proc uhci_port_reset_done
or al, 6 ; enable port, clear status change
out dx, ax
; 4. Store the current time and set status to 2 = reset recovery active.
mov eax, [timer_ticks]
DEBUGF 1,'K : reset done at %d\n',[timer_ticks]
invoke GetTimerTicks
DEBUGF 1,'K : reset done\n'
mov [esi+usb_controller.ResetTime], eax
mov [esi+usb_controller.ResettingStatus], 2
ret
@ -1272,12 +1281,12 @@ proc uhci_port_init
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller]
lea edx, [edx+ecx*2+UhciPort1StatusReg]
in ax, dx
DEBUGF 1,'K : [%d] UHCI %x status %x\n',[timer_ticks],esi,ax
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 usb_test_pending_port
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.
@ -1293,7 +1302,7 @@ proc uhci_port_init
in ax, dx
and al, not 4
out dx, ax ; disable the port
jmp usb_test_pending_port
jmp [usbhc_api.usb_test_pending_port]
.nothing:
ret
endp
@ -1316,7 +1325,7 @@ proc uhci_new_device
push eax ; ignored (ErrorTD)
push eax ; .Token field: DeviceAddress is zero, bit 20 = LowSpeedDevice
; 4. Notify the protocol layer.
call usb_new_device
invoke usbhc_api.usb_new_device
; 5. Cleanup the stack after step 3 and return.
add esp, 12
ret
@ -1327,8 +1336,7 @@ endp
; 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
call usb_subscription_done
ret
jmp [usbhc_api.usb_subscription_done]
endp
; This procedure returns USB device address from the uhci_pipe structure.
@ -1364,8 +1372,7 @@ proc uhci_set_endpoint_packet_size
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.
call usb_subscription_done
ret
jmp [usbhc_api.usb_subscription_done]
endp
; This procedure is called from API usb_open_pipe and processes
@ -1392,7 +1399,7 @@ end virtual
; 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
call get_phys_addr
invoke GetPhysAddr
mov [edi+uhci_pipe.HeadTD-sizeof.uhci_pipe], eax
; 3. Initialize Token field:
; take DeviceAddress and LowSpeedDevice from the parent pipe,
@ -1470,7 +1477,7 @@ end virtual
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]
call get_phys_addr
invoke GetPhysAddr
inc eax
inc eax
mov [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart], eax
@ -1512,18 +1519,6 @@ proc uhci_unlink_pipe
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
@@:
jmp usb1_free_endpoint
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
@ -1612,7 +1607,7 @@ endl
.fail:
mov edi, uhci_hardware_func
mov eax, [td]
stdcall usb_undo_tds, [origTD]
invoke usbhc_api.usb_undo_tds, [origTD]
xor eax, eax
jmp .nothing
endp
@ -1652,7 +1647,7 @@ end virtual
mov eax, [.packetSize]
add eax, eax
add eax, sizeof.uhci_original_buffer
call malloc
invoke Kmalloc
; 1d. If failed, return zero.
test eax, eax
jz .nothing
@ -1695,14 +1690,14 @@ end virtual
.notempbuf:
; 2. Allocate the next TD.
push eax
call usb1_allocate_general_td
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
call usb_init_transfer
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,
@ -1725,7 +1720,7 @@ end virtual
; 5b. Store physical address of the next TD.
push eax
sub eax, sizeof.uhci_gtd
call get_phys_addr
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
@ -1748,7 +1743,7 @@ end virtual
jz @f
mov eax, [edx+uhci_original_buffer.UsedBuffer]
@@:
call get_phys_addr
invoke GetPhysAddr
.hasphysbuf:
mov [ecx+uhci_gtd.Buffer-sizeof.uhci_gtd], eax
; 5d. For IN transfers, disallow short packets.
@ -1774,7 +1769,7 @@ end virtual
ret
.fail:
xchg eax, edx
call free
invoke Kfree
xor eax, eax
ret
endp
@ -1796,6 +1791,47 @@ proc uhci_insert_transfer
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.
@ -1804,10 +1840,23 @@ proc uhci_free_td
mov eax, [eax+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd]
and eax, not 1
jz .nobuf
push ebx
call free
pop ebx
invoke Kfree
.nobuf:
sub dword [esp+4], sizeof.uhci_gtd
jmp usb_free_common
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

View File

@ -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

View File

@ -694,7 +694,8 @@ end virtual
mov [ecx+PCIDEV.fd], edi
mov [eax+PCIDEV.bk], edi
mov eax, dword [.devfn]
mov word [edi+PCIDEV.devfn], ax
mov dword [edi+PCIDEV.devfn], eax
mov dword [edi+PCIDEV.owner], 0
mov bh, al
mov al, 2
mov bl, 8

View File

@ -1,238 +1,33 @@
; USB Host Controller support code: hardware-independent part,
; common for all controller types.
; =============================================================================
; ================================= Constants =================================
; =============================================================================
; USB device must have at least 100ms of stable power before initializing can
; proceed; one timer tick is 10ms, so enforce delay in 10 ticks
USB_CONNECT_DELAY = 10
; USB requires at least 10 ms for reset signalling. Normally, this is one timer
; tick. However, it is possible that we start reset signalling in the end of
; interval between timer ticks and then we test time in the start of the next
; interval; in this case, the delta between [timer_ticks] is 1, but the real
; time passed is significantly less than 10 ms. To avoid this, we add an extra
; tick; this guarantees that at least 10 ms have passed.
USB_RESET_TIME = 2
; USB requires at least 10 ms of reset recovery, a delay between reset
; signalling and any commands to device. Add an extra tick for the same reasons
; as with the previous constant.
USB_RESET_RECOVERY_TIME = 2
; =============================================================================
; ================================ Structures =================================
; =============================================================================
; Controller descriptor.
; This structure represents the common (controller-independent) part
; of a controller for the USB code. The corresponding controller-dependent
; part *hci_controller is located immediately before usb_controller.
struct usb_controller
; Two following fields organize all controllers in the global linked list.
Next dd ?
Prev dd ?
HardwareFunc dd ?
; Pointer to usb_hardware_func structure with controller-specific functions.
NumPorts dd ?
; Number of ports in the root hub.
SetAddressBuffer rb 8
; Buffer for USB control command SET_ADDRESS.
ExistingAddresses rd 128/32
; Bitmask for 128 bits; bit i is cleared <=> address i is free for allocating
; for new devices. Bit 0 is always set.
;
; The hardware is allowed to cache some data from hardware structures.
; Regular operations are designed considering this,
; but sometimes it is required to wait for synchronization of hardware cache
; with modified structures in memory.
; The code keeps two queues of pipes waiting for synchronization,
; one for asynchronous (bulk/control) pipes, one for periodic pipes, hardware
; cache is invalidated under different conditions for those types.
; Both queues are organized in the same way, as single-linked lists.
; There are three special positions: the head of list (new pipes are added
; here), the first pipe to be synchronized at the current iteration,
; the tail of list (all pipes starting from here are synchronized).
WaitPipeListAsync dd ?
WaitPipeListPeriodic dd ?
; List heads.
WaitPipeRequestAsync dd ?
WaitPipeRequestPeriodic dd ?
; Pending request to hardware to refresh cache for items from WaitPipeList*.
; (Pointers to some items in WaitPipeList* or NULLs).
ReadyPipeHeadAsync dd ?
ReadyPipeHeadPeriodic dd ?
; Items of RemovingList* which were released by hardware and are ready
; for further processing.
; (Pointers to some items in WaitPipeList* or NULLs).
NewConnected dd ?
; bit mask of recently connected ports of the root hub,
; bit set = a device was recently connected to the corresponding port;
; after USB_CONNECT_DELAY ticks of stable status these ports are moved to
; PendingPorts
NewDisconnected dd ?
; bit mask of disconnected ports of the root hub,
; bit set = a device in the corresponding port was disconnected,
; disconnect processing is required.
PendingPorts dd ?
; bit mask of ports which are ready to be initialized
ControlLock MUTEX ?
; mutex which guards all operations with control queue
BulkLock MUTEX ?
; mutex which guards all operations with bulk queue
PeriodicLock MUTEX ?
; mutex which guards all operations with periodic queues
WaitSpinlock:
; spinlock guarding WaitPipeRequest/ReadyPipeHead (but not WaitPipeList)
StartWaitFrame dd ?
; USB frame number when WaitPipeRequest* was registered.
ResettingHub dd ?
; Pointer to usb_hub responsible for the currently resetting port, if any.
; NULL for the root hub.
ResettingPort db ?
; Port that is currently resetting, 0-based.
ResettingSpeed db ?
; Speed of currently resetting device.
ResettingStatus db ?
; Status of port reset. 0 = no port is resetting, -1 = reset failed,
; 1 = reset in progress, 2 = reset recovery in progress.
rb 1 ; alignment
ResetTime dd ?
; Time when reset signalling or reset recovery has been started.
ConnectedTime rd 16
; Time, in timer ticks, when the port i has signalled the connect event.
; Valid only if bit i in NewConnected is set.
DevicesByPort rd 16
; Pointer to usb_pipe for zero endpoint (which serves as device handle)
; for each port.
ends
; Interface-specific data. Several interfaces of one device can operate
; independently, each is controlled by some driver and is identified by
; some driver-specific data passed as is to the driver.
struct usb_interface_data
DriverData dd ?
; Passed as is to the driver.
DriverFunc dd ?
; Pointer to USBSRV structure for the driver.
ends
; Device-specific data.
struct usb_device_data
PipeListLock MUTEX
; Lock guarding OpenedPipeList. Must be the first item of the structure,
; the code passes pointer to usb_device_data as is to mutex_lock/unlock.
OpenedPipeList rd 2
; List of all opened pipes for the device.
; Used when the device is disconnected, so all pipes should be closed.
ClosedPipeList rd 2
; List of all closed, but still valid pipes for the device.
; A pipe closed with USBClosePipe is just deallocated,
; but a pipe closed due to disconnect must remain valid until driver-provided
; disconnect handler returns; this list links all such pipes to deallocate them
; after disconnect processing.
NumPipes dd ?
; Number of not-yet-closed pipes.
Hub dd ?
; NULL if connected to the root hub, pointer to usb_hub otherwise.
TTHub dd ?
; Pointer to usb_hub for (the) hub with Transaction Translator for the device,
; NULL if the device operates in the same speed as the controller.
Port db ?
; Port on the hub, zero-based.
TTPort db ?
; Port on the TTHub, zero-based.
DeviceDescrSize db ?
; Size of device descriptor.
Speed db ?
; Device speed, one of USB_SPEED_*.
NumInterfaces dd ?
; Number of interfaces.
ConfigDataSize dd ?
; Total size of data associated with the configuration descriptor
; (including the configuration descriptor itself).
Interfaces dd ?
; Offset from the beginning of this structure to Interfaces field.
; Variable-length fields:
; DeviceDescriptor:
; device descriptor starts here
; ConfigDescriptor = DeviceDescriptor + DeviceDescrSize
; configuration descriptor with all associated data
; Interfaces = ALIGN_UP(ConfigDescriptor + ConfigDataSize, 4)
; array of NumInterfaces elements of type usb_interface_data
ends
usb_device_data.DeviceDescriptor = sizeof.usb_device_data
; Description of controller-specific data and functions.
struct usb_hardware_func
ID dd ? ; '*HCI'
DataSize dd ? ; sizeof(*hci_controller)
Init dd ?
; Initialize controller-specific part of controller data.
; in: eax -> *hci_controller to initialize, [ebp-4] = (bus shl 8) + devfn
; out: eax = 0 <=> failed, otherwise eax -> usb_controller
ProcessDeferred dd ?
; Called regularly from the main loop of USB thread
; (either due to timeout from a previous call, or due to explicit wakeup).
; in: esi -> usb_controller
; out: eax = maximum timeout for next call (-1 = infinity)
SetDeviceAddress dd ?
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address
GetDeviceAddress dd ?
; in: esi -> usb_controller, ebx -> usb_pipe
; out: eax = address
PortDisable dd ?
; Disable the given port in the root hub.
; in: esi -> usb_controller, ecx = port (zero-based)
InitiateReset dd ?
; Start reset signalling on the given port.
; in: esi -> usb_controller, ecx = port (zero-based)
SetEndpointPacketSize dd ?
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size
AllocPipe dd ?
; out: eax = pointer to allocated usb_pipe
FreePipe dd ?
; void stdcall with one argument = pointer to previously allocated usb_pipe
InitPipe dd ?
; 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
UnlinkPipe dd ?
; esi -> usb_controller, ebx -> usb_pipe
AllocTD dd ?
; out: eax = pointer to allocated usb_gtd
FreeTD dd ?
; void stdcall with one argument = pointer to previously allocated usb_gtd
AllocTransfer dd ?
; Allocate and initialize one stage of a transfer.
; ebx -> usb_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
InsertTransfer dd ?
; Activate previously initialized transfer (maybe with multiple stages).
; esi -> usb_controller, ebx -> usb_pipe,
; [esp+4] -> first usb_gtd for the transfer,
; ecx -> last descriptor for the transfer
NewDevice dd ?
; Initiate configuration of a new device (create pseudo-pipe describing that
; device and call usb_new_device).
; esi -> usb_controller, eax = speed (one of USB_SPEED_* constants).
ends
; =============================================================================
; =================================== Code ====================================
; =============================================================================
iglobal
; USB HC support: some functions interesting only for *HCI-drivers.
align 4
usb_hc_func:
dd usb_process_gtd
dd usb_init_static_endpoint
dd usb_wakeup_if_needed
dd usb_subscribe_control
dd usb_subscription_done
dd usb_allocate_common
dd usb_free_common
dd usb_td_to_virt
dd usb_init_transfer
dd usb_undo_tds
dd usb_test_pending_port
dd usb_get_tt
dd usb_get_tt_think_time
dd usb_new_device
dd usb_disconnect_stage2
dd usb_process_wait_lists
dd usb_unlink_td
dd usb_is_final_packet
dd usb_find_ehci_companion
endg
; Initializes one controller, called by usb_init for every controller.
; edi -> usb_hardware_func, eax -> PCIDEV structure for the device.
; eax -> PCIDEV structure for the device.
proc usb_init_controller
push ebp
mov ebp, esp
@ -240,6 +35,10 @@ proc usb_init_controller
; make [ebp-4] = (bus shl 8) + devfn, used by controller-specific Init funcs.
push dword [eax+PCIDEV.devfn]
push eax
mov edi, [eax+PCIDEV.owner]
test edi, edi
jz .nothing
mov edi, [edi+USBSRV.usb_func]
; 2. Allocate *hci_controller + usb_controller.
mov ebx, [edi+usb_hardware_func.DataSize]
add ebx, sizeof.usb_controller
@ -264,6 +63,8 @@ proc usb_init_controller
mov [edi+usb_controller.ResettingPort-sizeof.usb_controller], al ; no resetting port
dec eax ; don't allocate zero address
mov [edi+usb_controller.ExistingAddresses-sizeof.usb_controller], eax
mov eax, [ebp-4]
mov [edi+usb_controller.PCICoordinates-sizeof.usb_controller], eax
lea ecx, [edi+usb_controller.PeriodicLock-sizeof.usb_controller]
call mutex_init
add ecx, usb_controller.ControlLock - usb_controller.PeriodicLock
@ -474,3 +275,48 @@ proc usb_process_one_wait_list
.nothing:
ret
endp
; Called from USB1 controller-specific initialization.
; Finds EHCI companion controller for given USB1 controller.
; in: bl = PCI device:function for USB1 controller, bh = PCI bus
; out: eax -> usb_controller for EHCI companion
proc usb_find_ehci_companion
; 1. Loop over all registered controllers.
mov eax, usb_controllers_list
.next:
mov eax, [eax+usb_controller.Next]
cmp eax, usb_controllers_list
jz .notfound
; 2. For every controller, check the type, ignore everything that is not EHCI.
mov edx, [eax+usb_controller.HardwareFunc]
cmp [edx+usb_hardware_func.ID], 'EHCI'
jnz .next
; 3. For EHCI controller, compare PCI coordinates with input data:
; bus and device must be the same, function can be different.
mov edx, [eax+usb_controller.PCICoordinates]
xor edx, ebx
cmp dx, 8
jae .next
ret
.notfound:
xor eax, eax
ret
endp
; Find Transaction Translator hub and port for the given device.
; in: edx = parent hub for the device, ecx = port for the device
; out: edx = TT hub for the device, ecx = TT port for the device.
proc usb_get_tt
; If the parent hub is high-speed, it is TT for the device.
; Otherwise, the parent hub itself is behind TT, and the device
; has the same TT hub+port as the parent hub.
mov eax, [edx+usb_hub.ConfigPipe]
mov eax, [eax+usb_pipe.DeviceData]
cmp [eax+usb_device_data.Speed], USB_SPEED_HS
jz @f
movzx ecx, [eax+usb_device_data.TTPort]
mov edx, [eax+usb_device_data.TTHub]
@@:
mov edx, [edx+usb_hub.ConfigPipe]
ret
endp

View File

@ -1262,3 +1262,14 @@ end virtual
.nothing:
retn 4
endp
; Helper function for USB2 scheduler.
; in: eax -> usb_hub
; out: ecx = TT think time for the hub in FS-bytes
proc usb_get_tt_think_time
movzx ecx, [eax+usb_hub.HubCharacteristics]
shr ecx, 5
and ecx, 3
inc ecx
ret
endp

View File

@ -29,19 +29,20 @@
; ProcessDeferred and sleeps until this moment is reached or the thread
; is awakened by IRQ handler.
iglobal
uhci_service_name:
db 'UHCI',0
ohci_service_name:
db 'OHCI',0
ehci_service_name:
db 'EHCI',0
endg
; Initializes the USB subsystem.
proc usb_init
; 1. Initialize all locks.
mov ecx, usb_controllers_list_mutex
call mutex_init
mov ecx, usb1_ep_mutex
call mutex_init
mov ecx, usb_gtd_mutex
call mutex_init
mov ecx, ehci_ep_mutex
call mutex_init
mov ecx, ehci_gtd_mutex
call mutex_init
; 2. Kick off BIOS from all USB controllers, calling the corresponding function
; *hci_kickoff_bios. Also count USB controllers for the next step.
; Note: USB1 companion(s) must go before the corresponding EHCI controller,
@ -59,18 +60,33 @@ proc usb_init
jz .done_kickoff
cmp word [esi+PCIDEV.class+1], 0x0C03
jnz .kickoff
mov eax, uhci_kickoff_bios
mov ebx, uhci_service_name
cmp byte [esi+PCIDEV.class], 0x00
jz .do_kickoff
mov eax, ohci_kickoff_bios
mov ebx, ohci_service_name
cmp byte [esi+PCIDEV.class], 0x10
jz .do_kickoff
mov eax, ehci_kickoff_bios
mov ebx, ehci_service_name
cmp byte [esi+PCIDEV.class], 0x20
jnz .kickoff
.do_kickoff:
inc dword [esp]
call eax
push ebx esi
stdcall get_service, ebx
pop esi ebx
test eax, eax
jz .driver_fail
mov edx, [eax+USBSRV.usb_func]
cmp [edx+usb_hardware_func.Version], USBHC_VERSION
jnz .driver_invalid
mov [esi+PCIDEV.owner], eax
call [edx+usb_hardware_func.BeforeInit]
jmp .kickoff
.driver_fail:
DEBUGF 1,'K : failed to load driver %s\n',ebx
jmp .kickoff
.driver_invalid:
DEBUGF 1,'K : driver %s has wrong version\n',ebx
jmp .kickoff
.done_kickoff:
pop eax
@ -97,7 +113,6 @@ proc usb_init
jz .done_ehci
cmp [eax+PCIDEV.class], 0x0C0320
jnz .scan_ehci
mov edi, ehci_hardware_func
call usb_init_controller
jmp .scan_ehci
.done_ehci:
@ -108,10 +123,8 @@ proc usb_init
mov eax, [eax+PCIDEV.fd]
cmp eax, pcidev_list
jz .done_usb1
mov edi, uhci_hardware_func
cmp [eax+PCIDEV.class], 0x0C0300
jz @f
mov edi, ohci_hardware_func
cmp [eax+PCIDEV.class], 0x0C0310
jnz .scan_usb1
@@:
@ -238,11 +251,8 @@ usb_controllers_list_mutex MUTEX
endg
include "memory.inc"
include "common.inc"
include "hccommon.inc"
include "pipe.inc"
include "ohci.inc"
include "uhci.inc"
include "ehci.inc"
include "protocol.inc"
include "hub.inc"
include "scheduler.inc"

View File

@ -12,77 +12,6 @@
; Data for one pool: dd pointer to the first page, MUTEX lock.
uglobal
; Structures in UHCI and OHCI have equal sizes.
; Thus, functions and data for allocating/freeing can be shared;
; we keep them here rather than in controller-specific files.
align 4
; Data for UHCI and OHCI endpoints pool.
usb1_ep_first_page dd ?
usb1_ep_mutex MUTEX
; Data for UHCI and OHCI general transfer descriptors pool.
usb_gtd_first_page dd ?
usb_gtd_mutex MUTEX
endg
; sanity check: structures in UHCI and OHCI should be the same for allocation
if (sizeof.ohci_pipe = sizeof.uhci_pipe)
; Allocates one endpoint structure for UHCI/OHCI.
; Returns pointer to software part (usb_pipe) in eax.
proc usb1_allocate_endpoint
push ebx
mov ebx, usb1_ep_mutex
stdcall 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 UHCI/OHCI.
; Stdcall with one argument, pointer to software part (usb_pipe).
proc usb1_free_endpoint
sub dword [esp+4], sizeof.ohci_pipe
jmp usb_free_common
endp
else
; sanity check continued
.err allocate_endpoint/free_endpoint must be different for OHCI and UHCI
end if
; sanity check: structures in UHCI and OHCI should be the same for allocation
if (sizeof.ohci_gtd = sizeof.uhci_gtd)
; Allocates one general transfer descriptor structure for UHCI/OHCI.
; Returns pointer to software part (usb_gtd) in eax.
proc usb1_allocate_general_td
push ebx
mov ebx, usb_gtd_mutex
stdcall 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 UHCI/OHCI.
; Stdcall with one argument, pointer to software part (usb_gtd).
proc usb1_free_general_td
sub dword [esp+4], sizeof.ohci_gtd
jmp usb_free_common
endp
else
; sanity check continued
.err allocate_general_td/free_general_td must be different for OHCI and UHCI
end if
; Allocator for fixed-size blocks: allocate a block.
; [ebx-4] = pointer to the first page, ebx = pointer to MUTEX structure.
proc usb_allocate_common
@ -187,12 +116,12 @@ end virtual
ret 4
endp
; Helper procedure for OHCI: translate physical address in ecx
; Helper procedure: translate physical address in ecx
; of some transfer descriptor to linear address.
; in: eax = address of first page
proc usb_td_to_virt
; Traverse all pages used for transfer descriptors, looking for the one
; with physical address as in ecx.
mov eax, [usb_gtd_first_page]
@@:
test eax, eax
jz .zero

View File

@ -1,195 +1,5 @@
; Functions for USB pipe manipulation: opening/closing, sending data etc.
;
; =============================================================================
; ================================= Constants =================================
; =============================================================================
; USB pipe types
CONTROL_PIPE = 0
ISOCHRONOUS_PIPE = 1
BULK_PIPE = 2
INTERRUPT_PIPE = 3
; Status codes for transfer callbacks.
; Taken from OHCI as most verbose controller in this sense.
USB_STATUS_OK = 0 ; no error
USB_STATUS_CRC = 1 ; CRC error
USB_STATUS_BITSTUFF = 2 ; bit stuffing violation
USB_STATUS_TOGGLE = 3 ; data toggle mismatch
USB_STATUS_STALL = 4 ; device returned STALL
USB_STATUS_NORESPONSE = 5 ; device not responding
USB_STATUS_PIDCHECK = 6 ; invalid PID check bits
USB_STATUS_WRONGPID = 7 ; unexpected PID value
USB_STATUS_OVERRUN = 8 ; too many data from endpoint
USB_STATUS_UNDERRUN = 9 ; too few data from endpoint
USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer
USB_STATUS_CLOSED = 16 ; pipe closed
; either explicitly with USBClosePipe
; or implicitly due to device disconnect
; flags for usb_pipe.Flags
USB_FLAG_CLOSED = 1 ; pipe is closed, no new transfers
; pipe is closed, return error instead of submitting any new transfer
USB_FLAG_CAN_FREE = 2
; pipe is closed via explicit call to USBClosePipe, so it can be freed without
; any driver notification; if this flag is not set, then the pipe is closed due
; to device disconnect, so it must remain valid until return from disconnect
; callback provided by the driver
USB_FLAG_EXTRA_WAIT = 4
; The pipe was in wait list, while another event occured;
; when the first wait will be done, reinsert the pipe to wait list
USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT
; =============================================================================
; ================================ Structures =================================
; =============================================================================
; Pipe descriptor.
; * An USB pipe is described by two structures, for hardware and for software.
; * This is the software part. The hardware part is defined in a driver
; of the corresponding controller.
; * The hardware part is located immediately before usb_pipe,
; both are allocated at once by controller-specific code
; (it knows the total length, which depends on the hardware part).
struct usb_pipe
Controller dd ?
; Pointer to usb_controller structure corresponding to this pipe.
; Must be the first dword after hardware part, see *hci_new_device.
;
; Every endpoint is included into one of processing lists:
; * Bulk list contains all Bulk endpoints.
; * Control list contains all Control endpoints.
; * Several Periodic lists serve Interrupt endpoints with different interval.
; - There are N=2^n "leaf" periodic lists for N ms interval, one is processed
; in the frames 0,N,2N,..., another is processed in the frames
; 1,1+N,1+2N,... and so on. The hardware starts processing of periodic
; endpoints in every frame from the list identified by lower n bits of the
; frame number; the addresses of these N lists are written to the
; controller data area during the initialization.
; - We assume that n=5, N=32 to simplify the code and compact the data.
; OHCI works in this way. UHCI and EHCI actually have n=10, N=1024,
; but this is an overkill for interrupt endpoints; the large value of N is
; useful only for isochronous transfers in UHCI and EHCI. UHCI/EHCI code
; initializes "leaf" lists k,k+32,k+64,...,k+(1024-32) to the same value,
; giving essentially N=32.
; This restriction means that the actual maximum interval of polling any
; interrupt endpoint is 32ms, which seems to be a reasonable value.
; - Similarly, there are 16 lists for 16-ms interval, 8 lists for 8-ms
; interval and so on. Finally, there is one list for 1ms interval. Their
; addresses are not directly known to the controller.
; - The hardware serves endpoints following a physical link from the hardware
; part.
; - The hardware links are organized as follows. If the list item is not the
; last, it's hardware link points to the next item. The hardware link of
; the last item points to the first item of the "next" list.
; - The "next" list for k-th and (k+M)-th periodic lists for interval 2M ms
; is the k-th periodic list for interval M ms, M >= 1. In this scheme,
; if two "previous" lists are served in the frames k,k+2M,k+4M,...
; and k+M,k+3M,k+5M,... correspondingly, the "next" list is served in
; the frames k,k+M,k+2M,k+3M,k+4M,k+5M,..., which is exactly what we want.
; - The links between Periodic, Control, Bulk lists and the processing of
; Isochronous endpoints are controller-specific.
; * The head of every processing list is a static entry which does not
; correspond to any real pipe. It is described by usb_static_ep
; structure, not usb_pipe. For OHCI and UHCI, sizeof.usb_static_ep plus
; sizeof hardware part is 20h, the total number of lists is
; 32+16+8+4+2+1+1+1 = 65, so all these structures fit in one page,
; leaving space for other data. This is another reason for 32ms limit.
; * Static endpoint descriptors are kept in *hci_controller structure.
; * All items in every processing list, including the static head, are
; organized in a double-linked list using .NextVirt and .PrevVirt fields.
; * [[item.NextVirt].PrevVirt] = [[item.PrevVirt].NextVirt] for all items.
NextVirt dd ?
; Next endpoint in the processing list.
; See also PrevVirt field and the description before NextVirt field.
PrevVirt dd ?
; Previous endpoint in the processing list.
; See also NextVirt field and the description before NextVirt field.
;
; Every pipe has the associated transfer queue, that is, the double-linked
; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt
; endpoints this list consists of usb_gtd structures
; (GTD = General Transfer Descriptors), for Isochronous endpoints
; this list consists of usb_itd structures, which are not developed yet.
; The pipe needs to know only the last TD; the first TD can be
; obtained as [[pipe.LastTD].NextVirt].
LastTD dd ?
; Last TD in the transfer queue.
;
; All opened pipes corresponding to the same physical device are organized in
; the double-linked list using .NextSibling and .PrevSibling fields.
; The head of this list is kept in usb_device_data structure (OpenedPipeList).
; This list is used when the device is disconnected and all pipes for the
; device should be closed.
; Also, all pipes closed due to disconnect must remain valid at least until
; driver-provided disconnect function returns; all should-be-freed-but-not-now
; pipes for one device are organized in another double-linked list with
; the head in usb_device_data.ClosedPipeList; this list uses the same link
; fields, one pipe can never be in both lists.
NextSibling dd ?
; Next pipe for the physical device.
PrevSibling dd ?
; Previous pipe for the physical device.
;
; When hardware part of pipe is changed, some time is needed before further
; actions so that hardware reacts on this change. During that time,
; all changed pipes are organized in single-linked list with the head
; usb_controller.WaitPipeList* and link field NextWait.
; Currently there are two possible reasons to change:
; change of address/packet size in initial configuration,
; close of the pipe. They are distinguished by USB_FLAG_CLOSED.
NextWait dd ?
Lock MUTEX
; Mutex that guards operations with transfer queue for this pipe.
Type db ?
; Type of pipe, one of {CONTROL,ISOCHRONOUS,BULK,INTERRUPT}_PIPE.
Flags db ?
; Combination of flags, USB_FLAG_*.
rb 2 ; dword alignment
DeviceData dd ?
; Pointer to usb_device_data, common for all pipes for one device.
ends
; This structure describes the static head of every list of pipes.
struct usb_static_ep
; software fields
Bandwidth dd ?
; valid only for interrupt/isochronous USB1 lists
; The offsets of the following two fields must be the same in this structure
; and in usb_pipe.
NextVirt dd ?
PrevVirt dd ?
ends
; This structure represents one transfer descriptor
; ('g' stands for "general" as opposed to isochronous usb_itd).
; Note that one transfer can have several descriptors:
; a control transfer has three stages.
; Additionally, every controller has a limit on transfer length with
; one descriptor (packet size for UHCI, 1K for OHCI, 4K for EHCI),
; large transfers must be split into individual packets according to that limit.
struct usb_gtd
Callback dd ?
; Zero for intermediate descriptors, pointer to callback function
; for final descriptor. See the docs for description of the callback.
UserData dd ?
; Dword which is passed to Callback as is, not used by USB code itself.
; Two following fields organize all descriptors for one pipe in
; the linked list.
NextVirt dd ?
PrevVirt dd ?
Pipe dd ?
; Pointer to the parent usb_pipe.
Buffer dd ?
; Pointer to data for this descriptor.
Length dd ?
; Length of data for this descriptor.
ends
; =============================================================================
; =================================== Code ====================================
; =============================================================================
USB_STDCALL_VERIFY = 1
macro stdcall_verify [arg]
{
@ -216,7 +26,7 @@ endp
proc usb_open_pipe stdcall uses ebx esi edi,\
config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword
locals
tt_vars rd (ehci_select_tt_interrupt_list.local_vars_size + 3) / 4
tt_vars rd 24 ; should be enough for ehci_select_tt_interrupt_list
targetsmask dd ? ; S-Mask for USB2
bandwidth dd ?
target dd ?
@ -810,6 +620,27 @@ proc usb_unlink_td
ret
endp
; One part of transfer is completed, run the associated callback
; or update total length in the next part of transfer.
; in: ebx -> usb_gtd, ecx = status, edx = length
proc usb_process_gtd
; 1. Test whether it is the last descriptor in the transfer
; <=> it has an associated callback.
mov eax, [ebx+usb_gtd.Callback]
test eax, eax
jz .nocallback
; 2. It has an associated callback; call it with corresponding parameters.
stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData]
ret
.nocallback:
; 3. It is an intermediate descriptor. Add its length to the length
; in the following descriptor.
mov eax, [ebx+usb_gtd.NextVirt]
add [eax+usb_gtd.Length], edx
ret
endp
if USB_STDCALL_VERIFY
proc verify_regs
virtual at esp

View File

@ -29,11 +29,6 @@ USB_DEVICE_QUALIFIER_DESCR = 6
USB_OTHER_SPEED_CONFIG_DESCR = 7
USB_INTERFACE_POWER_DESCR = 8
; Possible speeds of USB devices
USB_SPEED_FS = 0 ; full-speed
USB_SPEED_LS = 1 ; low-speed
USB_SPEED_HS = 2 ; high-speed
; Compile-time setting. If set, the code will dump all descriptors as they are
; read to the debug board.
USB_DUMP_DESCRIPTORS = 1
@ -370,7 +365,7 @@ proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, l
; so setting the address could take some time until the cache is evicted.
; Thus, the call is asynchronous; meet us in usb_after_set_address when it will
; be safe to continue.
dbgstr 'address set in device'
; dbgstr 'address set in device'
call [eax+usb_hardware_func.SetDeviceAddress]
; 2b. If the port is in non-root hub, clear 'reset in progress' flag.
; In any case, proceed to 4.
@ -409,7 +404,7 @@ endp
; is cleared after request from usb_set_address_callback.
; in: ebx -> usb_pipe
proc usb_after_set_address
dbgstr 'address set for controller'
; dbgstr 'address set for controller'
; Issue control transfer GET_DESCRIPTOR(DEVICE_DESCR) for first 8 bytes.
; Remember, we still do not know the actual packet size;
; 8-bytes-request is safe.

View File

@ -509,6 +509,8 @@ struct PCIDEV
class dd ?
devfn db ?
bus db ?
rb 2
owner dd ? ; pointer to SRV or 0
ends
; The following macro assume that we are on uniprocessor machine.

View File

@ -214,10 +214,35 @@ proc get_service stdcall, sz_name:dword
mov edx, [edx+SRV.fd]
jmp @B
.not_load:
mov eax, [sz_name]
; Try to load .dll driver first. If not, fallback to .obj.
push edi
sub esp, 36
mov edi, esp
mov dword [edi], '/sys'
mov dword [edi+4], '/dri'
mov dword [edi+8], 'vers'
mov byte [edi+12], '/'
@@:
mov dl, [eax]
mov [edi+13], dl
inc eax
inc edi
test dl, dl
jnz @b
mov dword [edi+12], '.sys'
mov byte [edi+16], 0
mov edi, esp
stdcall load_pe_driver, edi, 0
add esp, 36
pop edi
test eax, eax
jnz .nothing
pop ebp
jmp load_driver
.ok:
mov eax, edx
.nothing:
ret
endp

View File

@ -45,6 +45,7 @@ __exports:
map_io_mem, 'MapIoMem', \ ; stdcall
map_page, 'MapPage', \ ; stdcall
get_pg_addr, 'GetPgAddr', \ ; eax
get_phys_addr, 'GetPhysAddr', \ ; eax
map_space, 'MapSpace', \
release_pages, 'ReleasePages', \
\
@ -113,6 +114,7 @@ __exports:
usb_normal_transfer_async, 'USBNormalTransferAsync', \
usb_control_async, 'USBControlTransferAsync', \
usb_get_param, 'USBGetParam', \
usb_hc_func, 'USBHCFunc', \
\
NET_add_device, 'NetRegDev', \
NET_remove_device, 'NetUnRegDev', \

View File

@ -1253,22 +1253,7 @@ f68:
cmp edx, OS_BASE
jae .fail
mov edi, edx
stdcall load_PE, ecx
mov esi, eax
test eax, eax
jz @F
push edi
push DRV_ENTRY
call eax
add esp, 8
test eax, eax
jz @F
mov [eax+SRV.entry], esi
@@:
stdcall load_pe_driver, ecx, edx
mov [esp+32], eax
ret
.22:
@ -1348,26 +1333,32 @@ f68call: ; keep this table closer to main code
align 4
proc load_pe_driver stdcall, file:dword
proc load_pe_driver stdcall, file:dword, cmdline:dword
push esi
stdcall load_PE, [file]
test eax, eax
jz .fail
mov esi, eax
stdcall eax, DRV_ENTRY
push [cmdline]
push DRV_ENTRY
call eax
pop ecx
pop ecx
test eax, eax
jz .fail
mov [eax+SRV.entry], esi
pop esi
ret
.fail:
xor eax, eax
pop esi
ret
endp
align 4
proc init_mtrr
@ -1415,9 +1406,9 @@ proc init_mtrr
xor eax, eax
xor edx, edx
@@:
wrmsr
inc ecx
cmp ecx, 0x210
wrmsr
cmp ecx, 0x20F
jb @b
; enable MTRRs
pop eax

View File

@ -85,13 +85,12 @@ L3:
mov ecx, eax
add edi, DWORD PTR [edx+260]
add ecx, 3
shr ecx, 2
rep movsd
L4:
mov ecx, DWORD PTR [edx+256]
add ecx, 4095
and ecx, -4096
cmp ecx, eax
jbe L6
sub ecx, eax
@ -178,6 +177,10 @@ L23:
mov ecx, DWORD PTR [eax-4]
mov DWORD PTR [esp+48], edi
mov edx, DWORD PTR [eax-20]
test edx, edx
jnz @f
mov edx, ecx
@@:
mov DWORD PTR [esp+52], 0
add ecx, ebp
add edx, ebp