forked from KolibriOS/kolibrios
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:
parent
bcb5772288
commit
a10422fbce
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
232
drivers/usb/usb1_scheduler.inc
Normal file
232
drivers/usb/usb1_scheduler.inc
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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', \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user