/kernel/branches/kolibri-lldw/bus/pci/PCIe.inc |
---|
0,0 → 1,119 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2010-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; ;; |
;; PCIe.INC ;; |
;; ;; |
;; Extended PCI express services ;; |
;; ;; |
;; art_zh <artem@jerdev.co.uk> ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
;*************************************************************************** |
; Function |
; pci_ext_config: |
; |
; Description |
; PCIe extended (memory-mapped) config space detection |
; |
; WARNINGs: |
; 1) Very Experimental! |
; 2) direct HT-detection (no ACPI or BIOS service used) |
; 3) Only AMD/HT processors currently supported |
; |
;*************************************************************************** |
PCIe_CONFIG_SPACE = 0xF0000000 ; to be moved to const.inc |
mmio_pcie_cfg_addr dd 0x0 ; intel pcie space may be defined here |
mmio_pcie_cfg_lim dd 0x0 ; upper pcie space address |
align 4 |
pci_ext_config: |
mov ebx, [mmio_pcie_cfg_addr] |
or ebx, ebx |
jz @f |
or ebx, 0x7FFFFFFF ; required by PCI-SIG standards |
jnz .pcie_failed |
add ebx, 0x0FFFFC |
cmp ebx, [mmio_pcie_cfg_lim]; is the space limit correct? |
ja .pcie_failed |
jmp .pcie_cfg_mapped |
@@: |
mov ebx, [cpu_vendor] |
cmp ebx, dword [AMD_str] |
jne .pcie_failed |
mov bx, 0xC184 ; dev = 24, fn = 01, reg = 84h |
.check_HT_mmio: |
mov cx, bx |
mov ax, 0x0002 ; bus = 0, 1dword to read |
call pci_read_reg |
mov bx, cx |
sub bl, 4 |
and al, 0x80 ; check the NP bit |
jz .no_pcie_cfg |
shl eax, 8 ; bus:[27..20], dev:[19:15] |
or eax, 0x00007FFC ; fun:[14..12], reg:[11:2] |
mov [mmio_pcie_cfg_lim], eax |
mov cl, bl |
mov ax, 0x0002 ; bus = 0, 1dword to read |
call pci_read_reg |
mov bx, cx |
test al, 0x03 ; MMIO Base RW enabled? |
jz .no_pcie_cfg |
test al, 0x0C ; MMIO Base locked? |
jnz .no_pcie_cfg |
xor al, al |
shl eax, 8 |
test eax, 0x000F0000 ; MMIO Base must be bus0-aligned |
jnz .no_pcie_cfg |
mov [mmio_pcie_cfg_addr], eax |
add eax, 0x000FFFFC |
sub eax, [mmio_pcie_cfg_lim]; MMIO must cover at least one bus |
ja .no_pcie_cfg |
; -- it looks like a true PCIe config space; |
mov eax, [mmio_pcie_cfg_addr] ; physical address |
or eax, (PG_SHARED + PG_LARGE + PG_USER) |
mov ebx, PCIe_CONFIG_SPACE ; linear address |
mov ecx, ebx |
shr ebx, 20 |
add ebx, sys_pgdir ; PgDir entry @ |
@@: |
mov dword[ebx], eax ; map 4 buses |
invlpg [ecx] |
cmp bl, 4 |
jz .pcie_cfg_mapped ; fix it later |
add bl, 4 ; next PgDir entry |
add eax, 0x400000 ; eax += 4M |
add ecx, 0x400000 |
jmp @b |
.pcie_cfg_mapped: |
; -- glad to have the extended PCIe config field found |
; mov esi, boot_pcie_ok |
; call boot_log |
ret ; <<<<<<<<<<< OK >>>>>>>>>>> |
.no_pcie_cfg: |
xor eax, eax |
mov [mmio_pcie_cfg_addr], eax |
mov [mmio_pcie_cfg_lim], eax |
add bl, 12 |
cmp bl, 0xC0 ; MMIO regs lay below this offset |
jb .check_HT_mmio |
.pcie_failed: |
; mov esi, boot_pcie_fail |
; call boot_log |
ret ; <<<<<<<<< FAILURE >>>>>>>>> |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/pci/pci32.inc |
---|
0,0 → 1,679 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; ;; |
;; PCI32.INC ;; |
;; ;; |
;; 32 bit PCI driver code ;; |
;; ;; |
;; Version 0.3 April 9, 2007 ;; |
;; Version 0.2 December 21st, 2002 ;; |
;; ;; |
;; Author: Victor Prodan, victorprodan@yahoo.com ;; |
;; Mihailov Ilia, ghost.nsk@gmail.com ;; |
;; Credits: ;; |
;; Ralf Brown ;; |
;; Mike Hibbett, mikeh@oceanfree.net ;; |
;; ;; |
;; See file COPYING for details ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
;*************************************************************************** |
; Function |
; pci_api: |
; |
; Description |
; entry point for system PCI calls |
;*************************************************************************** |
;mmio_pci_addr = 0x400 ; set actual PCI address here to activate user-MMIO |
iglobal |
align 4 |
f62call: |
dd pci_fn_0 |
dd pci_fn_1 |
dd pci_fn_2 |
dd pci_service_not_supported ;3 |
dd pci_read_reg ;4 byte |
dd pci_read_reg ;5 word |
dd pci_read_reg ;6 dword |
dd pci_service_not_supported ;7 |
dd pci_write_reg ;8 byte |
dd pci_write_reg ;9 word |
dd pci_write_reg ;10 dword |
if defined mmio_pci_addr |
dd pci_mmio_init ;11 |
dd pci_mmio_map ;12 |
dd pci_mmio_unmap ;13 |
end if |
endg |
align 4 |
pci_api: |
;cross |
mov eax, ebx |
mov ebx, ecx |
mov ecx, edx |
cmp [pci_access_enabled], 1 |
jne pci_service_not_supported |
movzx edx, al |
if defined mmio_pci_addr |
cmp al, 13 |
ja pci_service_not_supported |
else |
cmp al, 10 |
ja pci_service_not_supported |
end if |
call dword [f62call+edx*4] |
mov dword [esp+32], eax |
ret |
align 4 |
pci_api_drv: |
cmp [pci_access_enabled], 1 |
jne .fail |
cmp eax, 2 |
ja .fail |
jmp dword [f62call+eax*4] |
.fail: |
or eax, -1 |
ret |
;; ============================================ |
pci_fn_0: |
; PCI function 0: get pci version (AH.AL) |
movzx eax, word [BOOT.pci_data+2] |
ret |
pci_fn_1: |
; PCI function 1: get last bus in AL |
mov al, [BOOT.pci_data+1] |
ret |
pci_fn_2: |
; PCI function 2: get pci access mechanism |
mov al, [BOOT.pci_data] |
ret |
pci_service_not_supported: |
or eax, -1 |
mov dword [esp+32], eax |
ret |
;*************************************************************************** |
; Function |
; pci_make_config_cmd |
; |
; Description |
; creates a command dword for use with the PCI bus |
; bus # in ah |
; device+func in bh (dddddfff) |
; register in bl |
; |
; command dword returned in eax ( 10000000 bbbbbbbb dddddfff rrrrrr00 ) |
;*************************************************************************** |
align 4 |
pci_make_config_cmd: |
shl eax, 8 ; move bus to bits 16-23 |
mov ax, bx ; combine all |
and eax, 0xffffff |
or eax, 0x80000000 |
ret |
;*************************************************************************** |
; Function |
; pci_read_reg: |
; |
; Description |
; read a register from the PCI config space into EAX/AX/AL |
; IN: ah=bus,device+func=bh,register address=bl |
; number of bytes to read (1,2,4) coded into AL, bits 0-1 |
; (0 - byte, 1 - word, 2 - dword) |
;*************************************************************************** |
align 4 |
pci_read_reg: |
push ebx esi |
cmp byte [BOOT.pci_data], 2;what mechanism will we use? |
je pci_read_reg_2 |
; mechanism 1 |
mov esi, eax ; save register size into ESI |
and esi, 3 |
call pci_make_config_cmd |
mov ebx, eax |
mov dx, 0xcf8 |
; set up addressing to config data |
mov eax, ebx |
and al, 0xfc; make address dword-aligned |
out dx, eax |
; get requested DWORD of config data |
mov dl, 0xfc |
and bl, 3 |
or dl, bl ; add to port address first 2 bits of register address |
or esi, esi |
jz pci_read_byte1 |
cmp esi, 1 |
jz pci_read_word1 |
cmp esi, 2 |
jz pci_read_dword1 |
jmp pci_fin_read1 |
pci_read_byte1: |
in al, dx |
jmp pci_fin_read1 |
pci_read_word1: |
in ax, dx |
jmp pci_fin_read1 |
pci_read_dword1: |
in eax, dx |
pci_fin_read1: |
pop esi ebx |
ret |
pci_read_reg_2: |
test bh, 128 ;mech#2 only supports 16 devices per bus |
jnz pci_read_reg_err |
mov esi, eax ; save register size into ESI |
and esi, 3 |
mov dx, 0xcfa |
; out 0xcfa,bus |
mov al, ah |
out dx, al |
; out 0xcf8,0x80 |
mov dl, 0xf8 |
mov al, 0x80 |
out dx, al |
; compute addr |
shr bh, 3; func is ignored in mechanism 2 |
or bh, 0xc0 |
mov dx, bx |
or esi, esi |
jz pci_read_byte2 |
cmp esi, 1 |
jz pci_read_word2 |
cmp esi, 2 |
jz pci_read_dword2 |
jmp pci_fin_read2 |
pci_read_byte2: |
in al, dx |
jmp pci_fin_read2 |
pci_read_word2: |
in ax, dx |
jmp pci_fin_read2 |
pci_read_dword2: |
in eax, dx |
pci_fin_read2: |
pop esi ebx |
ret |
pci_read_reg_err: |
xor eax, eax |
dec eax |
pop esi ebx |
ret |
;*************************************************************************** |
; Function |
; pci_write_reg: |
; |
; Description |
; write a register from ECX/CX/CL into the PCI config space |
; IN: ah=bus,device+func=bh,register address (dword aligned)=bl, |
; value to write in ecx |
; number of bytes to write (1,2,4) coded into AL, bits 0-1 |
; (0 - byte, 1 - word, 2 - dword) |
;*************************************************************************** |
align 4 |
pci_write_reg: |
push esi ebx |
cmp byte [BOOT.pci_data], 2;what mechanism will we use? |
je pci_write_reg_2 |
; mechanism 1 |
mov esi, eax ; save register size into ESI |
and esi, 3 |
call pci_make_config_cmd |
mov ebx, eax |
mov dx, 0xcf8 |
; set up addressing to config data |
mov eax, ebx |
and al, 0xfc; make address dword-aligned |
out dx, eax |
; write DWORD of config data |
mov dl, 0xfc |
and bl, 3 |
or dl, bl |
mov eax, ecx |
or esi, esi |
jz pci_write_byte1 |
cmp esi, 1 |
jz pci_write_word1 |
cmp esi, 2 |
jz pci_write_dword1 |
jmp pci_fin_write1 |
pci_write_byte1: |
out dx, al |
jmp pci_fin_write1 |
pci_write_word1: |
out dx, ax |
jmp pci_fin_write1 |
pci_write_dword1: |
out dx, eax |
pci_fin_write1: |
xor eax, eax |
pop ebx esi |
ret |
pci_write_reg_2: |
test bh, 128 ;mech#2 only supports 16 devices per bus |
jnz pci_write_reg_err |
mov esi, eax ; save register size into ESI |
and esi, 3 |
mov dx, 0xcfa |
; out 0xcfa,bus |
mov al, ah |
out dx, al |
; out 0xcf8,0x80 |
mov dl, 0xf8 |
mov al, 0x80 |
out dx, al |
; compute addr |
shr bh, 3; func is ignored in mechanism 2 |
or bh, 0xc0 |
mov dx, bx |
; write register |
mov eax, ecx |
or esi, esi |
jz pci_write_byte2 |
cmp esi, 1 |
jz pci_write_word2 |
cmp esi, 2 |
jz pci_write_dword2 |
jmp pci_fin_write2 |
pci_write_byte2: |
out dx, al |
jmp pci_fin_write2 |
pci_write_word2: |
out dx, ax |
jmp pci_fin_write2 |
pci_write_dword2: |
out dx, eax |
pci_fin_write2: |
xor eax, eax |
pop ebx esi |
ret |
pci_write_reg_err: |
xor eax, eax |
dec eax |
pop ebx esi |
ret |
if defined mmio_pci_addr ; must be set above |
;*************************************************************************** |
; Function |
; pci_mmio_init |
; |
; Description |
; IN: bx = device's PCI bus address (bbbbbbbbdddddfff) |
; Returns eax = user heap space available (bytes) |
; Error codes |
; eax = -1 : PCI user access blocked, |
; eax = -2 : device not registered for uMMIO service |
; eax = -3 : user heap initialization failure |
;*************************************************************************** |
pci_mmio_init: |
cmp bx, mmio_pci_addr |
jz @f |
mov eax, -2 |
ret |
@@: |
call init_heap ; (if not initialized yet) |
or eax, eax |
jz @f |
ret |
@@: |
mov eax, -3 |
ret |
;*************************************************************************** |
; Function |
; pci_mmio_map |
; |
; Description |
; maps a block of PCI memory to user-accessible linear address |
; |
; WARNING! This VERY EXPERIMENTAL service is for one chosen PCI device only! |
; The target device address should be set in kernel var mmio_pci_addr |
; |
; IN: ah = BAR#; |
; IN: ebx = block size (bytes); |
; IN: ecx = offset in MMIO block (in 4K-pages, to avoid misaligned pages); |
; |
; Returns eax = MMIO block's linear address in the userspace (if no error) |
; |
; |
; Error codes |
; eax = -1 : user access to PCI blocked, |
; eax = -2 : an invalid BAR register referred |
; eax = -3 : no i/o space on that BAR |
; eax = -4 : a port i/o BAR register referred |
; eax = -5 : dynamic userspace allocation problem |
;*************************************************************************** |
pci_mmio_map: |
and edx, 0x0ffff |
cmp ah, 6 |
jc .bar_0_5 |
jz .bar_rom |
mov eax, -2 |
ret |
.bar_rom: |
mov ah, 8 ; bar6 = Expansion ROM base address |
.bar_0_5: |
push ecx |
add ebx, 4095 |
and ebx, -4096 |
push ebx |
mov bl, ah ; bl = BAR# (0..5), however bl=8 for BAR6 |
shl bl, 1 |
shl bl, 1 |
add bl, 0x10; now bl = BAR offset in PCI config. space |
mov ax, mmio_pci_addr |
mov bh, al ; bh = dddddfff |
mov al, 2 ; al : DW to read |
call pci_read_reg |
or eax, eax |
jnz @f |
mov eax, -3 ; empty I/O space |
jmp mmio_ret_fail |
@@: |
test eax, 1 |
jz @f |
mov eax, -4 ; damned ports (not MMIO space) |
jmp mmio_ret_fail |
@@: |
pop ecx ; ecx = block size, bytes (expanded to whole page) |
mov ebx, ecx; user_alloc destroys eax, ecx, edx, but saves ebx |
and eax, 0xFFFFFFF0 |
push eax ; store MMIO physical address + keep 2DWords in the stack |
stdcall user_alloc, ecx |
or eax, eax |
jnz mmio_map_over |
mov eax, -5 ; problem with page allocation |
mmio_ret_fail: |
pop ecx |
pop edx |
ret |
mmio_map_over: |
mov ecx, ebx; ecx = size (bytes, expanded to whole page) |
shr ecx, 12 ; ecx = number of pages |
mov ebx, eax; ebx = linear address |
pop eax ; eax = MMIO start |
pop edx ; edx = MMIO shift (pages) |
shl edx, 12 ; edx = MMIO shift (bytes) |
add eax, edx; eax = uMMIO physical address |
or eax, PG_SHARED |
or eax, PG_UW |
or eax, PG_NOCACHE |
mov edi, ebx |
call commit_pages |
mov eax, edi |
ret |
;*************************************************************************** |
; Function |
; pci_mmio_unmap_page |
; |
; Description |
; unmaps the linear space previously tied to a PCI memory block |
; |
; IN: ebx = linear address of space previously allocated by pci_mmio_map |
; returns eax = 1 if successfully unmapped |
; |
; Error codes |
; eax = -1 if no user PCI access allowed, |
; eax = 0 if unmapping failed |
;*************************************************************************** |
pci_mmio_unmap: |
stdcall user_free, ebx |
ret |
end if |
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
uglobal |
align 4 |
; VendID (2), DevID (2), Revision = 0 (1), Class Code (3), FNum (1), Bus (1) |
pci_emu_dat: |
times 30*10 db 0 |
endg |
;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= |
align 4 |
sys_pcibios: |
cmp [pci_access_enabled], 1 |
jne .unsupported_func |
cmp [pci_bios_entry], 0 |
jz .emulate_bios |
push ds |
mov ax, pci_data_sel |
mov ds, ax |
mov eax, ebp |
mov ah, 0B1h |
call pword [cs:pci_bios_entry] |
pop ds |
jmp .return |
;-=-=-=-=-=-=-=-= |
.emulate_bios: |
cmp ebp, 1 ; PCI_FUNCTION_ID |
jnz .not_PCI_BIOS_PRESENT |
mov edx, 'PCI ' |
mov al, [BOOT.pci_data] |
mov bx, word[BOOT.pci_data + 2] |
mov cl, [BOOT.pci_data + 1] |
xor ah, ah |
jmp .return_abcd |
.not_PCI_BIOS_PRESENT: |
cmp ebp, 2 ; FIND_PCI_DEVICE |
jne .not_FIND_PCI_DEVICE |
mov ebx, pci_emu_dat |
..nxt: |
cmp [ebx], dx |
jne ..no |
cmp [ebx + 2], cx |
jne ..no |
dec si |
jns ..no |
mov bx, [ebx + 4] |
xor ah, ah |
jmp .return_ab |
..no: |
cmp word[ebx], 0 |
je ..dev_not_found |
add ebx, 10 |
jmp ..nxt |
..dev_not_found: |
mov ah, 0x86 ; DEVICE_NOT_FOUND |
jmp .return_a |
.not_FIND_PCI_DEVICE: |
cmp ebp, 3 ; FIND_PCI_CLASS_CODE |
jne .not_FIND_PCI_CLASS_CODE |
mov esi, pci_emu_dat |
shl ecx, 8 |
..nxt2: |
cmp [esi], ecx |
jne ..no2 |
mov bx, [esi] |
xor ah, ah |
jmp .return_ab |
..no2: |
cmp dword[esi], 0 |
je ..dev_not_found |
add esi, 10 |
jmp ..nxt2 |
.not_FIND_PCI_CLASS_CODE: |
cmp ebp, 8 ; READ_CONFIG_* |
jb .not_READ_CONFIG |
cmp ebp, 0x0A |
ja .not_READ_CONFIG |
mov eax, ebp |
mov ah, bh |
mov edx, edi |
mov bh, bl |
mov bl, dl |
call pci_read_reg |
mov ecx, eax |
xor ah, ah ; SUCCESSFUL |
jmp .return_abc |
.not_READ_CONFIG: |
cmp ebp, 0x0B ; WRITE_CONFIG_* |
jb .not_WRITE_CONFIG |
cmp ebp, 0x0D |
ja .not_WRITE_CONFIG |
lea eax, [ebp+1] |
mov ah, bh |
mov edx, edi |
mov bh, bl |
mov bl, dl |
call pci_write_reg |
xor ah, ah ; SUCCESSFUL |
jmp .return_abc |
.not_WRITE_CONFIG: |
.unsupported_func: |
mov ah, 0x81 ; FUNC_NOT_SUPPORTED |
.return: |
mov dword[esp + 4 ], edi |
mov dword[esp + 8], esi |
.return_abcd: |
mov dword[esp + 24], edx |
.return_abc: |
mov dword[esp + 28], ecx |
.return_ab: |
mov dword[esp + 20], ebx |
.return_a: |
mov dword[esp + 32], eax |
ret |
proc pci_enum |
push ebp |
mov ebp, esp |
push 0 |
virtual at ebp-4 |
.devfn db ? |
.bus db ? |
end virtual |
.loop: |
mov ah, [.bus] |
mov al, 2 |
mov bh, [.devfn] |
mov bl, 0 |
call pci_read_reg |
cmp eax, 0xFFFFFFFF |
jnz .has_device |
test byte [.devfn], 7 |
jnz .next_func |
jmp .no_device |
.has_device: |
push eax |
movi eax, sizeof.PCIDEV |
call malloc |
pop ecx |
test eax, eax |
jz .nomemory |
mov edi, eax |
mov [edi+PCIDEV.vendor_device_id], ecx |
mov eax, pcidev_list |
mov ecx, [eax+PCIDEV.bk] |
mov [edi+PCIDEV.bk], ecx |
mov [edi+PCIDEV.fd], eax |
mov [ecx+PCIDEV.fd], edi |
mov [eax+PCIDEV.bk], edi |
mov eax, dword [.devfn] |
mov dword [edi+PCIDEV.devfn], eax |
mov dword [edi+PCIDEV.owner], 0 |
mov bh, al |
mov al, 2 |
mov bl, 8 |
call pci_read_reg |
shr eax, 8 |
mov [edi+PCIDEV.class], eax |
test byte [.devfn], 7 |
jnz .next_func |
mov ah, [.bus] |
mov al, 0 |
mov bh, [.devfn] |
mov bl, 0Eh |
call pci_read_reg |
test al, al |
js .next_func |
.no_device: |
or byte [.devfn], 7 |
.next_func: |
inc dword [.devfn] |
mov ah, [.bus] |
cmp ah, [BOOT.pci_data+1] |
jbe .loop |
.nomemory: |
leave |
ret |
endp |
; Export for drivers. Just returns the pointer to the pci-devices list. |
proc get_pcidev_list |
mov eax, pcidev_list |
ret |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Rev |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/pci/pci16.inc |
---|
0,0 → 1,51 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; PCI16.INC ;; |
;; ;; |
;; 16 bit PCI driver code ;; |
;; ;; |
;; Version 0.2 December 21st, 2002 ;; |
;; ;; |
;; Author: Victor Prodan, victorprodan@yahoo.com ;; |
;; ;; |
;; See file COPYING for details ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
init_pci_16: |
pushad |
xor ax, ax |
mov es, ax |
mov byte [es:BOOT_LO.pci_data], 1;default mechanism:1 |
mov ax, 0xb101 |
int 0x1a |
or ah, ah |
jnz pci16skip |
mov [es:BOOT_LO.pci_data+1], cl;last PCI bus in system |
mov word[es:BOOT_LO.pci_data+2], bx |
mov dword[es:BOOT_LO.pci_data+4], edi |
; we have a PCI BIOS, so check which configuration mechanism(s) |
; it supports |
; AL = PCI hardware characteristics (bit0 => mechanism1, bit1 => mechanism2) |
test al, 1 |
jnz pci16skip |
test al, 2 |
jz pci16skip |
mov byte [es:BOOT_LO.pci_data], 2; if (al&3)==2 => mechanism 2 |
pci16skip: |
mov ax, 0x1000 |
mov es, ax |
popad |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Rev |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/pci |
---|
Property changes: |
Added: svn:ignore |
+*.mnt |
+lang.inc |
+*.bat |
+out.txt |
+scin* |
+*.obj |
/kernel/branches/kolibri-lldw/bus/usb/hccommon.inc |
---|
0,0 → 1,348 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
; USB Host Controller support code: hardware-independent part, |
; common for all controller types. |
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 slab_alloc |
dd slab_free |
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. |
; eax -> PCIDEV structure for the device. |
proc usb_init_controller |
push ebp |
mov ebp, esp |
; 1. Store in the stack PCI coordinates and save pointer to PCIDEV: |
; 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 |
stdcall kernel_alloc, ebx |
test eax, eax |
jz .nothing |
; 3. Zero-initialize both structures. |
push edi eax |
mov ecx, ebx |
shr ecx, 2 |
xchg edi, eax |
xor eax, eax |
rep stosd |
; 4. Initialize usb_controller structure, |
; except data known only to controller-specific code (like NumPorts) |
; and link fields |
; (this structure will be inserted to the overall list at step 6). |
dec eax |
mov [edi+usb_controller.ExistingAddresses+4-sizeof.usb_controller], eax |
mov [edi+usb_controller.ExistingAddresses+8-sizeof.usb_controller], eax |
mov [edi+usb_controller.ExistingAddresses+12-sizeof.usb_controller], eax |
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 |
call mutex_init |
add ecx, usb_controller.BulkLock - usb_controller.ControlLock |
call mutex_init |
pop eax edi |
mov [eax+ebx-sizeof.usb_controller+usb_controller.HardwareFunc], edi |
push eax |
; 5. Call controller-specific initialization. |
; If failed, free memory allocated in step 2 and return. |
call [edi+usb_hardware_func.Init] |
test eax, eax |
jz .fail |
pop ecx |
; 6. Insert the controller to the global list. |
xchg eax, ebx |
mov ecx, usb_controllers_list_mutex |
call mutex_lock |
mov edx, usb_controllers_list |
mov eax, [edx+usb_controller.Prev] |
mov [ebx+usb_controller.Next], edx |
mov [ebx+usb_controller.Prev], eax |
mov [edx+usb_controller.Prev], ebx |
mov [eax+usb_controller.Next], ebx |
call mutex_unlock |
; 7. Wakeup USB thread to call ProcessDeferred. |
call usb_wakeup |
.nothing: |
; 8. Restore pointer to PCIDEV saved in step 1 and return. |
pop eax |
leave |
ret |
.fail: |
call kernel_free |
jmp .nothing |
endp |
; Helper function, calculates physical address including offset in page. |
proc get_phys_addr |
push ecx |
mov ecx, eax |
and ecx, 0xFFF |
call get_pg_addr |
add eax, ecx |
pop ecx |
ret |
endp |
; Put the given control/bulk pipe in the wait list; |
; called when the pipe structure is changed and a possible hardware cache |
; needs to be synchronized. When it will be known that the cache is updated, |
; usb_subscription_done procedure will be called. |
proc usb_subscribe_control |
cmp [ebx+usb_pipe.NextWait], -1 |
jnz @f |
mov eax, [esi+usb_controller.WaitPipeListAsync] |
mov [ebx+usb_pipe.NextWait], eax |
mov [esi+usb_controller.WaitPipeListAsync], ebx |
@@: |
ret |
endp |
; Same as usb_subscribe_control, but for interrupt/isochronous pipe. |
proc usb_subscribe_periodic |
cmp [ebx+usb_pipe.NextWait], -1 |
jnz @f |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [ebx+usb_pipe.NextWait], eax |
mov [esi+usb_controller.WaitPipeListPeriodic], ebx |
@@: |
ret |
endp |
; Called after synchronization of hardware cache with software changes. |
; Continues process of device enumeration based on when it was delayed |
; due to call to usb_subscribe_control. |
proc usb_subscription_done |
mov eax, [ebx+usb_pipe.DeviceData] |
cmp [eax+usb_device_data.DeviceDescrSize], 0 |
jz usb_after_set_address |
jmp usb_after_set_endpoint_size |
endp |
; This function is called when a new device has either passed |
; or failed first stages of configuration, so the next device |
; can enter configuration process. |
proc usb_test_pending_port |
mov [esi+usb_controller.ResettingPort], -1 |
cmp [esi+usb_controller.PendingPorts], 0 |
jz .nothing |
bsf ecx, [esi+usb_controller.PendingPorts] |
btr [esi+usb_controller.PendingPorts], ecx |
mov eax, [esi+usb_controller.HardwareFunc] |
jmp [eax+usb_hardware_func.InitiateReset] |
.nothing: |
ret |
endp |
; This procedure is regularly called from controller-specific ProcessDeferred, |
; it checks whether there are disconnected events and if so, process them. |
proc usb_disconnect_stage2 |
bsf ecx, [esi+usb_controller.NewDisconnected] |
jz .nothing |
lock btr [esi+usb_controller.NewDisconnected], ecx |
btr [esi+usb_controller.PendingPorts], ecx |
xor ebx, ebx |
xchg ebx, [esi+usb_controller.DevicesByPort+ecx*4] |
test ebx, ebx |
jz usb_disconnect_stage2 |
call usb_device_disconnected |
jmp usb_disconnect_stage2 |
.nothing: |
ret |
endp |
; Initial stage of disconnect processing: called when device is disconnected. |
proc usb_device_disconnected |
; Loop over all pipes, close everything, wait until hardware reacts. |
; The final handling is done in usb_pipe_closed. |
push ebx |
mov ecx, [ebx+usb_pipe.DeviceData] |
call mutex_lock |
lea eax, [ecx+usb_device_data.OpenedPipeList-usb_pipe.NextSibling] |
push eax |
mov ebx, [eax+usb_pipe.NextSibling] |
.pipe_loop: |
call usb_close_pipe_nolock |
mov ebx, [ebx+usb_pipe.NextSibling] |
cmp ebx, [esp] |
jnz .pipe_loop |
pop eax |
pop ebx |
mov ecx, [ebx+usb_pipe.DeviceData] |
call mutex_unlock |
ret |
endp |
; Called from controller-specific ProcessDeferred, |
; processes wait-pipe-done notifications, |
; returns whether there are more items in wait queues. |
; in: esi -> usb_controller |
; out: eax = bitmask of pipe types with non-empty wait queue |
proc usb_process_wait_lists |
xor edx, edx |
push edx |
call usb_process_one_wait_list |
jnc @f |
or byte [esp], 1 shl CONTROL_PIPE |
@@: |
movi edx, 4 |
call usb_process_one_wait_list |
jnc @f |
or byte [esp], 1 shl INTERRUPT_PIPE |
@@: |
xor edx, edx |
call usb_process_one_wait_list |
jnc @f |
or byte [esp], 1 shl CONTROL_PIPE |
@@: |
pop eax |
ret |
endp |
; Helper procedure for usb_process_wait_lists; |
; does the same for one wait queue. |
; in: esi -> usb_controller, |
; edx=0 for *Async, edx=4 for *Periodic list |
; out: CF = issue new request |
proc usb_process_one_wait_list |
; 1. Check whether there is a pending request. If so, do nothing. |
mov ebx, [esi+usb_controller.WaitPipeRequestAsync+edx] |
cmp ebx, [esi+usb_controller.ReadyPipeHeadAsync+edx] |
clc |
jnz .nothing |
; 2. Check whether there are new data. If so, issue a new request. |
cmp ebx, [esi+usb_controller.WaitPipeListAsync+edx] |
stc |
jnz .nothing |
test ebx, ebx |
jz .nothing |
; 3. Clear all lists. |
xor ecx, ecx |
mov [esi+usb_controller.WaitPipeListAsync+edx], ecx |
mov [esi+usb_controller.WaitPipeRequestAsync+edx], ecx |
mov [esi+usb_controller.ReadyPipeHeadAsync+edx], ecx |
; 4. Loop over all pipes from the wait list. |
.pipe_loop: |
; For every pipe: |
; 5. Save edx and next pipe in the list. |
push edx |
push [ebx+usb_pipe.NextWait] |
; 6. If USB_FLAG_EXTRA_WAIT is set, reinsert the pipe to the list and continue. |
test [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT |
jz .process |
mov eax, [esi+usb_controller.WaitPipeListAsync+edx] |
mov [ebx+usb_pipe.NextWait], eax |
mov [esi+usb_controller.WaitPipeListAsync+edx], ebx |
jmp .continue |
.process: |
; 7. Call the handler depending on USB_FLAG_CLOSED and USB_FLAG_DISABLED. |
or [ebx+usb_pipe.NextWait], -1 |
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
jz .nodisconnect |
call usb_pipe_closed |
jmp .continue |
.nodisconnect: |
test [ebx+usb_pipe.Flags], USB_FLAG_DISABLED |
jz .nodisabled |
call usb_pipe_disabled |
jmp .continue |
.nodisabled: |
call usb_subscription_done |
.continue: |
; 8. Restore edx and next pipe saved in step 5 and continue the loop. |
pop ebx |
pop edx |
test ebx, ebx |
jnz .pipe_loop |
.check_new_work: |
; 9. Set CF depending on whether WaitPipeList* is nonzero. |
cmp [esi+usb_controller.WaitPipeListAsync+edx], 1 |
cmc |
.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 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/usb/memory.inc |
---|
0,0 → 1,43 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
; Memory management for USB structures. |
; Protocol layer uses the common kernel heap malloc/free. |
; Hardware layer has special requirements: |
; * memory blocks should be properly aligned |
; * memory blocks should not cross page boundary |
; Hardware layer allocates fixed-size blocks. |
; Thus, hardware layer uses the system slab allocator. |
; 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. |
@@: |
test eax, eax |
jz .zero |
push eax |
call get_pg_addr |
sub eax, ecx |
jz .found |
cmp eax, -0x1000 |
ja .found |
pop eax |
mov eax, [eax+0x1000-4] |
jmp @b |
.found: |
; When found, combine page address from eax with page offset from ecx. |
pop eax |
and ecx, 0xFFF |
add eax, ecx |
.zero: |
ret |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/usb/hub.inc |
---|
0,0 → 1,1285 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
; Support for USB (non-root) hubs: |
; powering up/resetting/disabling ports, |
; watching for adding/removing devices. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; Hub constants |
; USB hub descriptor type |
USB_HUB_DESCRIPTOR = 29h |
; Features for CLEAR_FEATURE commands to the hub. |
C_HUB_LOCAL_POWER = 0 |
C_HUB_OVER_CURRENT = 1 |
; Bits in result of GET_STATUS command for a port. |
; Also suitable for CLEAR_FEATURE/SET_FEATURE commands, where applicable, |
; except TEST/INDICATOR. |
PORT_CONNECTION = 0 |
PORT_ENABLE = 1 |
PORT_SUSPEND = 2 |
PORT_OVER_CURRENT = 3 |
PORT_RESET = 4 |
PORT_POWER = 8 |
PORT_LOW_SPEED = 9 |
PORT_HIGH_SPEED = 10 |
PORT_TEST_BIT = 11 |
PORT_INDICATOR_BIT = 12 |
C_PORT_CONNECTION = 16 |
C_PORT_ENABLE = 17 |
C_PORT_SUSPEND = 18 |
C_PORT_OVER_CURRENT = 19 |
C_PORT_RESET = 20 |
PORT_TEST_FEATURE = 21 |
PORT_INDICATOR_FEATURE = 22 |
; Internal constants |
; Bits in usb_hub.Actions |
HUB_WAIT_POWERED = 1 |
; ports were powered, wait until power is stable |
HUB_WAIT_CONNECT = 2 |
; some device was connected, wait initial debounce interval |
HUB_RESET_IN_PROGRESS = 4 |
; reset in progress, so buffer for config requests is owned |
; by reset process; this includes all stages from initial disconnect test |
; to end of setting address (fail on any stage should lead to disabling port, |
; which requires a config request) |
HUB_RESET_WAITING = 8 |
; the port is ready for reset, but another device somewhere on the bus |
; is resetting. Implies HUB_RESET_IN_PROGRESS |
HUB_RESET_SIGNAL = 10h |
; reset signalling is active for some port in the hub |
; Implies HUB_RESET_IN_PROGRESS |
HUB_RESET_RECOVERY = 20h |
; reset recovery is active for some port in the hub |
; Implies HUB_RESET_IN_PROGRESS |
; Well, I think that those 5 flags WAIT_CONNECT and RESET_* require additional |
; comments. So that is the overview of what happens with a new device assuming |
; no errors. |
; * device is connected; |
; * hub notifies us about connect event; after some processing |
; usb_hub_port_change finally processes that event, setting the flag |
; HUB_WAIT_CONNECT and storing time when the device was connected; |
; * 100 ms delay; |
; * usb_hub_process_deferred clears HUB_WAIT_CONNECT, |
; sets HUB_RESET_IN_PROGRESS, stores the port index in ConfigBuffer and asks |
; the hub whether there was a disconnect event for that port during those |
; 100 ms (on the hardware level notifications are obtained using polling |
; with some intervals, so it is possible that the corresponding notification |
; has not arrived yet); |
; * usb_hub_connect_port_status checks that there was no disconnect event |
; and sets HUB_RESET_WAITING flag (HUB_RESET_IN_PROGRESS is still set, |
; ConfigBuffer still contains the port index); |
; * usb_hub_process_deferred checks whether there is another device currently |
; resetting. If so, it waits until reset is done |
; (with HUB_RESET_WAITING and HUB_RESET_IN_PROGRESS bits set); |
; * usb_hub_process_deferred clears HUB_RESET_WAITING, sets HUB_RESET_SIGNAL |
; and initiates reset signalling on the port; |
; * usb_hub_process_deferred checks the status every tick; |
; when reset signalling is stopped by the hub, usb_hub_resetting_port_status |
; callback clears HUB_RESET_SIGNAL and sets HUB_RESET_RECOVERY; |
; * 10 ms (at least) delay; |
; * usb_hub_process_deferred clears HUB_RESET_RECOVERY and notifies other code |
; that the new device is ready to be configured; |
; * when it is possible to reset another device, the protocol layer |
; clears HUB_RESET_IN_PROGRESS bit. |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; This structure contains all used data for one hub. |
struct usb_hub |
; All configured hubs are organized in the global usb_hub_list. |
; Two following fields give next/prev items in that list. |
; While the hub is unconfigured, they point to usb_hub itself. |
Next dd ? |
Prev dd ? |
Controller dd ? |
; Pointer to usb_controller for the bus. |
; |
; Handles of two pipes: configuration control pipe for zero endpoint opened by |
; the common code and status interrupt pipe opened by us. |
ConfigPipe dd ? |
StatusPipe dd ? |
NumPorts dd ? |
; Number of downstream ports; from 1 to 255. |
MaxPacketSize dd ? |
; Maximum packet size for interrupt endpoint. |
; Usually equals ceil((1+NumPorts)/8), but some hubs give additional bytes. |
Actions dd ? |
; Bitfield with HUB_* constants. |
PoweredOnTime dd ? |
; Time (in ticks) when all downstream ports were powered up. |
ResetTime dd ? |
; Time (in ticks) when the current port was reset; |
; when a port is resetting, contains the last tick of status check; |
; when reset recovery for a port is active, contains the time when |
; reset was completed. |
; |
; There are two possible reasons for configuration requests: |
; synchronous, when certain time is passed after something, |
; and asynchronous, when the hub is notifying about some change and |
; config request needs to be issued in order to query details. |
; Use two different buffers to avoid unnecessary dependencies. |
ConfigBuffer rb 8 |
; Buffer for configuration requests for synchronous events. |
ChangeConfigBuffer rb 8 |
; Buffer for configuration requests for status changes. |
AccStatusChange db ? |
; Accumulated status change. See 11.12.3 of USB2 spec or comments in code. |
HubCharacteristics dw ? |
; Copy of usb_hub_descr.wHubCharacteristics. |
PowerOnInterval db ? |
; Copy of usb_hub_descr.bPwrOn2PwrGood. |
; |
; Two following fields are written at once by GET_STATUS request |
; and must remain in this order. |
StatusData dw ? |
; Bitfield with 1 shl PORT_* indicating status of the current port. |
StatusChange dw ? |
; Bitfield with 1 shl PORT_* indicating change in status of the current port. |
; Two following fields are written at once by GET_STATUS request |
; and must remain in this order. |
; The meaning is the same as of StatusData/StatusChange; two following fields |
; are used by the synchronous requests to avoid unnecessary interactions with |
; the asynchronous handler. |
ResetStatusData dw ? |
ResetStatusChange dw ? |
StatusChangePtr dd ? |
; Pointer to StatusChangeBuf. |
ConnectedDevicesPtr dd ? |
; Pointer to ConnectedDevices. |
ConnectedTimePtr dd ? |
; Pointer to ConnectedTime. |
; |
; Variable-length parts: |
; DeviceRemovable rb (NumPorts+8)/8 |
; Bit i+1 = device at port i (zero-based) is non-removable. |
; StatusChangeBuf rb (NumPorts+8)/8 |
; Buffer for status interrupt pipe. Bit 0 = hub status change, |
; other bits = status change of the corresponding ports. |
; ConnectedDevices rd NumPorts |
; Pointers to config pipes for connected devices or zero if no device connected. |
; ConnectedTime rd NumPorts |
; For initial debounce interval: |
; time (in ticks) when a device was connected at that port. |
; Normally: -1 |
ends |
; Hub descriptor. |
struct usb_hub_descr usb_descr |
bNbrPorts db ? |
; Number of downstream ports. |
wHubCharacteristics dw ? |
; Bit 0: 0 = all ports are powered at once, 1 = individual port power switching |
; Bit 1: reserved, must be zero |
; Bit 2: 1 = the hub is part of a compound device |
; Bits 3-4: 00 = global overcurrent protection, |
; 01 = individual port overcurrent protection, |
; 1x = no overcurrent protection |
; Bits 5-6: Transaction Translator Think Time, 8*(value+1) full-speed bit times |
; Bit 7: 1 = port indicators supported |
; Other bits are reserved. |
bPwrOn2PwrGood db ? |
; Time in 2ms intervals between powering up a port and a port becoming ready. |
bHubContrCurrent db ? |
; Maximum current requirements of the Hub Controller electronics in mA. |
; DeviceRemovable - variable length |
; Bit 0 is reserved, bit i+1 = device at port i is non-removable. |
; PortPwrCtrlMask - variable length |
; Obsolete, exists for compatibility. We ignore it. |
ends |
iglobal |
align 4 |
; Implementation of struct USBFUNC for hubs. |
usb_hub_callbacks: |
dd usb_hub_callbacks_end - usb_hub_callbacks |
dd usb_hub_init |
dd usb_hub_disconnect |
usb_hub_callbacks_end: |
usb_hub_pseudosrv dd usb_hub_callbacks |
endg |
; This procedure is called when new hub is detected. |
; It initializes the device. |
; Technically, initialization implies sending several USB queries, |
; so it is split in several procedures. The first is usb_hub_init, |
; other are callbacks which will be called at some time in the future, |
; when the device will respond. |
; edx = usb_interface_descr, ecx = length rest |
proc usb_hub_init |
push ebx esi ; save used registers to be stdcall |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.pipe dd ? ; handle of the config pipe |
.config dd ? ; pointer to usb_config_descr |
.interface dd ? ; pointer to usb_interface_descr |
end virtual |
; 1. Check that the maximal nesting is not exceeded: |
; 5 non-root hubs is the maximum according to the spec. |
mov ebx, [.pipe] |
push 5 |
mov eax, ebx |
.count_parents: |
mov eax, [eax+usb_pipe.DeviceData] |
mov eax, [eax+usb_device_data.Hub] |
test eax, eax |
jz .depth_ok |
mov eax, [eax+usb_hub.ConfigPipe] |
dec dword [esp] |
jnz .count_parents |
pop eax |
dbgstr 'Hub chain is too long' |
jmp .return0 |
.depth_ok: |
pop eax |
; Hubs use one IN interrupt endpoint for polling the device |
; 2. Locate the descriptor of the interrupt endpoint. |
; Loop over all descriptors owned by this interface. |
.lookep: |
; 2a. Skip the current descriptor. |
movzx eax, [edx+usb_descr.bLength] |
add edx, eax |
sub ecx, eax |
jb .errorep |
; 2b. Length of data left must be at least sizeof.usb_endpoint_descr. |
cmp ecx, sizeof.usb_endpoint_descr |
jb .errorep |
; 2c. If we have found another interface descriptor but not found our endpoint, |
; this is an error: all subsequent descriptors belong to that interface |
; (or further interfaces). |
cmp [edx+usb_endpoint_descr.bDescriptorType], USB_INTERFACE_DESCR |
jz .errorep |
; 2d. Ignore all interface-related descriptors except endpoint descriptor. |
cmp [edx+usb_endpoint_descr.bDescriptorType], USB_ENDPOINT_DESCR |
jnz .lookep |
; 2e. Length of endpoint descriptor must be at least sizeof.usb_endpoint_descr. |
cmp [edx+usb_endpoint_descr.bLength], sizeof.usb_endpoint_descr |
jb .errorep |
; 2f. Ignore all endpoints except for INTERRUPT IN. |
cmp [edx+usb_endpoint_descr.bEndpointAddress], 0 |
jge .lookep |
mov al, [edx+usb_endpoint_descr.bmAttributes] |
and al, 3 |
cmp al, INTERRUPT_PIPE |
jnz .lookep |
; We have located the descriptor for INTERRUPT IN endpoint, |
; the pointer is in edx. |
; 3. Allocate memory for the hub descriptor. |
; Maximum length (assuming 255 downstream ports) is 40 bytes. |
; Allocate 4 extra bytes to keep wMaxPacketSize. |
; 3a. Save registers. |
push edx |
; 3b. Call the allocator. |
movi eax, 44 |
call malloc |
; 3c. Restore registers. |
pop ecx |
; 3d. If failed, say something to the debug board and return error. |
test eax, eax |
jz .nomemory |
; 3e. Store the pointer in esi. xchg eax,r32 is one byte shorter than mov. |
xchg esi, eax |
; 4. Open a pipe for the status endpoint with descriptor found in step 1. |
movzx eax, [ecx+usb_endpoint_descr.bEndpointAddress] |
movzx edx, [ecx+usb_endpoint_descr.bInterval] |
movzx ecx, [ecx+usb_endpoint_descr.wMaxPacketSize] |
test ecx, (1 shl 11) - 1 |
jz .free |
push ecx |
stdcall usb_open_pipe, ebx, eax, ecx, INTERRUPT_PIPE, edx |
pop ecx |
; If failed, free the memory allocated in step 3, |
; say something to the debug board and return error. |
test eax, eax |
jz .free |
; 5. Send control query for the hub descriptor, |
; pass status pipe as a callback parameter, |
; allow short packets. |
and ecx, (1 shl 11) - 1 |
mov [esi+40], ecx |
mov dword [esi], 0xA0 + \ ; class-specific request |
(USB_GET_DESCRIPTOR shl 8) + \ |
(0 shl 16) + \ ; descriptor index 0 |
(USB_HUB_DESCRIPTOR shl 24) |
mov dword [esi+4], 40 shl 16 |
stdcall usb_control_async, ebx, esi, esi, 40, usb_hub_got_config, eax, 1 |
; 6. If failed, free the memory allocated in step 3, |
; say something to the debug board and return error. |
test eax, eax |
jz .free |
; Otherwise, return 1. usb_hub_got_config will overwrite it later. |
xor eax, eax |
inc eax |
jmp .nothing |
.free: |
xchg eax, esi |
call free |
jmp .return0 |
.errorep: |
dbgstr 'Invalid config descriptor for a hub' |
jmp .return0 |
.nomemory: |
dbgstr 'No memory for USB hub data' |
.return0: |
xor eax, eax |
.nothing: |
pop esi ebx ; restore used registers to be stdcall |
retn 12 |
endp |
; This procedure is called when the request for the hub descriptor initiated |
; by usb_hub_init is finished, either successfully or unsuccessfully. |
proc usb_hub_got_config stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
push ebx ; save used registers to be stdcall |
; 1. If failed, say something to the debug board, free the buffer |
; and stop the initialization. |
cmp [status], 0 |
jnz .invalid |
; 2. The length must be at least sizeof.usb_hub_descr. |
; Note that [length] includes 8 bytes of setup packet. |
cmp [length], 8 + sizeof.usb_hub_descr |
jb .invalid |
; 3. Sanity checks for the hub descriptor. |
mov eax, [buffer] |
if USB_DUMP_DESCRIPTORS |
mov ecx, [length] |
sub ecx, 8 |
DEBUGF 1,'K : hub config:' |
push eax |
@@: |
DEBUGF 1,' %x',[eax]:2 |
inc eax |
dec ecx |
jnz @b |
DEBUGF 1,'\n' |
pop eax |
end if |
cmp [eax+usb_hub_descr.bLength], sizeof.usb_hub_descr |
jb .invalid |
cmp [eax+usb_hub_descr.bDescriptorType], USB_HUB_DESCRIPTOR |
jnz .invalid |
movzx ecx, [eax+usb_hub_descr.bNbrPorts] |
test ecx, ecx |
jz .invalid |
; 4. We use sizeof.usb_hub_descr bytes plus DeviceRemovable info; |
; size of DeviceRemovable is (NumPorts+1) bits, this gives |
; floor(NumPorts/8)+1 bytes. Check that all data are present in the |
; descriptor and were successfully read. |
mov edx, ecx |
shr edx, 3 |
add edx, sizeof.usb_hub_descr + 1 |
cmp [eax+usb_hub_descr.bLength], dl |
jb .invalid |
sub [length], 8 |
cmp [length], edx |
jb .invalid |
; 5. Allocate the memory for usb_hub structure. |
; Total size of variable-length data is ALIGN_UP(floor(NumPorts/8)+1+MaxPacketSize,4)+8*NumPorts. |
add edx, [eax+40] |
add edx, sizeof.usb_hub - sizeof.usb_hub_descr + 3 |
and edx, not 3 |
lea eax, [edx+ecx*8] |
push ecx edx |
call malloc |
pop edx ecx |
test eax, eax |
jz .nomemory |
xchg eax, ebx |
; 6. Fill usb_hub structure. |
mov [ebx+usb_hub.NumPorts], ecx |
add edx, ebx |
mov [ebx+usb_hub.ConnectedDevicesPtr], edx |
mov eax, [pipe] |
mov [ebx+usb_hub.ConfigPipe], eax |
mov edx, [eax+usb_pipe.Controller] |
mov [ebx+usb_hub.Controller], edx |
mov eax, [calldata] |
mov [ebx+usb_hub.StatusPipe], eax |
push esi edi |
mov esi, [buffer] |
mov eax, [esi+40] |
mov [ebx+usb_hub.MaxPacketSize], eax |
; The following commands load bNbrPorts, wHubCharacteristics, bPwrOn2PwrGood. |
mov edx, dword [esi+usb_hub_descr.bNbrPorts] |
mov dl, 0 |
; The following command zeroes AccStatusChange and stores |
; HubCharacteristics and PowerOnInterval. |
mov dword [ebx+usb_hub.AccStatusChange], edx |
xor eax, eax |
mov [ebx+usb_hub.Actions], eax |
; Copy DeviceRemovable data. |
lea edi, [ebx+sizeof.usb_hub] |
add esi, sizeof.usb_hub_descr |
mov edx, ecx |
shr ecx, 3 |
inc ecx |
rep movsb |
mov [ebx+usb_hub.StatusChangePtr], edi |
; Zero ConnectedDevices. |
mov edi, [ebx+usb_hub.ConnectedDevicesPtr] |
mov ecx, edx |
rep stosd |
mov [ebx+usb_hub.ConnectedTimePtr], edi |
; Set ConnectedTime to -1. |
dec eax |
mov ecx, edx |
rep stosd |
pop edi esi |
; 7. Replace value of 1 returned from usb_hub_init to the real value. |
; Note: hubs are part of the core USB code, so this code can work with |
; internals of other parts. Another way, the only possible one for external |
; drivers, is to use two memory allocations: one (returned from AddDevice and |
; fixed after that) for pointer, another for real data. That would work also, |
; but wastes one allocation. |
mov eax, [pipe] |
mov eax, [eax+usb_pipe.DeviceData] |
add eax, [eax+usb_device_data.Interfaces] |
.scan: |
cmp [eax+usb_interface_data.DriverData], 1 |
jnz @f |
cmp [eax+usb_interface_data.DriverFunc], usb_hub_pseudosrv - USBSRV.usb_func |
jz .scan_found |
@@: |
add eax, sizeof.usb_interface_data |
jmp .scan |
.scan_found: |
mov [eax+usb_interface_data.DriverData], ebx |
; 8. Insert the hub structure to the tail of the overall list of all hubs. |
mov ecx, usb_hubs_list |
mov edx, [ecx+usb_hub.Prev] |
mov [ecx+usb_hub.Prev], ebx |
mov [edx+usb_hub.Next], ebx |
mov [ebx+usb_hub.Prev], edx |
mov [ebx+usb_hub.Next], ecx |
; 9. Start powering up all ports. |
DEBUGF 1,'K : found hub with %d ports\n',[ebx+usb_hub.NumPorts] |
lea eax, [ebx+usb_hub.ConfigBuffer] |
xor ecx, ecx |
mov dword [eax], 23h + \ ; class-specific request to hub port |
(USB_SET_FEATURE shl 8) + \ |
(PORT_POWER shl 16) |
mov edx, [ebx+usb_hub.NumPorts] |
mov dword [eax+4], edx |
stdcall usb_control_async, [ebx+usb_hub.ConfigPipe], eax, ecx, ecx, usb_hub_port_powered, ebx, ecx |
.freebuf: |
; 10. Free the buffer for hub descriptor and return. |
mov eax, [buffer] |
call free |
pop ebx ; restore used registers to be stdcall |
ret |
.nomemory: |
dbgstr 'No memory for USB hub data' |
jmp .freebuf |
.invalid: |
dbgstr 'Invalid hub descriptor' |
jmp .freebuf |
endp |
; This procedure is called when the request to power up some port is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_port_powered stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether the operation was successful. |
; If not, say something to the debug board and ssstop the initialization. |
cmp [status], 0 |
jnz .invalid |
; 2. Check whether all ports were powered. |
; If so, go to 4. Otherwise, proceed to 3. |
mov eax, [calldata] |
dec dword [eax+usb_hub.ConfigBuffer+4] |
jz .done |
; 3. Power up the next port and return. |
lea edx, [eax+usb_hub.ConfigBuffer] |
xor ecx, ecx |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, ecx, usb_hub_port_powered, eax, ecx |
.nothing: |
ret |
.done: |
; 4. All ports were powered. |
; The hub requires some delay until power will be stable, the delay value |
; is provided in the hub descriptor; we have copied that value to |
; usb_hub.PowerOnInterval. Note the time and set the corresponding flag |
; for usb_hub_process_deferred. |
mov ecx, [timer_ticks] |
mov [eax+usb_hub.PoweredOnTime], ecx |
or [eax+usb_hub.Actions], HUB_WAIT_POWERED |
jmp .nothing |
.invalid: |
dbgstr 'Error while powering hub ports' |
jmp .nothing |
endp |
; Requests notification about any changes in hub/ports configuration. |
; Called when initial configuration is done and when a previous notification |
; has been processed. |
proc usb_hub_wait_change |
stdcall usb_normal_transfer_async, [eax+usb_hub.StatusPipe], \ |
[eax+usb_hub.StatusChangePtr], [eax+usb_hub.MaxPacketSize], usb_hub_changed, eax, 1 |
ret |
endp |
; This procedure is called when something has changed on the hub. |
proc usb_hub_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; DEBUGF 1,'K : [%d] int pipe for hub %x\n',[timer_ticks],[calldata] |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
xor ecx, ecx |
cmp [status], ecx |
jnz .failed |
; 2. If no data were retrieved, restart waiting. |
mov eax, [calldata] |
cmp [length], ecx |
jz .continue |
; 3. If size of data retrieved is less than maximal, pad with zeroes; |
; this corresponds to 'state of other ports was not changed' |
mov ecx, [eax+usb_hub.NumPorts] |
shr ecx, 3 |
inc ecx |
sub ecx, [length] |
jbe .restart |
push eax edi |
mov edi, [buffer] |
add edi, [length] |
xor eax, eax |
rep stosb |
pop edi eax |
.restart: |
; State of some elements of the hub was changed. |
; Find the first element that was changed, |
; ask the hub about nature of the change, |
; clear the corresponding change, |
; reask the hub about status+change (it is possible that another change |
; occurs between the first ask and clearing the change; we won't see that |
; change, so we need to query the status after clearing the change), |
; continue two previous steps until nothing changes, |
; process all changes which were registered. |
; When all changes for one element will be processed, return to here and look |
; for other changed elements. |
mov edx, [eax+usb_hub.StatusChangePtr] |
; We keep all observed changes in the special var usb_hub.AccStatusChange; |
; it will be logical OR of all observed StatusChange's. |
; 4. No observed changes yet, zero usb_hub.AccStatusChange. |
xor ecx, ecx |
mov [eax+usb_hub.AccStatusChange], cl |
; 5. Test whether there was a change in the hub itself. |
; If so, query hub state. |
btr dword [edx], ecx |
jnc .no_hub_change |
.next_hub_change: |
; DEBUGF 1,'K : [%d] querying status of hub %x\n',[timer_ticks],eax |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
lea ecx, [eax+usb_hub.StatusData] |
mov dword [edx], 0A0h + \ ; class-specific request from hub itself |
(USB_GET_STATUS shl 8) |
mov dword [edx+4], 4 shl 16 ; get 4 bytes |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_status, eax, 0 |
jmp .nothing |
.no_hub_change: |
; 6. Find the first port with changed state and clear the corresponding bit |
; (so next scan after .restart will not consider this port again). |
; If found, go to 8. Otherwise, advance to 7. |
inc ecx |
.test_port_change: |
btr [edx], ecx |
jc .found_port_change |
inc ecx |
cmp ecx, [eax+usb_hub.NumPorts] |
jbe .test_port_change |
.continue: |
; 7. All changes have been processed. Wait for next notification. |
call usb_hub_wait_change |
.nothing: |
ret |
.found_port_change: |
mov dword [eax+usb_hub.ChangeConfigBuffer+4], ecx |
.next_port_change: |
; 8. Query port state. Continue work in usb_hub_port_status callback. |
; movzx ecx, [eax+usb_hub.ChangeConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] querying status of hub %x port %d\n',[timer_ticks],eax,ecx |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
mov dword [edx], 0A3h + \ ; class-specific request from hub port |
(USB_GET_STATUS shl 8) |
mov byte [edx+6], 4 ; data length = 4 bytes |
lea ecx, [eax+usb_hub.StatusData] |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, ecx, 4, usb_hub_port_status, eax, 0 |
jmp .nothing |
.failed: |
cmp [status], USB_STATUS_CLOSED |
jz .nothing |
dbgstr 'Querying hub notification failed' |
jmp .nothing |
endp |
; This procedure is called when the request of hub status is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. Accumulate observed changes. |
mov eax, [calldata] |
mov dl, byte [eax+usb_hub.StatusChange] |
or [eax+usb_hub.AccStatusChange], dl |
.next_change: |
; 3. Find the first change. If found, advance to 4. Otherwise, go to 5. |
mov cl, C_HUB_OVER_CURRENT |
btr dword [eax+usb_hub.StatusChange], 1 |
jc .clear_hub_change |
mov cl, C_HUB_LOCAL_POWER |
btr dword [eax+usb_hub.StatusChange], 0 |
jnc .final |
.clear_hub_change: |
; 4. Clear the change and continue in usb_hub_change_cleared callback. |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
mov dword [edx], 20h + \ ; class-specific request to hub itself |
(USB_CLEAR_FEATURE shl 8) |
mov [edx+2], cl ; feature selector |
and dword [edx+4], 0 |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_change_cleared, eax, 0 |
.nothing: |
ret |
.final: |
; 5. All changes cleared and accumulated, now process them. |
; Note: that needs work. |
DEBUGF 1,'K : hub status %x\n',[eax+usb_hub.AccStatusChange]:2 |
test [eax+usb_hub.AccStatusChange], 1 |
jz .no_local_power |
test [eax+usb_hub.StatusData], 1 |
jz .local_power_lost |
dbgstr 'Hub local power is now good' |
jmp .no_local_power |
.local_power_lost: |
dbgstr 'Hub local power is now lost' |
.no_local_power: |
test [eax+usb_hub.AccStatusChange], 2 |
jz .no_overcurrent |
test [eax+usb_hub.StatusData], 2 |
jz .no_overcurrent |
dbgstr 'Hub global overcurrent' |
.no_overcurrent: |
; 6. Process possible changes for other ports. |
jmp usb_hub_changed.restart |
.failed: |
dbgstr 'Querying hub status failed' |
jmp .nothing |
endp |
; This procedure is called when the request to clear hub change is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_change_cleared stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. If there is a change which was observed, but not yet cleared, |
; go to the code which clears it. |
mov eax, [calldata] |
cmp [eax+usb_hub.StatusChange], 0 |
jnz usb_hub_status.next_change |
; 3. Otherwise, go to the code which queries the status. |
jmp usb_hub_changed.next_hub_change |
.failed: |
dbgstr 'Clearing hub change failed' |
ret |
endp |
; This procedure is called when the request of port status is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. Accumulate observed changes. |
mov eax, [calldata] |
; movzx ecx, [eax+usb_hub.ChangeConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d status %x change %x\n',[timer_ticks],eax,ecx,[eax+usb_hub.StatusData]:4,[eax+usb_hub.StatusChange]:4 |
mov dl, byte [eax+usb_hub.StatusChange] |
or [eax+usb_hub.AccStatusChange], dl |
.next_change: |
; 3. Find the first change. If found, advance to 4. Otherwise, go to 5. |
; Ignore change in reset status; it is cleared by synchronous code |
; (usb_hub_process_deferred), so avoid unnecessary interference. |
; mov cl, C_PORT_RESET |
btr dword [eax+usb_hub.StatusChange], PORT_RESET |
; jc .clear_port_change |
mov cl, C_PORT_OVER_CURRENT |
btr dword [eax+usb_hub.StatusChange], PORT_OVER_CURRENT |
jc .clear_port_change |
mov cl, C_PORT_SUSPEND |
btr dword [eax+usb_hub.StatusChange], PORT_SUSPEND |
jc .clear_port_change |
mov cl, C_PORT_ENABLE |
btr dword [eax+usb_hub.StatusChange], PORT_ENABLE |
jc .clear_port_change |
mov cl, C_PORT_CONNECTION |
btr dword [eax+usb_hub.StatusChange], PORT_CONNECTION |
jnc .final |
.clear_port_change: |
; 4. Clear the change and continue in usb_hub_port_changed callback. |
call usb_hub_clear_port_change |
jmp .nothing |
.final: |
; All changes cleared and accumulated, now process them. |
movzx ecx, byte [eax+usb_hub.ChangeConfigBuffer+4] |
dec ecx |
DEBUGF 1,'K : final: hub %x port %d status %x change %x\n',eax,ecx,[eax+usb_hub.StatusData]:4,[eax+usb_hub.AccStatusChange]:2 |
; 5. Process connect/disconnect events. |
; 5a. Test whether there is such event. |
test byte [eax+usb_hub.AccStatusChange], 1 shl PORT_CONNECTION |
jz .nodisconnect |
; 5b. If there was a connected device, notify the main code about disconnect. |
push ebx |
mov edx, [eax+usb_hub.ConnectedDevicesPtr] |
xor ebx, ebx |
xchg ebx, [edx+ecx*4] |
test ebx, ebx |
jz @f |
push eax ecx |
call usb_device_disconnected |
pop ecx eax |
@@: |
pop ebx |
; 5c. If the disconnect event corresponds to the port which is currently |
; resetting, then another request from synchronous code could be in the fly, |
; so aborting reset immediately would lead to problems with those requests. |
; Thus, just set the corresponding status and let the synchronous code process. |
test byte [eax+usb_hub.Actions], (HUB_RESET_SIGNAL or HUB_RESET_RECOVERY) |
jz @f |
mov edx, [eax+usb_hub.Controller] |
cmp [edx+usb_controller.ResettingPort], cl |
jnz @f |
mov [edx+usb_controller.ResettingStatus], -1 |
@@: |
; 5d. If the current status is 'connected', store the current time as connect |
; time and set the corresponding bit for usb_hub_process_deferred. |
; Otherwise, set connect time to -1. |
; If current time is -1, pretend that the event occured one tick later and |
; store zero. |
mov edx, [eax+usb_hub.ConnectedTimePtr] |
test byte [eax+usb_hub.StatusData], 1 shl PORT_CONNECTION |
jz .disconnected |
or [eax+usb_hub.Actions], HUB_WAIT_CONNECT |
push eax |
call usb_hub_store_connected_time |
pop eax |
jmp @f |
.disconnected: |
or dword [edx+ecx*4], -1 |
@@: |
.nodisconnect: |
; 6. Process port disabling. |
test [eax+usb_hub.AccStatusChange], 1 shl PORT_ENABLE |
jz .nodisable |
test byte [eax+usb_hub.StatusData], 1 shl PORT_ENABLE |
jnz .nodisable |
; Note: that needs work. |
dbgstr 'Port disabled' |
.nodisable: |
; 7. Process port overcurrent. |
test [eax+usb_hub.AccStatusChange], 1 shl PORT_OVER_CURRENT |
jz .noovercurrent |
test byte [eax+usb_hub.StatusData], 1 shl PORT_OVER_CURRENT |
jz .noovercurrent |
; Note: that needs work. |
dbgstr 'Port over-current' |
.noovercurrent: |
; 8. Process possible changes for other ports. |
jmp usb_hub_changed.restart |
.failed: |
dbgstr 'Querying port status failed' |
.nothing: |
ret |
endp |
; Helper procedure to store current time in ConnectedTime, |
; advancing -1 to zero if needed. |
proc usb_hub_store_connected_time |
mov eax, [timer_ticks] |
; transform -1 to 0, leave other values as is |
cmp eax, -1 |
sbb eax, -1 |
mov [edx+ecx*4], eax |
ret |
endp |
; Helper procedure for several parts of hub code. |
; Sends a request to clear the given feature of the port. |
; eax -> usb_hub, cl = feature; |
; as is should be called from async code, sync code should set |
; edx to ConfigBuffer and call usb_hub_clear_port_change.buffer; |
; port number (1-based) should be filled in [edx+4] by previous requests. |
proc usb_hub_clear_port_change |
lea edx, [eax+usb_hub.ChangeConfigBuffer] |
.buffer: |
; push edx |
; movzx edx, byte [edx+4] |
; dec edx |
; DEBUGF 1,'K : [%d] hub %x port %d clear feature %d\n',[timer_ticks],eax,edx,cl |
; pop edx |
mov dword [edx], 23h + \ ; class-specific request to hub port |
(USB_CLEAR_FEATURE shl 8) |
mov byte [edx+2], cl |
and dword [edx+4], 0xFF |
stdcall usb_control_async, [eax+usb_hub.ConfigPipe], edx, edx, 0, usb_hub_port_changed, eax, 0 |
ret |
endp |
; This procedure is called when the request to clear port change is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_port_changed stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. Check whether our request has failed. |
; If so, say something to the debug board and stop processing notifications. |
cmp [status], 0 |
jnz .failed |
; 2. If the request was originated by synchronous code, no further processing |
; is required. |
mov eax, [calldata] |
lea edx, [eax+usb_hub.ConfigBuffer] |
cmp [buffer], edx |
jz .nothing |
; 3. If there is a change which was observed, but not yet cleared, |
; go to the code which clears it. |
cmp [eax+usb_hub.StatusChange], 0 |
jnz usb_hub_port_status.next_change |
; 4. Otherwise, go to the code which queries the status. |
jmp usb_hub_changed.next_port_change |
.failed: |
dbgstr 'Clearing port change failed' |
.nothing: |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; contains synchronous code which should be activated at certain time |
; (e.g. reset a recently connected device after debounce interval 100ms). |
; Returns the number of ticks when it should be called next time. |
proc usb_hub_process_deferred |
; 1. Top-of-stack will contain return value; initialize to infinite timeout. |
push -1 |
; 2. If wait for stable power is active, then |
; either reschedule wakeup (if time is not over) |
; or start processing notifications. |
test byte [esi+usb_hub.Actions], HUB_WAIT_POWERED |
jz .no_powered |
movzx eax, [esi+usb_hub.PowerOnInterval] |
; three following instructions are equivalent to edx = ceil(eax / 5) + 1 |
; 1 extra tick is added to make sure that the interval is at least as needed |
; (it is possible that PoweredOnTime was set just before timer interrupt, and |
; this test goes on just after timer interrupt) |
add eax, 9 |
; two following instructions are equivalent to edx = floor(eax / 5) |
; for any 0 <= eax < 40000000h |
mov ecx, 33333334h |
mul ecx |
mov eax, [timer_ticks] |
sub eax, [esi+usb_hub.PoweredOnTime] |
sub eax, edx |
jge .powered_on |
neg eax |
pop ecx |
push eax |
jmp .no_powered |
.powered_on: |
and [esi+usb_hub.Actions], not HUB_WAIT_POWERED |
mov eax, esi |
call usb_hub_wait_change |
.no_powered: |
; 3. If reset is pending, check whether we can start it and start it, if so. |
test byte [esi+usb_hub.Actions], HUB_RESET_WAITING |
jz .no_wait_reset |
mov eax, [esi+usb_hub.Controller] |
cmp [eax+usb_controller.ResettingPort], -1 |
jnz .no_wait_reset |
call usb_hub_initiate_reset |
.no_wait_reset: |
; 4. If reset signalling is active, wait for end of reset signalling |
; and schedule wakeup in 1 tick. |
test byte [esi+usb_hub.Actions], HUB_RESET_SIGNAL |
jz .no_resetting_port |
; It has no sense to query status several times per tick. |
mov eax, [timer_ticks] |
cmp eax, [esi+usb_hub.ResetTime] |
jz @f |
mov [esi+usb_hub.ResetTime], eax |
movzx ecx, byte [esi+usb_hub.ConfigBuffer+4] |
mov eax, usb_hub_resetting_port_status |
call usb_hub_query_port_status |
@@: |
pop eax |
push 1 |
.no_resetting_port: |
; 5. If reset recovery is active and time is not over, reschedule wakeup. |
test byte [esi+usb_hub.Actions], HUB_RESET_RECOVERY |
jz .no_reset_recovery |
mov eax, [timer_ticks] |
sub eax, [esi+usb_hub.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_done |
neg eax |
cmp [esp], eax |
jb @f |
mov [esp], eax |
@@: |
jmp .no_reset_recovery |
.reset_done: |
; 6. If reset recovery is active and time is over, clear 'reset recovery' flag, |
; notify other code about a new device and let it do further steps. |
; If that fails, stop reset process for this port and disable that port. |
and [esi+usb_hub.Actions], not HUB_RESET_RECOVERY |
; Bits 9-10 of port status encode port speed. |
; If PORT_LOW_SPEED is set, the device is low-speed. Otherwise, |
; PORT_HIGH_SPEED bit distinguishes full-speed and high-speed devices. |
; This corresponds to values of USB_SPEED_FS=0, USB_SPEED_LS=1, USB_SPEED_HS=2. |
mov eax, dword [esi+usb_hub.ResetStatusData] |
shr eax, PORT_LOW_SPEED |
and eax, 3 |
test al, 1 |
jz @f |
mov al, 1 |
@@: |
; movzx ecx, [esi+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d speed %d\n',[timer_ticks],esi,ecx,eax |
push esi |
mov esi, [esi+usb_hub.Controller] |
cmp [esi+usb_controller.ResettingStatus], -1 |
jz .disconnected_while_reset |
mov edx, [esi+usb_controller.HardwareFunc] |
call [edx+usb_hardware_func.NewDevice] |
pop esi |
test eax, eax |
jnz .no_reset_recovery |
mov eax, esi |
call usb_hub_disable_resetting_port |
jmp .no_reset_recovery |
.disconnected_while_reset: |
pop esi |
mov eax, esi |
call usb_hub_reset_aborted |
.no_reset_recovery: |
; 7. Handle recent connection events. |
; Note: that should be done after step 6, because step 6 can clear |
; HUB_RESET_IN_PROGRESS flag. |
; 7a. Test whether there is such an event pending. If no, skip this step. |
test byte [esi+usb_hub.Actions], HUB_WAIT_CONNECT |
jz .no_wait_connect |
; 7b. If we have started reset process for another port in the same hub, |
; skip this step: the buffer for config requests can be used for that port. |
test byte [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS |
jnz .no_wait_connect |
; 7c. Clear flag 'there are connection events which should be processed'. |
; If there are another connection events, this flag will be set again. |
and [esi+usb_hub.Actions], not HUB_WAIT_CONNECT |
; 7d. Prepare for loop over all ports. |
xor ecx, ecx |
.test_wait_connect: |
; 7e. For every port test for recent connection event. |
; If none, continue the loop for the next port. |
mov edx, [esi+usb_hub.ConnectedTimePtr] |
mov eax, [edx+ecx*4] |
cmp eax, -1 |
jz .next_wait_connect |
or [esi+usb_hub.Actions], HUB_WAIT_CONNECT |
; 7f. Test whether initial delay is over. |
sub eax, [timer_ticks] |
neg eax |
sub eax, USB_CONNECT_DELAY |
jge .connect_delay_over |
; 7g. The initial delay is not over; |
; set the corresponding flag again, reschedule wakeup and continue the loop. |
neg eax |
cmp [esp], eax |
jb @f |
mov [esp], eax |
@@: |
jmp .next_wait_connect |
.connect_delay_over: |
; The initial delay is over. |
; It is possible that there was disconnect event during that delay, probably |
; with connect event after that. If so, we should restart the waiting. However, |
; on the hardware level connect/disconnect events from hubs are implemented |
; using polling with interval selected by the hub, so it is possible that |
; we have not yet observed that disconnect event. |
; Thus, we query port status+change data before all further processing. |
; 7h. Send the request for status+change data. |
push ecx |
; Hub requests expect 1-based port number, not zero-based we operate with. |
inc ecx |
mov eax, usb_hub_connect_port_status |
call usb_hub_query_port_status |
pop ecx |
; 3i. If request has been submitted successfully, set the flag |
; 'reset in progress, config buffer is owned by reset process' and break |
; from the loop. |
test eax, eax |
jz .next_wait_connect |
or [esi+usb_hub.Actions], HUB_RESET_IN_PROGRESS |
jmp .no_wait_connect |
.next_wait_connect: |
; 7j. Continue the loop for next port. |
inc ecx |
cmp ecx, [esi+usb_hub.NumPorts] |
jb .test_wait_connect |
.no_wait_connect: |
; 8. Pop return value from top-of-stack and return. |
pop eax |
ret |
endp |
; Helper procedure for other code. Called when reset process is aborted. |
proc usb_hub_reset_aborted |
; Clear 'reset in progress' flag and test for other devices which could be |
; waiting for reset. |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
push esi |
mov esi, [eax+usb_hub.Controller] |
call usb_test_pending_port |
pop esi |
ret |
endp |
; Helper procedure for usb_hub_process_deferred. |
; Sends a request to query port status. |
; esi -> usb_hub, eax = callback, ecx = 1-based port. |
proc usb_hub_query_port_status |
; dec ecx |
; DEBUGF 1,'K : [%d] [main] hub %x port %d query status\n',[timer_ticks],esi,ecx |
; inc ecx |
add ecx, 4 shl 16 ; data length = 4 |
lea edx, [esi+usb_hub.ConfigBuffer] |
mov dword [edx], 0A3h + \ ; class-specific request from hub port |
(USB_GET_STATUS shl 8) |
mov dword [edx+4], ecx |
lea ecx, [esi+usb_hub.ResetStatusData] |
stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, ecx, 4, eax, esi, 0 |
ret |
endp |
; This procedure is called when the request to query port status |
; initiated by usb_hub_process_deferred for testing connection is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_connect_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
push esi ; save used register to be stdcall |
mov eax, [calldata] |
mov esi, [pipe] |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] [connect test] hub %x port %d status %x change %x\n',[timer_ticks],eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4 |
; 1. In any case, clear 'reset in progress' flag. |
; If everything is ok, it would be set again. |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
; 2. If the request has failed, stop reset process. |
cmp [status], 0 |
jnz .nothing |
mov edx, [eax+usb_hub.ConnectedTimePtr] |
movzx ecx, byte [eax+usb_hub.ConfigBuffer+4] |
dec ecx |
; 3. Test whether there was a disconnect event. |
test byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION |
jz .reset |
; 4. There was a disconnect event. |
; There is another handler of connect/disconnect events, usb_hub_port_status. |
; However, we do not know whether it has already processed this event |
; or it will process it sometime later. |
; If ConnectedTime is -1, then another handler has already run, |
; there was no connection event, so just leave the value as -1. |
; Otherwise, there are two possibilities: either another handler has not yet |
; run (which is quite likely), or there was a connection event and the other |
; handler has run exactly while our request was processed (otherwise our |
; request would not been submitted; this is quite unlikely due to timing |
; requirements, but not impossible). In this case, set ConnectedTime to the |
; current time: in the likely case it prevents usb_hub_process_deferred from immediate |
; issuing of another requests (which would be just waste of time); |
; in the unlikely case it is still correct (although slightly increases |
; the debounce interval). |
cmp dword [edx+ecx*4], -1 |
jz .nothing |
call usb_hub_store_connected_time |
jmp .nothing |
.reset: |
; 5. The device remained connected for the entire debounce interval; |
; we can proceed with initialization. |
; Clear connected time for this port and notify usb_hub_process_deferred that |
; the new port is waiting for reset. |
or dword [edx+ecx*4], -1 |
or [eax+usb_hub.Actions], HUB_RESET_IN_PROGRESS + HUB_RESET_WAITING |
.nothing: |
pop esi ; restore used register to be stdcall |
ret |
endp |
; This procedure is called when the request to query port status |
; initiated by usb_hub_process_deferred for testing reset status is completed, |
; either successfully or unsuccessfully. |
proc usb_hub_resetting_port_status stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; 1. If the request has failed, do nothing. |
cmp [status], 0 |
jnz .nothing |
; 2. If reset signalling is still active, do nothing. |
mov eax, [calldata] |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : hub %x port %d ResetStatusData = %x change = %x\n',eax,ecx,[eax+usb_hub.ResetStatusData]:4,[eax+usb_hub.ResetStatusChange]:4 |
test byte [eax+usb_hub.ResetStatusData], 1 shl PORT_RESET |
jnz .nothing |
; 3. Store the current time to start reset recovery interval |
; and clear 'reset signalling active' flag. |
mov edx, [timer_ticks] |
mov [eax+usb_hub.ResetTime], edx |
and [eax+usb_hub.Actions], not HUB_RESET_SIGNAL |
; 4. If the device has not been disconnected, set 'reset recovery active' bit. |
; Otherwise, terminate reset process. |
test byte [eax+usb_hub.ResetStatusChange], 1 shl PORT_CONNECTION |
jnz .disconnected |
or [eax+usb_hub.Actions], HUB_RESET_RECOVERY |
.common: |
; In any case, clear change of resetting status. |
lea edx, [eax+usb_hub.ConfigBuffer] |
mov cl, C_PORT_RESET |
call usb_hub_clear_port_change.buffer |
.nothing: |
ret |
.disconnected: |
call usb_hub_reset_aborted |
jmp .common |
endp |
; Helper procedure for usb_hub_process_deferred. Initiates reset signalling |
; on the current port (given by 1-based value [ConfigBuffer+4]). |
; esi -> usb_hub, eax -> usb_controller |
proc usb_hub_initiate_reset |
; 1. Store hub+port data in the controller structure. |
movzx ecx, [esi+usb_hub.ConfigBuffer+4] |
dec ecx |
mov [eax+usb_controller.ResettingPort], cl |
mov [eax+usb_controller.ResettingHub], esi |
; 2. Store the current time and set 'reset signalling active' flag. |
mov eax, [timer_ticks] |
mov [esi+usb_hub.ResetTime], eax |
and [esi+usb_hub.Actions], not HUB_RESET_WAITING |
or [esi+usb_hub.Actions], HUB_RESET_SIGNAL |
; 3. Send request to the hub to initiate request signalling. |
lea edx, [esi+usb_hub.ConfigBuffer] |
; DEBUGF 1,'K : [%d] hub %x port %d initiate reset\n',[timer_ticks],esi,ecx |
mov dword [edx], 23h + \ |
(USB_SET_FEATURE shl 8) + \ |
(PORT_RESET shl 16) |
and dword [edx+4], 0xFF |
stdcall usb_control_async, [esi+usb_hub.ConfigPipe], edx, 0, 0, usb_hub_reset_started, esi, 0 |
test eax, eax |
jnz @f |
mov eax, esi |
call usb_hub_reset_aborted |
@@: |
ret |
endp |
; This procedure is called when the request to start reset signalling initiated |
; by usb_hub_initiate_reset is completed, either successfully or unsuccessfully. |
proc usb_hub_reset_started stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; If the request is successful, do nothing. |
; Otherwise, clear 'reset signalling' flag and abort reset process. |
mov eax, [calldata] |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d reset started\n',[timer_ticks],eax,ecx |
cmp [status], 0 |
jz .nothing |
and [eax+usb_hub.Actions], not HUB_RESET_SIGNAL |
dbgstr 'Failed to reset hub port' |
call usb_hub_reset_aborted |
.nothing: |
ret |
endp |
; This procedure is called by the protocol layer if something has failed during |
; initial stages of the configuration process, so the device should be disabled |
; at hub level. |
proc usb_hub_disable_resetting_port |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
; movzx ecx, [eax+usb_hub.ConfigBuffer+4] |
; dec ecx |
; DEBUGF 1,'K : [%d] hub %x port %d disable\n',[timer_ticks],eax,ecx |
lea edx, [eax+usb_hub.ConfigBuffer] |
mov cl, PORT_ENABLE |
jmp usb_hub_clear_port_change.buffer |
endp |
; This procedure is called when the hub is disconnected. |
proc usb_hub_disconnect |
virtual at esp |
dd ? ; return address |
.hubdata dd ? |
end virtual |
; 1. If the hub is disconnected during initial configuration, |
; 1 is stored as hub data and there is nothing to do. |
mov eax, [.hubdata] |
cmp eax, 1 |
jz .nothing |
; 2. Remove the hub from the overall list. |
mov ecx, [eax+usb_hub.Next] |
mov edx, [eax+usb_hub.Prev] |
mov [ecx+usb_hub.Prev], edx |
mov [edx+usb_hub.Next], ecx |
; 3. If some child is in reset process, abort reset. |
push esi |
mov esi, [eax+usb_hub.Controller] |
cmp [esi+usb_controller.ResettingHub], eax |
jnz @f |
cmp [esi+usb_controller.ResettingPort], -1 |
jz @f |
push eax |
call usb_test_pending_port |
pop eax |
@@: |
pop esi |
; 4. Loop over all children and notify other code that they were disconnected. |
push ebx |
xor ecx, ecx |
.disconnect_children: |
mov ebx, [eax+usb_hub.ConnectedDevicesPtr] |
mov ebx, [ebx+ecx*4] |
test ebx, ebx |
jz @f |
push eax ecx |
call usb_device_disconnected |
pop ecx eax |
@@: |
inc ecx |
cmp ecx, [eax+usb_hub.NumPorts] |
jb .disconnect_children |
; 4. Free memory allocated for the hub data. |
call free |
pop ebx |
.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 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/usb/init.inc |
---|
0,0 → 1,268 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
; Initialization of the USB subsystem. |
; Provides usb_init procedure, includes all needed files. |
; General notes: |
; * There is one entry point for external kernel code: usb_init is called |
; from initialization code and initializes USB subsystem. |
; * There are several entry points for API; see the docs for description. |
; * There are several functions which are called from controller-specific |
; parts of USB subsystem. The most important is usb_new_device, |
; which is called when a new device has been connected (over some time), |
; has been reset and is ready to start configuring. |
; * IRQ handlers are very restricted. They can not take any locks, |
; since otherwise a deadlock is possible: imagine that a code has taken the |
; lock and was interrupted by IRQ handler. Now IRQ handler would wait for |
; releasing the lock, and a lock owner would wait for exiting IRQ handler |
; to get the control. |
; * Thus, there is the special USB thread which processes almost all activity. |
; IRQ handlers do the minimal processing and wake this thread. |
; * Also the USB thread wakes occasionally to process tasks which can be |
; predicted without interrupts. These include e.g. a periodic roothub |
; scanning in UHCI and initializing in USB_CONNECT_DELAY ticks |
; after connecting a new device. |
; * The main procedure of USB thread, usb_thread_proc, does all its work |
; by querying usb_hardware_func.ProcessDeferred for every controller |
; and usb_hub_process_deferred for every hub. |
; ProcessDeferred does controller-specific actions and calculates the time |
; when it should be invoked again, possibly infinite. |
; usb_thread_proc selects the minimum from all times returned by |
; 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 |
; 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, |
; otherwise BIOS could see a device moving from EHCI to a companion; |
; first, this always wastes time; |
; second, some BIOSes are buggy, do not expect that move and try to refer to |
; previously-assigned controller instead of actual; sometimes that leads to |
; hangoff. |
; Thus, process controllers in PCI order. |
mov esi, pcidev_list |
push 0 |
.kickoff: |
mov esi, [esi+PCIDEV.fd] |
cmp esi, pcidev_list |
jz .done_kickoff |
cmp word [esi+PCIDEV.class+1], 0x0C03 |
jnz .kickoff |
mov ebx, uhci_service_name |
cmp byte [esi+PCIDEV.class], 0x00 |
jz .do_kickoff |
mov ebx, ohci_service_name |
cmp byte [esi+PCIDEV.class], 0x10 |
jz .do_kickoff |
mov ebx, ehci_service_name |
cmp byte [esi+PCIDEV.class], 0x20 |
jnz .kickoff |
.do_kickoff: |
inc dword [esp] |
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 |
; 3. If no controllers were found, exit. |
; Otherwise, run the USB thread. |
test eax, eax |
jz .nothing |
call create_usb_thread |
jz .nothing |
; 4. Initialize all USB controllers, calling usb_init_controller for each. |
; Note: USB1 companion(s) should go before the corresponding EHCI controller, |
; although this is not strictly necessary (this way, a companion would not try |
; to initialize high-speed device only to see a disconnect when EHCI takes |
; control). |
; Thus, process all EHCI controllers in the first loop, all USB1 controllers |
; in the second loop. (One loop in reversed PCI order could also be used, |
; but seems less natural.) |
; 4a. Loop over all PCI devices, call usb_init_controller |
; for all EHCI controllers. |
mov eax, pcidev_list |
.scan_ehci: |
mov eax, [eax+PCIDEV.fd] |
cmp eax, pcidev_list |
jz .done_ehci |
cmp [eax+PCIDEV.class], 0x0C0320 |
jnz .scan_ehci |
call usb_init_controller |
jmp .scan_ehci |
.done_ehci: |
; 4b. Loop over all PCI devices, call usb_init_controller |
; for all UHCI and OHCI controllers. |
mov eax, pcidev_list |
.scan_usb1: |
mov eax, [eax+PCIDEV.fd] |
cmp eax, pcidev_list |
jz .done_usb1 |
cmp [eax+PCIDEV.class], 0x0C0300 |
jz @f |
cmp [eax+PCIDEV.class], 0x0C0310 |
jnz .scan_usb1 |
@@: |
call usb_init_controller |
jmp .scan_usb1 |
.done_usb1: |
.nothing: |
ret |
endp |
uglobal |
align 4 |
usb_event dd ? |
endg |
; Helper function for usb_init. Creates and initializes the USB thread. |
proc create_usb_thread |
; 1. Create the thread. |
push edi |
movi ebx, 1 |
mov ecx, usb_thread_proc |
xor edx, edx |
call new_sys_threads |
pop edi |
; If failed, say something to the debug board and return with ZF set. |
test eax, eax |
jns @f |
DEBUGF 1,'K : cannot create kernel thread for USB, error %d\n',eax |
.clear: |
xor eax, eax |
jmp .nothing |
@@: |
; 2. Wait while the USB thread initializes itself. |
@@: |
call change_task |
cmp [usb_event], 0 |
jz @b |
; 3. If initialization failed, the USB thread sets [usb_event] to -1. |
; Return with ZF set or cleared corresponding to the result. |
cmp [usb_event], -1 |
jz .clear |
.nothing: |
ret |
endp |
; Helper function for IRQ handlers. Wakes the USB thread if ebx is nonzero. |
proc usb_wakeup_if_needed |
test ebx, ebx |
jz usb_wakeup.nothing |
usb_wakeup: |
xor edx, edx |
mov eax, [usb_event] |
mov ebx, [eax+EVENT.id] |
xor esi, esi |
call raise_event |
.nothing: |
ret |
endp |
; Main loop of the USB thread. |
proc usb_thread_proc |
; 1. Initialize: create event to allow wakeup by interrupt handlers. |
xor esi, esi |
mov ecx, MANUAL_DESTROY |
call create_event |
test eax, eax |
jnz @f |
; If failed, set [usb_event] to -1 and terminate myself. |
dbgstr 'cannot create event for USB thread' |
or [usb_event], -1 |
jmp sys_end |
@@: |
mov [usb_event], eax |
push -1 ; initial timeout: infinite |
usb_thread_wait: |
; 2. Main loop: wait for either wakeup event or timeout. |
pop ecx ; get timeout |
mov eax, [usb_event] |
mov ebx, [eax+EVENT.id] |
call wait_event_timeout |
push -1 ; default timeout: infinite |
; 3. Main loop: call worker functions of all controllers; |
; if some function schedules wakeup in timeout less than the current value, |
; replace that value with the returned timeout. |
mov esi, usb_controllers_list |
@@: |
mov esi, [esi+usb_controller.Next] |
cmp esi, usb_controllers_list |
jz .controllers_done |
mov eax, [esi+usb_controller.HardwareFunc] |
call [eax+usb_hardware_func.ProcessDeferred] |
cmp [esp], eax |
jb @b |
mov [esp], eax |
jmp @b |
.controllers_done: |
; 4. Main loop: call hub worker function for all hubs, |
; similarly calculating minimum of all returned timeouts. |
; When done, continue to 2. |
mov esi, usb_hubs_list |
@@: |
mov esi, [esi+usb_hub.Next] |
cmp esi, usb_hubs_list |
jz usb_thread_wait |
call usb_hub_process_deferred |
cmp [esp], eax |
jb @b |
mov [esp], eax |
jmp @b |
endp |
iglobal |
align 4 |
usb_controllers_list: |
dd usb_controllers_list |
dd usb_controllers_list |
usb_hubs_list: |
dd usb_hubs_list |
dd usb_hubs_list |
endg |
uglobal |
align 4 |
usb_controllers_list_mutex MUTEX |
endg |
include "memory.inc" |
include "common.inc" |
include "hccommon.inc" |
include "pipe.inc" |
include "protocol.inc" |
include "hub.inc" |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/usb/pipe.inc |
---|
0,0 → 1,860 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
; Functions for USB pipe manipulation: opening/closing, sending data etc. |
; |
USB_STDCALL_VERIFY = 1 |
macro stdcall_verify [arg] |
{ |
common |
if USB_STDCALL_VERIFY |
pushad |
stdcall arg |
call verify_regs |
popad |
else |
stdcall arg |
end if |
} |
if USB_STDCALL_VERIFY |
STDCALL_VERIFY_EXTRA = 20h |
else |
STDCALL_VERIFY_EXTRA = 0 |
end if |
; Initialization of usb_static_ep structure, |
; called from controller-specific initialization; edi -> usb_static_ep |
proc usb_init_static_endpoint |
mov [edi+usb_static_ep.NextVirt], edi |
mov [edi+usb_static_ep.PrevVirt], edi |
ret |
endp |
; Part of API for drivers, see documentation for USBOpenPipe. |
proc usb_open_pipe stdcall uses ebx esi edi,\ |
config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword |
locals |
tt_vars rd 24 ; should be enough for ehci_select_tt_interrupt_list |
targetsmask dd ? ; S-Mask for USB2 |
bandwidth dd ? |
target dd ? |
endl |
; 1. Verify type of pipe: it must be one of *_PIPE constants. |
; Isochronous pipes are not supported yet. |
mov eax, [type] |
cmp eax, INTERRUPT_PIPE |
ja .badtype |
cmp al, ISOCHRONOUS_PIPE |
jnz .goodtype |
.badtype: |
dbgstr 'unsupported type of USB pipe' |
jmp .return0 |
.goodtype: |
; 2. Allocate memory for pipe and transfer queue. |
; Empty transfer queue consists of one inactive TD. |
mov ebx, [config_pipe] |
mov esi, [ebx+usb_pipe.Controller] |
mov edx, [esi+usb_controller.HardwareFunc] |
call [edx+usb_hardware_func.AllocPipe] |
test eax, eax |
jz .nothing |
mov edi, eax |
mov edx, [esi+usb_controller.HardwareFunc] |
call [edx+usb_hardware_func.AllocTD] |
test eax, eax |
jz .free_and_return0 |
; 3. Initialize transfer queue: pointer to transfer descriptor, |
; pointers in transfer descriptor, queue lock. |
mov [edi+usb_pipe.LastTD], eax |
mov [eax+usb_gtd.NextVirt], eax |
mov [eax+usb_gtd.PrevVirt], eax |
mov [eax+usb_gtd.Pipe], edi |
lea ecx, [edi+usb_pipe.Lock] |
call mutex_init |
; 4. Initialize software part of pipe structure, except device-related fields. |
mov al, byte [type] |
mov [edi+usb_pipe.Type], al |
xor eax, eax |
mov [edi+usb_pipe.Flags], al |
mov [edi+usb_pipe.DeviceData], eax |
mov [edi+usb_pipe.Controller], esi |
or [edi+usb_pipe.NextWait], -1 |
; 5. Initialize device-related fields: |
; for zero endpoint, set .NextSibling = .PrevSibling = this; |
; for other endpoins, copy device data, take the lock guarding pipe list |
; for the device and verify that disconnect processing has not yet started |
; for the device. (Since disconnect processing also takes that lock, |
; either it has completed or it will not start until we release the lock.) |
; Note: usb_device_disconnected should not see the new pipe until |
; initialization is complete, so that lock will be held during next steps |
; (disconnect processing should either not see it at all, or see fully |
; initialized pipe). |
cmp [endpoint], eax |
jz .zero_endpoint |
mov ecx, [ebx+usb_pipe.DeviceData] |
mov [edi+usb_pipe.DeviceData], ecx |
call mutex_lock |
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
jz .common |
.fail: |
; If disconnect processing has completed, unlock the mutex, free memory |
; allocated in step 2 and return zero. |
call mutex_unlock |
mov edx, [esi+usb_controller.HardwareFunc] |
stdcall [edx+usb_hardware_func.FreeTD], [edi+usb_pipe.LastTD] |
.free_and_return0: |
mov edx, [esi+usb_controller.HardwareFunc] |
stdcall [edx+usb_hardware_func.FreePipe], edi |
.return0: |
xor eax, eax |
jmp .nothing |
.zero_endpoint: |
mov [edi+usb_pipe.NextSibling], edi |
mov [edi+usb_pipe.PrevSibling], edi |
.common: |
; 6. Initialize hardware part of pipe structure. |
; 6a. Acquire the corresponding mutex. |
lea ecx, [esi+usb_controller.ControlLock] |
cmp [type], BULK_PIPE |
jb @f ; control pipe |
lea ecx, [esi+usb_controller.BulkLock] |
jz @f ; bulk pipe |
lea ecx, [esi+usb_controller.PeriodicLock] |
@@: |
call mutex_lock |
; 6b. Let the controller-specific code do its job. |
push ecx |
mov edx, [esi+usb_controller.HardwareFunc] |
mov eax, [edi+usb_pipe.LastTD] |
mov ecx, [config_pipe] |
call [edx+usb_hardware_func.InitPipe] |
pop ecx |
; 6c. Release the mutex. |
push eax |
call mutex_unlock |
pop eax |
; 6d. If controller-specific code indicates failure, |
; release the lock taken in step 5, free memory allocated in step 2 |
; and return zero. |
test eax, eax |
jz .fail |
; 7. The pipe is initialized. If this is not the first pipe for the device, |
; insert it to the tail of pipe list for the device, |
; increment number of pipes, |
; release the lock taken at step 5. |
mov ecx, [edi+usb_pipe.DeviceData] |
test ecx, ecx |
jz @f |
mov eax, [ebx+usb_pipe.PrevSibling] |
mov [edi+usb_pipe.NextSibling], ebx |
mov [edi+usb_pipe.PrevSibling], eax |
mov [ebx+usb_pipe.PrevSibling], edi |
mov [eax+usb_pipe.NextSibling], edi |
inc [ecx+usb_device_data.NumPipes] |
call mutex_unlock |
@@: |
; 8. Return pointer to usb_pipe. |
mov eax, edi |
.nothing: |
ret |
endp |
; This procedure is called several times during initial device configuration, |
; when usb_device_data structure is reallocated. |
; It (re)initializes all pointers in usb_device_data. |
; ebx -> usb_pipe |
proc usb_reinit_pipe_list |
push eax |
; 1. (Re)initialize the lock guarding pipe list. |
mov ecx, [ebx+usb_pipe.DeviceData] |
call mutex_init |
; 2. Initialize list of opened pipes: two entries, the head and ebx. |
add ecx, usb_device_data.OpenedPipeList - usb_pipe.NextSibling |
mov [ecx+usb_pipe.NextSibling], ebx |
mov [ecx+usb_pipe.PrevSibling], ebx |
mov [ebx+usb_pipe.NextSibling], ecx |
mov [ebx+usb_pipe.PrevSibling], ecx |
; 3. Initialize list of closed pipes: empty list, only the head is present. |
add ecx, usb_device_data.ClosedPipeList - usb_device_data.OpenedPipeList |
mov [ecx+usb_pipe.NextSibling], ecx |
mov [ecx+usb_pipe.PrevSibling], ecx |
pop eax |
ret |
endp |
; Part of API for drivers, see documentation for USBClosePipe. |
proc usb_close_pipe |
push ebx esi ; save used registers to be stdcall |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.pipe dd ? |
end virtual |
; 1. Lock the pipe list for the device. |
mov ebx, [.pipe] |
mov esi, [ebx+usb_pipe.Controller] |
mov ecx, [ebx+usb_pipe.DeviceData] |
call mutex_lock |
; 2. Set the flag "the driver has abandoned this pipe, free it at any time". |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
or [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE |
call mutex_unlock |
; 3. Call the worker function. |
call usb_close_pipe_nolock |
; 4. Unlock the pipe list for the device. |
mov ecx, [ebx+usb_pipe.DeviceData] |
call mutex_unlock |
; 5. Wakeup the USB thread so that it can proceed with releasing that pipe. |
push edi |
call usb_wakeup |
pop edi |
; 6. Return. |
pop esi ebx ; restore used registers to be stdcall |
retn 4 |
endp |
; Worker function for pipe closing. Called by usb_close_pipe API and |
; from disconnect processing. |
; The lock guarding pipe list for the device should be held by the caller. |
; ebx -> usb_pipe, esi -> usb_controller |
proc usb_close_pipe_nolock |
; 1. Set the flag "pipe is closed, ignore new transfers". |
; If it was already set, do nothing. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
bts dword [ebx+usb_pipe.Flags], USB_FLAG_CLOSED_BIT |
jc .closed |
call mutex_unlock |
; 2. Remove the pipe from the list of opened pipes. |
mov eax, [ebx+usb_pipe.NextSibling] |
mov edx, [ebx+usb_pipe.PrevSibling] |
mov [eax+usb_pipe.PrevSibling], edx |
mov [edx+usb_pipe.NextSibling], eax |
; 3. Unlink the pipe from hardware structures. |
; 3a. Acquire the corresponding lock. |
lea edx, [esi+usb_controller.WaitPipeListAsync] |
lea ecx, [esi+usb_controller.ControlLock] |
cmp [ebx+usb_pipe.Type], BULK_PIPE |
jb @f ; control pipe |
lea ecx, [esi+usb_controller.BulkLock] |
jz @f ; bulk pipe |
add edx, usb_controller.WaitPipeListPeriodic - usb_controller.WaitPipeListAsync |
lea ecx, [esi+usb_controller.PeriodicLock] |
@@: |
push edx |
call mutex_lock |
push ecx |
; 3b. Let the controller-specific code do its job. |
test [ebx+usb_pipe.Flags], USB_FLAG_DISABLED |
jnz @f |
mov eax, [esi+usb_controller.HardwareFunc] |
call [eax+usb_hardware_func.DisablePipe] |
@@: |
mov eax, [esi+usb_controller.HardwareFunc] |
call [eax+usb_hardware_func.UnlinkPipe] |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
; 3c. Release the corresponding lock. |
pop ecx |
call mutex_unlock |
; 4. Put the pipe into wait queue. |
pop edx |
cmp [ebx+usb_pipe.NextWait], -1 |
jz .insert_new |
or [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT |
jmp .inserted |
.insert_new: |
mov eax, [edx] |
mov [ebx+usb_pipe.NextWait], eax |
mov [edx], ebx |
.inserted: |
; 5. Return. |
ret |
.closed: |
call mutex_unlock |
xor eax, eax |
ret |
endp |
; This procedure is called when all transfers are aborted |
; either due to call to usb_abort_pipe or due to pipe closing. |
; It notifies all callbacks and frees all transfer descriptors. |
; ebx -> usb_pipe, esi -> usb_controller, edi -> usb_hardware_func |
; three stack parameters: status code for callback functions |
; and descriptors where to start and stop. |
proc usb_pipe_aborted |
virtual at esp |
dd ? ; return address |
.status dd ? ; USB_STATUS_CLOSED or USB_STATUS_CANCELLED |
.first_td dd ? |
.last_td dd ? |
end virtual |
; Loop over all transfers, calling the driver with the given status |
; and freeing all descriptors except the last one. |
.loop: |
mov edx, [.first_td] |
cmp edx, [.last_td] |
jz .done |
mov ecx, [edx+usb_gtd.Callback] |
test ecx, ecx |
jz .no_callback |
stdcall_verify ecx, ebx, [.status+12+STDCALL_VERIFY_EXTRA], \ |
[edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData] |
mov edx, [.first_td] |
.no_callback: |
mov eax, [edx+usb_gtd.NextVirt] |
mov [.first_td], eax |
stdcall [edi+usb_hardware_func.FreeTD], edx |
jmp .loop |
.done: |
ret 12 |
endp |
; This procedure is called when a pipe with USB_FLAG_CLOSED is removed from the |
; corresponding wait list. It means that the hardware has fully forgot about it. |
; ebx -> usb_pipe, esi -> usb_controller |
proc usb_pipe_closed |
push edi |
mov edi, [esi+usb_controller.HardwareFunc] |
; 1. Notify all registered callbacks with status USB_STATUS_CLOSED, if any, |
; and free all transfer descriptors, including the last one. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
mov edx, [ebx+usb_pipe.LastTD] |
test edx, edx |
jz .no_transfer |
mov eax, [edx+usb_gtd.NextVirt] |
push edx |
push eax |
call mutex_unlock |
push USB_STATUS_CLOSED |
call usb_pipe_aborted |
; It is safe to free LastTD here: |
; usb_*_transfer_async do not enqueue new transfers if USB_FLAG_CLOSED is set. |
stdcall [edi+usb_hardware_func.FreeTD], [ebx+usb_pipe.LastTD] |
jmp @f |
.no_transfer: |
call mutex_unlock |
@@: |
; 2. Decrement number of pipes for the device. |
; If this pipe is the last pipe, go to 5. |
mov ecx, [ebx+usb_pipe.DeviceData] |
call mutex_lock |
dec [ecx+usb_device_data.NumPipes] |
jz .last_pipe |
call mutex_unlock |
; 3. If the flag "the driver has abandoned this pipe" is set, |
; free memory and return. |
test [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE |
jz .nofree |
stdcall [edi+usb_hardware_func.FreePipe], ebx |
pop edi |
ret |
; 4. Otherwise, add it to the list of closed pipes and return. |
.nofree: |
add ecx, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
mov edx, [ecx+usb_pipe.PrevSibling] |
mov [ebx+usb_pipe.NextSibling], ecx |
mov [ebx+usb_pipe.PrevSibling], edx |
mov [ecx+usb_pipe.PrevSibling], ebx |
mov [edx+usb_pipe.NextSibling], ebx |
pop edi |
ret |
.last_pipe: |
; That was the last pipe for the device. |
; 5. Notify device driver(s) about disconnect. |
call mutex_unlock |
mov eax, [ecx+usb_device_data.NumInterfaces] |
test eax, eax |
jz .notify_done |
add ecx, [ecx+usb_device_data.Interfaces] |
.notify_loop: |
mov edx, [ecx+usb_interface_data.DriverFunc] |
test edx, edx |
jz @f |
mov edx, [edx+USBSRV.usb_func] |
cmp [edx+USBFUNC.strucsize], USBFUNC.device_disconnect + 4 |
jb @f |
mov edx, [edx+USBFUNC.device_disconnect] |
test edx, edx |
jz @f |
push eax ecx |
stdcall_verify edx, [ecx+usb_interface_data.DriverData] |
pop ecx eax |
@@: |
add ecx, sizeof.usb_interface_data |
dec eax |
jnz .notify_loop |
.notify_done: |
; 6. Kill the timer, if active. |
; (Usually not; possible if device is disconnected |
; while processing SET_ADDRESS request). |
mov eax, [ebx+usb_pipe.DeviceData] |
cmp [eax+usb_device_data.Timer], 0 |
jz @f |
stdcall cancel_timer_hs, [eax+usb_device_data.Timer] |
mov [eax+usb_device_data.Timer], 0 |
@@: |
; 7. Bus address, if assigned, can now be reused. |
call [edi+usb_hardware_func.GetDeviceAddress] |
test eax, eax |
jz @f |
bts [esi+usb_controller.ExistingAddresses], eax |
@@: |
dbgstr 'USB device disconnected' |
; 8. All drivers have returned from disconnect callback, |
; so all drivers should not use any device-related pipes. |
; Free the remaining pipes. |
mov eax, [ebx+usb_pipe.DeviceData] |
add eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
push eax |
mov eax, [eax+usb_pipe.NextSibling] |
.free_loop: |
cmp eax, [esp] |
jz .free_done |
push [eax+usb_pipe.NextSibling] |
stdcall [edi+usb_hardware_func.FreePipe], eax |
pop eax |
jmp .free_loop |
.free_done: |
stdcall [edi+usb_hardware_func.FreePipe], ebx |
pop eax |
; 9. Free the usb_device_data structure. |
sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
call free |
; 10. Return. |
.nothing: |
pop edi |
ret |
endp |
; This procedure is called when a pipe with USB_FLAG_DISABLED is removed from the |
; corresponding wait list. It means that the hardware has fully forgot about it. |
; ebx -> usb_pipe, esi -> usb_controller |
proc usb_pipe_disabled |
push edi |
mov edi, [esi+usb_controller.HardwareFunc] |
; 1. Acquire pipe lock. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
; 2. Clear USB_FLAG_DISABLED in pipe state. |
and [ebx+usb_pipe.Flags], not USB_FLAG_DISABLED |
; 3. Sanity check: ignore uninitialized pipes. |
cmp [ebx+usb_pipe.LastTD], 0 |
jz .no_transfer |
; 4. Acquire the first and last to-be-cancelled transfer descriptor, |
; save them in stack for the step 6, |
; ask the controller driver to enable the pipe for hardware, |
; removing transfers between first and last to-be-cancelled descriptors. |
lea ecx, [esi+usb_controller.ControlLock] |
cmp [ebx+usb_pipe.Type], BULK_PIPE |
jb @f ; control pipe |
lea ecx, [esi+usb_controller.BulkLock] |
jz @f ; bulk pipe |
lea ecx, [esi+usb_controller.PeriodicLock] |
@@: |
call mutex_lock |
mov eax, [ebx+usb_pipe.BaseList] |
mov edx, [eax+usb_pipe.NextVirt] |
mov [ebx+usb_pipe.NextVirt], edx |
mov [ebx+usb_pipe.PrevVirt], eax |
mov [edx+usb_pipe.PrevVirt], ebx |
mov [eax+usb_pipe.NextVirt], ebx |
mov eax, [ebx+usb_pipe.LastTD] |
mov edx, [eax+usb_gtd.NextVirt] |
mov [eax+usb_gtd.NextVirt], eax |
mov [eax+usb_gtd.PrevVirt], eax |
push eax |
push edx |
push ecx |
call [edi+usb_hardware_func.EnablePipe] |
pop ecx |
call mutex_unlock |
; 5. Release pipe lock acquired at step 1. |
; Callbacks called at step 6 can insert new transfers, |
; so we cannot call usb_pipe_aborted while holding pipe lock. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_unlock |
; 6. Notify all registered callbacks with status USB_STATUS_CANCELLED, if any. |
; Two arguments describing transfers range were pushed at step 4. |
push USB_STATUS_CANCELLED |
call usb_pipe_aborted |
pop edi |
ret |
.no_transfer: |
call mutex_unlock |
pop edi |
ret |
endp |
; Part of API for drivers, see documentation for USBNormalTransferAsync. |
proc usb_normal_transfer_async stdcall uses ebx edi,\ |
pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword |
; 1. Sanity check: callback must be nonzero. |
; (It is important for other parts of code.) |
xor eax, eax |
cmp [callback], eax |
jz .nothing |
; 2. Lock the transfer queue. |
mov ebx, [pipe] |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
; 3. If the pipe has already been closed (presumably due to device disconnect), |
; release the lock taken in step 2 and return zero. |
xor eax, eax |
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
jnz .unlock |
; 4. Allocate and initialize TDs for the transfer. |
mov edx, [ebx+usb_pipe.Controller] |
mov edi, [edx+usb_controller.HardwareFunc] |
stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], [ebx+usb_pipe.LastTD], 0 |
; If failed, release the lock taken in step 2 and return zero. |
test eax, eax |
jz .unlock |
; 5. Store callback and its parameters in the last descriptor for this transfer. |
mov ecx, [eax+usb_gtd.PrevVirt] |
mov edx, [callback] |
mov [ecx+usb_gtd.Callback], edx |
mov edx, [calldata] |
mov [ecx+usb_gtd.UserData], edx |
mov edx, [buffer] |
mov [ecx+usb_gtd.Buffer], edx |
; 6. Advance LastTD pointer and activate transfer. |
push [ebx+usb_pipe.LastTD] |
mov [ebx+usb_pipe.LastTD], eax |
call [edi+usb_hardware_func.InsertTransfer] |
pop eax |
; 7. Release the lock taken in step 2 and |
; return pointer to the first descriptor for the new transfer. |
.unlock: |
push eax |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_unlock |
pop eax |
.nothing: |
ret |
endp |
; Part of API for drivers, see documentation for USBControlTransferAsync. |
proc usb_control_async stdcall uses ebx edi,\ |
pipe:dword, config:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword |
locals |
last_td dd ? |
endl |
; 1. Sanity check: callback must be nonzero. |
; (It is important for other parts of code.) |
cmp [callback], 0 |
jz .return0 |
; 2. Lock the transfer queue. |
mov ebx, [pipe] |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
; 3. If the pipe has already been closed (presumably due to device disconnect), |
; release the lock taken in step 2 and return zero. |
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
jnz .unlock_return0 |
; A control transfer contains two or three stages: |
; Setup stage, optional Data stage, Status stage. |
; 4. Allocate and initialize TDs for the Setup stage. |
; Payload is 8 bytes from [config]. |
mov edx, [ebx+usb_pipe.Controller] |
mov edi, [edx+usb_controller.HardwareFunc] |
stdcall [edi+usb_hardware_func.AllocTransfer], [config], 8, 0, [ebx+usb_pipe.LastTD], (2 shl 2) + 0 |
; short transfer is an error, direction is DATA0, token is SETUP |
mov [last_td], eax |
test eax, eax |
jz .fail |
; 5. Allocate and initialize TDs for the Data stage, if [size] is nonzero. |
; Payload is [size] bytes from [buffer]. |
mov edx, [config] |
mov ecx, (3 shl 2) + 1 ; DATA1, token is OUT |
cmp byte [edx], 0 |
jns @f |
cmp [size], 0 |
jz @f |
inc ecx ; token is IN |
@@: |
cmp [size], 0 |
jz .nodata |
push ecx |
stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], eax, ecx |
pop ecx |
test eax, eax |
jz .fail |
mov [last_td], eax |
.nodata: |
; 6. Allocate and initialize TDs for the Status stage. |
; No payload. |
xor ecx, 3 ; IN becomes OUT, OUT becomes IN |
stdcall [edi+usb_hardware_func.AllocTransfer], 0, 0, 0, eax, ecx |
test eax, eax |
jz .fail |
; 7. Store callback and its parameters in the last descriptor for this transfer. |
mov ecx, [eax+usb_gtd.PrevVirt] |
mov edx, [callback] |
mov [ecx+usb_gtd.Callback], edx |
mov edx, [calldata] |
mov [ecx+usb_gtd.UserData], edx |
mov edx, [buffer] |
mov [ecx+usb_gtd.Buffer], edx |
; 8. Advance LastTD pointer and activate transfer. |
push [ebx+usb_pipe.LastTD] |
mov [ebx+usb_pipe.LastTD], eax |
call [edi+usb_hardware_func.InsertTransfer] |
; 9. Release the lock taken in step 2 and |
; return pointer to the first descriptor for the new transfer. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_unlock |
pop eax |
ret |
.fail: |
mov eax, [last_td] |
test eax, eax |
jz .unlock_return0 |
stdcall usb_undo_tds, [ebx+usb_pipe.LastTD] |
.unlock_return0: |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_unlock |
.return0: |
xor eax, eax |
ret |
endp |
; Part of API for drivers, see documentation for USBAbortPipe. |
proc usb_abort_pipe |
push ebx esi ; save used registers to be stdcall |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.pipe dd ? |
end virtual |
mov ebx, [.pipe] |
; 1. Acquire pipe lock. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
; 2. If the pipe is already closed or abort is in progress, |
; just release pipe lock and return. |
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED + USB_FLAG_DISABLED |
jnz .nothing |
; 3. Mark the pipe as aborting. |
or [ebx+usb_pipe.Flags], USB_FLAG_DISABLED |
; 4. We cannot do anything except adding new transfers concurrently with hardware. |
; Ask the controller driver to (temporarily) remove the pipe from hardware queue. |
mov esi, [ebx+usb_pipe.Controller] |
; 4a. Acquire queue lock. |
lea ecx, [esi+usb_controller.ControlLock] |
cmp [ebx+usb_pipe.Type], BULK_PIPE |
jb @f ; control pipe |
lea ecx, [esi+usb_controller.BulkLock] |
jz @f ; bulk pipe |
lea ecx, [esi+usb_controller.PeriodicLock] |
@@: |
call mutex_lock |
push ecx |
; 4b. Call the driver. |
mov eax, [esi+usb_controller.HardwareFunc] |
call [eax+usb_hardware_func.DisablePipe] |
; 4c. Remove the pipe from software list. |
mov eax, [ebx+usb_pipe.NextVirt] |
mov edx, [ebx+usb_pipe.PrevVirt] |
mov [eax+usb_pipe.PrevVirt], edx |
mov [edx+usb_pipe.NextVirt], eax |
; 4c. Register the pipe in corresponding wait list. |
test [ebx+usb_pipe.Type], 1 |
jz .control_bulk |
call usb_subscribe_periodic |
jmp @f |
.control_bulk: |
call usb_subscribe_control |
@@: |
; 4d. Release queue lock. |
pop ecx |
call mutex_unlock |
; 4e. Notify the USB thread about new work. |
push ebx esi edi |
call usb_wakeup |
pop edi esi ebx |
; That's all for now. To be continued in usb_pipe_disabled. |
; 5. Release pipe lock acquired at step 1 and return. |
.nothing: |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_unlock |
pop esi ebx |
ret 4 |
endp |
; Part of API for drivers, see documentation for USBGetParam. |
proc usb_get_param |
virtual at esp |
dd ? ; return address |
.pipe dd ? |
.param dd ? |
end virtual |
mov edx, [.param] |
mov ecx, [.pipe] |
mov eax, [ecx+usb_pipe.DeviceData] |
test edx, edx |
jz .get_device_descriptor |
dec edx |
jz .get_config_descriptor |
dec edx |
jz .get_speed |
or eax, -1 |
ret 8 |
.get_device_descriptor: |
add eax, usb_device_data.DeviceDescriptor |
ret 8 |
.get_config_descriptor: |
movzx ecx, [eax+usb_device_data.DeviceDescrSize] |
lea eax, [eax+ecx+usb_device_data.DeviceDescriptor] |
ret 8 |
.get_speed: |
movzx eax, [eax+usb_device_data.Speed] |
ret 8 |
endp |
; Initialize software part of usb_gtd. Called from controller-specific code |
; somewhere in AllocTransfer with eax -> next (inactive) usb_gtd, |
; ebx -> usb_pipe, ebp frame from call to AllocTransfer with [.td] -> |
; current (initializing) usb_gtd. |
; Returns ecx = [.td]. |
proc usb_init_transfer |
virtual at ebp-4 |
.Size dd ? |
rd 2 |
.Buffer dd ? |
dd ? |
.Flags dd ? |
.td dd ? |
end virtual |
mov [eax+usb_gtd.Pipe], ebx |
mov ecx, [.td] |
mov [eax+usb_gtd.PrevVirt], ecx |
mov edx, [ecx+usb_gtd.NextVirt] |
mov [ecx+usb_gtd.NextVirt], eax |
mov [eax+usb_gtd.NextVirt], edx |
mov [edx+usb_gtd.PrevVirt], eax |
mov edx, [.Size] |
mov [ecx+usb_gtd.Length], edx |
xor edx, edx |
mov [ecx+usb_gtd.Callback], edx |
mov [ecx+usb_gtd.UserData], edx |
ret |
endp |
; Free all TDs for the current transfer if something has failed |
; during initialization (e.g. no memory for the next TD). |
; Stdcall with one stack argument = first TD for the transfer |
; and eax = last initialized TD for the transfer. |
proc usb_undo_tds |
push [eax+usb_gtd.NextVirt] |
@@: |
cmp eax, [esp+8] |
jz @f |
push [eax+usb_gtd.PrevVirt] |
stdcall [edi+usb_hardware_func.FreeTD], eax |
pop eax |
jmp @b |
@@: |
pop ecx |
mov [eax+usb_gtd.NextVirt], ecx |
mov [ecx+usb_gtd.PrevVirt], eax |
ret 4 |
endp |
; Helper procedure for handling short packets in controller-specific code. |
; Returns with CF cleared if this is the final packet in some stage: |
; for control transfers that means one of Data and Status stages, |
; for other transfers - the final packet in the only stage. |
proc usb_is_final_packet |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .nothing |
mov eax, [ebx+usb_gtd.NextVirt] |
cmp [eax+usb_gtd.Callback], 0 |
jz .stc |
mov eax, [ebx+usb_gtd.Pipe] |
cmp [eax+usb_pipe.Type], CONTROL_PIPE |
jz .nothing |
.stc: |
stc |
.nothing: |
ret |
endp |
; Helper procedure for controller-specific code: |
; removes one TD from the transfer queue, ebx -> usb_gtd to remove. |
proc usb_unlink_td |
mov ecx, [ebx+usb_gtd.Pipe] |
add ecx, usb_pipe.Lock |
call mutex_lock |
mov eax, [ebx+usb_gtd.PrevVirt] |
mov edx, [ebx+usb_gtd.NextVirt] |
mov [edx+usb_gtd.PrevVirt], eax |
mov [eax+usb_gtd.NextVirt], edx |
call mutex_unlock |
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 |
dd ? ; return address |
.edi dd ? |
.esi dd ? |
.ebp dd ? |
.esp dd ? |
.ebx dd ? |
.edx dd ? |
.ecx dd ? |
.eax dd ? |
end virtual |
cmp ebx, [.ebx] |
jz @f |
dbgstr 'ERROR!!! ebx changed' |
@@: |
cmp esi, [.esi] |
jz @f |
dbgstr 'ERROR!!! esi changed' |
@@: |
cmp edi, [.edi] |
jz @f |
dbgstr 'ERROR!!! edi changed' |
@@: |
cmp ebp, [.ebp] |
jz @f |
dbgstr 'ERROR!!! ebp changed' |
@@: |
ret |
endp |
end if |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/usb/protocol.inc |
---|
0,0 → 1,1019 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
$Revision$ |
; Implementation of the USB protocol for device enumeration. |
; Manage a USB device when it becomes ready for USB commands: |
; configure, enumerate, load the corresponding driver(s), |
; pass device information to the driver. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; USB standard request codes |
USB_GET_STATUS = 0 |
USB_CLEAR_FEATURE = 1 |
USB_SET_FEATURE = 3 |
USB_SET_ADDRESS = 5 |
USB_GET_DESCRIPTOR = 6 |
USB_SET_DESCRIPTOR = 7 |
USB_GET_CONFIGURATION = 8 |
USB_SET_CONFIGURATION = 9 |
USB_GET_INTERFACE = 10 |
USB_SET_INTERFACE = 11 |
USB_SYNCH_FRAME = 12 |
; USB standard descriptor types |
USB_DEVICE_DESCR = 1 |
USB_CONFIG_DESCR = 2 |
USB_STRING_DESCR = 3 |
USB_INTERFACE_DESCR = 4 |
USB_ENDPOINT_DESCR = 5 |
USB_DEVICE_QUALIFIER_DESCR = 6 |
USB_OTHER_SPEED_CONFIG_DESCR = 7 |
USB_INTERFACE_POWER_DESCR = 8 |
; Compile-time setting. If set, the code will dump all descriptors as they are |
; read to the debug board. |
USB_DUMP_DESCRIPTORS = 1 |
; According to the USB specification (9.2.6.3), |
; any device must response to SET_ADDRESS in 50 ms, or 5 timer ticks. |
; Of course, our world is far from ideal. |
; I have seen devices that just NAK everything when being reset from working |
; state, but start to work after second reset. |
; Our strategy is as follows: give 2 seconds for the first attempt, |
; this should be enough for normal devices and not too long to detect buggy ones. |
; If the device continues NAKing, reset it and retry several times, |
; doubling the interval: 2s -> 4s -> 8s -> 16s. Give up after that. |
; Numbers are quite arbitrary. |
TIMEOUT_SET_ADDRESS_INITIAL = 200 |
TIMEOUT_SET_ADDRESS_LAST = 1600 |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; USB descriptors. See USB specification for detailed explanations. |
; First two bytes of every descriptor have the same meaning. |
struct usb_descr |
bLength db ? |
; Size of this descriptor in bytes |
bDescriptorType db ? |
; One of USB_*_DESCR constants. |
ends |
; USB device descriptor |
struct usb_device_descr usb_descr |
bcdUSB dw ? |
; USB Specification Release number in BCD, e.g. 110h = USB 1.1 |
bDeviceClass db ? |
; USB Device Class Code |
bDeviceSubClass db ? |
; USB Device Subclass Code |
bDeviceProtocol db ? |
; USB Device Protocol Code |
bMaxPacketSize0 db ? |
; Maximum packet size for zero endpoint |
idVendor dw ? |
; Vendor ID |
idProduct dw ? |
; Product ID |
bcdDevice dw ? |
; Device release number in BCD |
iManufacturer db ? |
; Index of string descriptor describing manufacturer |
iProduct db ? |
; Index of string descriptor describing product |
iSerialNumber db ? |
; Index of string descriptor describing serial number |
bNumConfigurations db ? |
; Number of possible configurations |
ends |
; USB configuration descriptor |
struct usb_config_descr usb_descr |
wTotalLength dw ? |
; Total length of data returned for this configuration |
bNumInterfaces db ? |
; Number of interfaces in this configuration |
bConfigurationValue db ? |
; Value for SET_CONFIGURATION control request |
iConfiguration db ? |
; Index of string descriptor describing this configuration |
bmAttributes db ? |
; Bit 6 is SelfPowered, bit 5 is RemoteWakeupSupported, |
; bit 7 must be 1, other bits must be 0 |
bMaxPower db ? |
; Maximum power consumption from the bus in 2mA units |
ends |
; USB interface descriptor |
struct usb_interface_descr usb_descr |
; The following two fields work in pair. Sometimes one interface can work |
; in different modes; e.g. videostream from web-cameras requires different |
; bandwidth depending on resolution/quality/compression settings. |
; Each mode of each interface has its own descriptor with its own endpoints |
; following; all descriptors for one interface have the same bInterfaceNumber, |
; and different bAlternateSetting. |
; By default, any interface operates in mode with bAlternateSetting = 0. |
; Often this is the only mode. If there are another modes, the active mode |
; is selected by SET_INTERFACE(bAlternateSetting) control request. |
bInterfaceNumber db ? |
bAlternateSetting db ? |
bNumEndpoints db ? |
; Number of endpoints used by this interface, excluding zero endpoint |
bInterfaceClass db ? |
; USB Interface Class Code |
bInterfaceSubClass db ? |
; USB Interface Subclass Code |
bInterfaceProtocol db ? |
; USB Interface Protocol Code |
iInterface db ? |
; Index of string descriptor describing this interface |
ends |
; USB endpoint descriptor |
struct usb_endpoint_descr usb_descr |
bEndpointAddress db ? |
; Lower 4 bits form endpoint number, |
; upper bit is 0 for OUT endpoints and 1 for IN endpoints, |
; other bits must be zero |
bmAttributes db ? |
; Lower 2 bits form transfer type, one of *_PIPE, |
; other bits must be zero for non-isochronous endpoints; |
; refer to the USB specification for meaning in isochronous case |
wMaxPacketSize dw ? |
; Lower 11 bits form maximum packet size, |
; next two bits specify the number of additional transactions per microframe |
; for high-speed periodic endpoints, other bits must be zero. |
bInterval db ? |
; Interval for polling endpoint for data transfers. |
; Isochronous and high-speed interrupt endpoints: poll every 2^(bInterval-1) |
; (micro)frames |
; Full/low-speed interrupt endpoints: poll every bInterval frames |
; High-speed bulk/control OUT endpoints: maximum NAK rate |
ends |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; When a new device is ready to be configured, a controller-specific code |
; calls usb_new_device. |
; The sequence of further actions: |
; * open pipe for the zero endpoint (usb_new_device); |
; maximum packet size is not known yet, but it must be at least 8 bytes, |
; so it is safe to send packets with <= 8 bytes |
; * issue SET_ADDRESS control request (usb_new_device) |
; * set the new device address in the pipe (usb_set_address_callback) |
; * notify a controller-specific code that initialization of other ports |
; can be started (usb_set_address_callback) |
; * issue GET_DESCRIPTOR control request for first 8 bytes of device descriptor |
; (usb_after_set_address) |
; * first 8 bytes of device descriptor contain the true packet size for zero |
; endpoint, so set the true packet size (usb_get_descr8_callback) |
; * first 8 bytes of a descriptor contain the full size of this descriptor, |
; issue GET_DESCRIPTOR control request for the full device descriptor |
; (usb_after_set_endpoint_size) |
; * issue GET_DESCRIPTOR control request for first 8 bytes of configuration |
; descriptor (usb_get_descr_callback) |
; * issue GET_DESCRIPTOR control request for full configuration descriptor |
; (usb_know_length_callback) |
; * issue SET_CONFIGURATION control request (usb_set_config_callback) |
; * parse configuration descriptor, load the corresponding driver(s), |
; pass the configuration descriptor to the driver and let the driver do |
; the further work (usb_got_config_callback) |
; This function is called from controller-specific part |
; when a new device is ready to be configured. |
; in: ecx -> pseudo-pipe, part of usb_pipe |
; in: esi -> usb_controller |
; in: [esi+usb_controller.ResettingHub] is the pointer to usb_hub for device, |
; NULL if the device is connected to the root hub |
; in: [esi+usb_controller.ResettingPort] is the port for the device, zero-based |
; in: [esi+usb_controller.ResettingSpeed] is the speed of the device, one of |
; USB_SPEED_xx. |
; out: eax = 0 <=> failed, the caller should disable the port. |
proc usb_new_device |
push ebx edi ; save used registers to be stdcall |
; 1. Check whether we're here because we were trying to reset |
; already-registered device in hope to fix something serious. |
; If so, skip allocation and go to 6. |
movzx eax, [esi+usb_controller.ResettingPort] |
mov edx, [esi+usb_controller.ResettingHub] |
test edx, edx |
jz .test_roothub |
mov edx, [edx+usb_hub.ConnectedDevicesPtr] |
mov ebx, [edx+eax*4] |
jmp @f |
.test_roothub: |
mov ebx, [esi+usb_controller.DevicesByPort+eax*4] |
@@: |
test ebx, ebx |
jnz .try_set_address |
; 2. Allocate resources. Any device uses the following resources: |
; - device address in the bus |
; - memory for device data |
; - pipe for zero endpoint |
; If some allocation fails, we must undo our actions. Closing the pipe |
; is a hard task, so we avoid it and open the pipe as the last resource. |
; The order for other two allocations is quite arbitrary. |
; 2a. Allocate a bus address. |
push ecx |
call usb_set_address_request |
pop ecx |
; 2b. If failed, just return zero. |
test eax, eax |
jz .nothing |
; 2c. Allocate memory for device data. |
; For now, we need sizeof.usb_device_data and extra 8 bytes for GET_DESCRIPTOR |
; input and output, see usb_after_set_address. Later we will reallocate it |
; to actual size needed for descriptors. |
movi eax, sizeof.usb_device_data + 8 |
push ecx |
call malloc |
pop ecx |
; 2d. If failed, free the bus address and return zero. |
test eax, eax |
jz .nomemory |
; 2e. Open pipe for endpoint zero. |
; For now, we do not know the actual maximum packet size; |
; for full-speed devices it can be any of 8, 16, 32, 64 bytes, |
; low-speed devices must have 8 bytes, high-speed devices must have 64 bytes. |
; Thus, we must use some fake "maximum packet size" until the actual size |
; will be known. However, the maximum packet size must be at least 8, and |
; initial stages of the configuration process involves only packets of <= 8 |
; bytes, they will be transferred correctly as long as |
; the fake "maximum packet size" is also at least 8. |
; Thus, any number >= 8 is suitable for actual hardware. |
; However, software emulation of EHCI in VirtualBox assumes that high-speed |
; control transfers are those originating from pipes with max packet size = 64, |
; even on early stages of the configuration process. This is incorrect, |
; but we have no specific preferences, so let VirtualBox be happy and use 64 |
; as the fake "maximum packet size". |
push eax |
; We will need many zeroes. |
; "push edi" is one byte, "push 0" is two bytes; save space, use edi. |
xor edi, edi |
stdcall usb_open_pipe, ecx, edi, 64, edi, edi |
; Put pointer to pipe into ebx. "xchg eax,reg" is one byte, mov is two bytes. |
xchg eax, ebx |
pop eax |
; 2f. If failed, free the memory, the bus address and return zero. |
test ebx, ebx |
jz .freememory |
; 3. Store pointer to device data in the pipe structure. |
mov [ebx+usb_pipe.DeviceData], eax |
; 4. Init device data, using usb_controller.Resetting* variables. |
mov [eax+usb_device_data.Timer], edi |
mov dword [eax+usb_device_data.DeviceDescriptor], TIMEOUT_SET_ADDRESS_INITIAL |
mov [eax+usb_device_data.TTHub], edi |
mov [eax+usb_device_data.TTPort], 0 |
mov [eax+usb_device_data.NumInterfaces], edi |
mov [eax+usb_device_data.DeviceDescrSize], 0 |
mov dl, [esi+usb_controller.ResettingSpeed] |
mov [eax+usb_device_data.Speed], dl |
mov [eax+usb_device_data.NumPipes], 1 |
push ebx |
cmp dl, USB_SPEED_HS |
jz .nott |
mov ebx, [esi+usb_controller.ResettingHub] |
test ebx, ebx |
jz .nott |
mov cl, [esi+usb_controller.ResettingPort] |
mov edx, [ebx+usb_hub.ConfigPipe] |
mov edx, [edx+usb_pipe.DeviceData] |
cmp [edx+usb_device_data.TTHub], 0 |
jz @f |
mov cl, [edx+usb_device_data.TTPort] |
mov ebx, [edx+usb_device_data.TTHub] |
jmp .has_tt |
@@: |
cmp [edx+usb_device_data.Speed], USB_SPEED_HS |
jnz .nott |
.has_tt: |
mov [eax+usb_device_data.TTHub], ebx |
mov [eax+usb_device_data.TTPort], cl |
.nott: |
pop ebx |
mov [eax+usb_device_data.ConfigDataSize], edi |
mov [eax+usb_device_data.Interfaces], edi |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov [eax+usb_device_data.Port], cl |
mov edx, [esi+usb_controller.ResettingHub] |
mov [eax+usb_device_data.Hub], edx |
; 5. Store pointer to the config pipe in the hub data. |
; Config pipe serves as device identifier. |
; Root hubs use the array inside usb_controller structure, |
; non-root hubs use the array immediately after usb_hub structure. |
test edx, edx |
jz .roothub |
mov edx, [edx+usb_hub.ConnectedDevicesPtr] |
mov [edx+ecx*4], ebx |
jmp @f |
.roothub: |
mov [esi+usb_controller.DevicesByPort+ecx*4], ebx |
@@: |
call usb_reinit_pipe_list |
; 6. Issue SET_ADDRESS control request, using buffer filled in step 2a. |
; 6a. Configure timer to force reset after timeout. |
; Note: we can't use self-destructing timer, because we need to be able to cancel it, |
; and for self-destructing timer we could have race condition in cancelling/destructing. |
; DEBUGF 1,'K : pipe %x\n',ebx |
.try_set_address: |
xor edi, edi |
mov edx, [ebx+usb_pipe.DeviceData] |
stdcall timer_hs, [edx+usb_device_data.DeviceDescriptor], 7FFFFFFFh, usb_abort_pipe, ebx |
test eax, eax |
jz .nothing |
mov edx, [ebx+usb_pipe.DeviceData] |
mov [edx+usb_device_data.Timer], eax |
; 6b. If it succeeded, setup timer to configure wait timeout. |
lea eax, [esi+usb_controller.SetAddressBuffer] |
stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi |
; Use the return value from usb_control_async as our return value; |
; if it is zero, then something has failed. |
.nothing: |
; 7. Return. |
pop edi ebx ; restore used registers to be stdcall |
ret |
; Handlers of failures in steps 2b, 2d, 2f. |
.freememory: |
call free |
jmp .freeaddr |
.nomemory: |
dbgstr 'No memory for device data' |
.freeaddr: |
mov ecx, dword [esi+usb_controller.SetAddressBuffer+2] |
bts [esi+usb_controller.ExistingAddresses], ecx |
xor eax, eax |
jmp .nothing |
endp |
; Helper procedure for usb_new_device. |
; Allocates a new USB address and fills usb_controller.SetAddressBuffer |
; with data for SET_ADDRESS(allocated_address) request. |
; out: eax = 0 <=> failed |
; Destroys edi. |
proc usb_set_address_request |
; There are 128 bits, one for each possible address. |
; Note: only the USB thread works with usb_controller.ExistingAddresses, |
; so there is no need for synchronization. |
; We must find a bit set to 1 and clear it. |
; 1. Find the first dword which has a nonzero bit = which is nonzero. |
mov ecx, 128/32 |
lea edi, [esi+usb_controller.ExistingAddresses] |
xor eax, eax |
repz scasd |
; 2. If all dwords are zero, return an error. |
jz .error |
; 3. The dword at [edi-4] is nonzero. Find the lowest nonzero bit. |
bsf eax, [edi-4] |
; Now eax = bit number inside the dword at [edi-4]. |
; 4. Clear the bit. |
btr [edi-4], eax |
; 5. Generate the address by edi = memory address and eax = bit inside dword. |
; Address = eax + 8 * (edi-4 - (esi+usb_controller.ExistingAddress)). |
sub edi, esi |
lea edi, [eax+(edi-4-usb_controller.ExistingAddresses)*8] |
; 6. Store the allocated address in SetAddressBuffer and fill remaining fields. |
; Note that usb_controller is zeroed at allocation, so only command byte needs |
; to be filled. |
mov byte [esi+usb_controller.SetAddressBuffer+1], USB_SET_ADDRESS |
mov dword [esi+usb_controller.SetAddressBuffer+2], edi |
; 7. Return non-zero value in eax. |
inc eax |
.nothing: |
ret |
.error: |
dbgstr 'cannot allocate USB address' |
xor eax, eax |
jmp .nothing |
endp |
; This procedure is called by USB stack when SET_ADDRESS request initiated by |
; usb_new_device is completed, either successfully or unsuccessfully. |
; Note that USB stack uses esi = pointer to usb_controller. |
proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
push ebx ; save ebx to be stdcall |
mov ebx, [pipe] |
; 1. In any case, cancel the timer. |
mov eax, [ebx+usb_pipe.DeviceData] |
stdcall cancel_timer_hs, [eax+usb_device_data.Timer] |
mov eax, [ebx+usb_pipe.DeviceData] |
mov [eax+usb_device_data.Timer], 0 |
; Load data to registers for further references. |
mov ecx, dword [esi+usb_controller.SetAddressBuffer+2] |
mov eax, [esi+usb_controller.HardwareFunc] |
; 2. Check whether the device has accepted new address. If so, proceed to 3. |
; Otherwise, go to 4 if killed by usb_set_address_timeout or to 5 otherwise. |
cmp [status], USB_STATUS_CANCELLED |
jz .timeout |
cmp [status], 0 |
jnz .error |
; 3. Address accepted. |
; 3a. The controller-specific structure for the control pipe still uses |
; zero address. Call the controller-specific function to change it to |
; the actual address. |
; Note that the hardware could cache the controller-specific structure, |
; 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' |
call [eax+usb_hardware_func.SetDeviceAddress] |
; 3b. If the port is in non-root hub, clear 'reset in progress' flag. |
; In any case, proceed to 6. |
mov eax, [esi+usb_controller.ResettingHub] |
test eax, eax |
jz .return |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
.return: |
; 6. Address configuration done, we can proceed with other ports. |
; Call the worker function for that. |
call usb_test_pending_port |
.wakeup: |
push esi edi |
call usb_wakeup |
pop edi esi |
.nothing: |
pop ebx ; restore ebx to be stdcall |
ret |
.timeout: |
; 4. Device continues to NAK the request. Reset it and retry. |
mov edx, [ebx+usb_pipe.DeviceData] |
mov ecx, [edx+usb_device_data.DeviceDescriptor] |
add ecx, ecx |
cmp ecx, TIMEOUT_SET_ADDRESS_LAST |
ja .error |
mov [edx+usb_device_data.DeviceDescriptor], ecx |
dbgstr 'Timeout in USB device initialization, trying to reset...' |
cmp [esi+usb_controller.ResettingHub], 0 |
jz .reset_roothub |
push esi |
mov esi, [esi+usb_controller.ResettingHub] |
call usb_hub_initiate_reset |
pop esi |
jmp .nothing |
.reset_roothub: |
movzx ecx, [esi+usb_controller.ResettingPort] |
call [eax+usb_hardware_func.InitiateReset] |
jmp .wakeup |
.error: |
; 5. Device error: device not responding, disconnect etc. |
DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status] |
; 5a. The address has not been accepted. Mark it as free. |
bts dword [esi+usb_controller.ExistingAddresses], ecx |
; 5b. Disable the port with bad device. |
; For the root hub, call the controller-specific function and go to 6. |
; For non-root hubs, let the hub code do its work and return (the request |
; could take some time, the hub code is responsible for proceeding). |
cmp [esi+usb_controller.ResettingHub], 0 |
jz .roothub |
mov eax, [esi+usb_controller.ResettingHub] |
call usb_hub_disable_resetting_port |
jmp .nothing |
.roothub: |
movzx ecx, [esi+usb_controller.ResettingPort] |
call [eax+usb_hardware_func.PortDisable] |
jmp .return |
endp |
; This procedure is called from usb_subscription_done when the hardware cache |
; is cleared after request from usb_set_address_callback. |
; in: ebx -> usb_pipe |
proc usb_after_set_address |
; 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. |
; usb_new_device has allocated 8 extra bytes besides sizeof.usb_device_data; |
; use them for both input and output. |
mov eax, [ebx+usb_pipe.DeviceData] |
add eax, usb_device_data.DeviceDescriptor |
mov dword [eax], \ |
80h + \ ; device-to-host, standard, device-wide |
(USB_GET_DESCRIPTOR shl 8) + \ ; request |
(0 shl 16) + \ ; descriptor index: there is only one |
(USB_DEVICE_DESCR shl 24) ; descriptor type |
mov dword [eax+4], 8 shl 16 ; data length |
stdcall usb_control_async, ebx, eax, eax, 8, usb_get_descr8_callback, eax, 0 |
ret |
endp |
; This procedure is called by USB stack when GET_DESCRIPTOR(DEVICE_DESCR) |
; request initiated by usb_after_set_address is completed, either successfully |
; or unsuccessfully. |
; Note that USB stack uses esi = pointer to usb_controller. |
proc usb_get_descr8_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; mov eax, [buffer] |
; DEBUGF 1,'K : descr8: l=%x; %x %x %x %x %x %x %x %x\n',[length],\ |
; [eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2 |
push edi ebx ; save used registers to be stdcall |
mov ebx, [pipe] |
; 1. Check whether the operation was successful. |
; If not, say something to the debug board and stop the initialization. |
cmp [status], 0 |
jnz .error |
; 2. Length of descriptor must be at least sizeof.usb_device_descr bytes. |
; If not, say something to the debug board and stop the initialization. |
mov eax, [ebx+usb_pipe.DeviceData] |
cmp [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bLength], sizeof.usb_device_descr |
jb .error |
; 3. Now first 8 bytes of device descriptor are known; |
; set DeviceDescrSize accordingly. |
mov [eax+usb_device_data.DeviceDescrSize], 8 |
; 4. The controller-specific structure for the control pipe still uses |
; the fake "maximum packet size". Call the controller-specific function to |
; change it to the actual packet size from the device. |
; Note that the hardware could cache the controller-specific structure, |
; so changing it could take some time until the cache is evicted. |
; Thus, the call is asynchronous; meet us in usb_after_set_endpoint_size |
; when it will be safe to continue. |
movzx ecx, [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bMaxPacketSize0] |
mov eax, [esi+usb_controller.HardwareFunc] |
call [eax+usb_hardware_func.SetEndpointPacketSize] |
.nothing: |
; 5. Return. |
pop ebx edi ; restore used registers to be stdcall |
ret |
.error: |
dbgstr 'error with USB device descriptor' |
jmp .nothing |
endp |
; This procedure is called from usb_subscription_done when the hardware cache |
; is cleared after request from usb_get_descr8_callback. |
; in: ebx -> usb_pipe |
proc usb_after_set_endpoint_size |
; 1. Reallocate memory for device data: |
; add memory for now-known size of device descriptor and extra 8 bytes |
; for further actions. |
; 1a. Allocate new memory. |
mov eax, [ebx+usb_pipe.DeviceData] |
movzx eax, [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bLength] |
; save length for step 2 |
push eax |
add eax, sizeof.usb_device_data + 8 |
call malloc |
; 1b. If failed, say something to the debug board and stop the initialization. |
test eax, eax |
jz .nomemory |
; 1c. Copy data from old memory to new memory and switch the pointer in usb_pipe. |
push eax |
push esi edi |
mov esi, [ebx+usb_pipe.DeviceData] |
mov [ebx+usb_pipe.DeviceData], eax |
mov edi, eax |
mov eax, esi |
mov ecx, sizeof.usb_device_data / 4 |
rep movsd |
pop edi esi |
call usb_reinit_pipe_list |
; 1d. Free the old memory. |
call free |
pop eax |
; 2. Issue control transfer GET_DESCRIPTOR(DEVICE) for full descriptor. |
; restore length saved in step 1a |
pop edx |
add eax, sizeof.usb_device_data |
mov dword [eax], \ |
80h + \ ; device-to-host, standard, device-wide |
(USB_GET_DESCRIPTOR shl 8) + \ ; request |
(0 shl 16) + \ ; descriptor index: there is only one |
(USB_DEVICE_DESCR shl 24) ; descriptor type |
and dword [eax+4], 0 |
mov [eax+6], dl ; data length |
stdcall usb_control_async, ebx, eax, eax, edx, usb_get_descr_callback, eax, 0 |
; 3. Return. |
ret |
.nomemory: |
dbgstr 'No memory for device data' |
ret |
endp |
; This procedure is called by USB stack when GET_DESCRIPTOR(DEVICE) |
; request initiated by usb_after_set_endpoint_size is completed, |
; either successfully or unsuccessfully. |
proc usb_get_descr_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; Note: the prolog is the same as in usb_get_descr8_callback. |
push edi ebx ; save used registers to be stdcall |
; 1. Check whether the operation was successful. |
; If not, say something to the debug board and stop the initialization. |
cmp [status], 0 |
jnz usb_get_descr8_callback.error |
; The full descriptor is known, dump it if specified by compile-time option. |
if USB_DUMP_DESCRIPTORS |
mov eax, [buffer] |
mov ecx, [length] |
sub ecx, 8 |
jbe .skipdebug |
DEBUGF 1,'K : device descriptor:' |
@@: |
DEBUGF 1,' %x',[eax]:2 |
inc eax |
dec ecx |
jnz @b |
DEBUGF 1,'\n' |
.skipdebug: |
end if |
; 2. Check that bLength is the same as was in the previous request. |
; If not, say something to the debug board and stop the initialization. |
; It is important, because usb_after_set_endpoint_size has allocated memory |
; according to the old bLength. Note that [length] for control transfers |
; includes 8 bytes of setup packet, so data length = [length] - 8. |
mov eax, [buffer] |
movzx ecx, [eax+usb_device_descr.bLength] |
add ecx, 8 |
cmp [length], ecx |
jnz usb_get_descr8_callback.error |
; Amuse the user if she is watching the debug board. |
mov cl, [eax+usb_device_descr.bNumConfigurations] |
DEBUGF 1,'K : found USB device with ID %x:%x, %d configuration(s)\n',\ |
[eax+usb_device_descr.idVendor]:4,\ |
[eax+usb_device_descr.idProduct]:4,\ |
cl |
; 3. If there are no configurations, stop the initialization. |
cmp [eax+usb_device_descr.bNumConfigurations], 0 |
jz .nothing |
; 4. Copy length of device descriptor to device data structure. |
movzx edx, [eax+usb_device_descr.bLength] |
mov [eax+usb_device_data.DeviceDescrSize-usb_device_data.DeviceDescriptor], dl |
; 5. Issue control transfer GET_DESCRIPTOR(CONFIGURATION). We do not know |
; the full length of that descriptor, so start with first 8 bytes, they contain |
; the full length. |
; usb_after_set_endpoint_size has allocated 8 extra bytes after the |
; device descriptor, use them for both input and output. |
add eax, edx |
mov dword [eax], \ |
80h + \ ; device-to-host, standard, device-wide |
(USB_GET_DESCRIPTOR shl 8) + \ ; request |
(0 shl 16) + \ ; descriptor index: there is only one |
(USB_CONFIG_DESCR shl 24) ; descriptor type |
mov dword [eax+4], 8 shl 16 ; data length |
stdcall usb_control_async, [pipe], eax, eax, 8, usb_know_length_callback, eax, 0 |
.nothing: |
; 6. Return. |
pop ebx edi ; restore used registers to be stdcall |
ret |
endp |
; This procedure is called by USB stack when GET_DESCRIPTOR(CONFIGURATION) |
; request initiated by usb_get_descr_callback is completed, |
; either successfully or unsuccessfully. |
proc usb_know_length_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
push ebx ; save used registers to be stdcall |
; 1. Check whether the operation was successful. |
; If not, say something to the debug board and stop the initialization. |
cmp [status], 0 |
jnz .error |
; 2. Get the total length of data associated with config descriptor and store |
; it in device data structure. The total length must be at least |
; sizeof.usb_config_descr bytes; if not, say something to the debug board and |
; stop the initialization. |
mov eax, [buffer] |
mov edx, [pipe] |
movzx ecx, [eax+usb_config_descr.wTotalLength] |
mov eax, [edx+usb_pipe.DeviceData] |
cmp ecx, sizeof.usb_config_descr |
jb .error |
mov [eax+usb_device_data.ConfigDataSize], ecx |
; 3. Reallocate memory for device data: |
; include usb_device_data structure, device descriptor, |
; config descriptor with all associated data, and extra bytes |
; sufficient for 8 bytes control packet and for one usb_interface_data struc. |
; Align extra bytes to dword boundary. |
if sizeof.usb_interface_data > 8 |
.extra_size = sizeof.usb_interface_data |
else |
.extra_size = 8 |
end if |
; 3a. Allocate new memory. |
movzx edx, [eax+usb_device_data.DeviceDescrSize] |
lea eax, [ecx+edx+sizeof.usb_device_data+.extra_size+3] |
and eax, not 3 |
push eax |
call malloc |
pop edx |
; 3b. If failed, say something to the debug board and stop the initialization. |
test eax, eax |
jz .nomemory |
; 3c. Copy data from old memory to new memory and switch the pointer in usb_pipe. |
push eax |
mov ebx, [pipe] |
push esi edi |
mov esi, [ebx+usb_pipe.DeviceData] |
mov edi, eax |
mov [ebx+usb_pipe.DeviceData], eax |
mov eax, esi |
movzx ecx, [esi+usb_device_data.DeviceDescrSize] |
sub edx, .extra_size |
mov [esi+usb_device_data.Interfaces], edx |
add ecx, sizeof.usb_device_data + 8 |
mov edx, ecx |
shr ecx, 2 |
and edx, 3 |
rep movsd |
mov ecx, edx |
rep movsb |
pop edi esi |
call usb_reinit_pipe_list |
; 3d. Free old memory. |
call free |
pop eax |
; 4. Issue control transfer GET_DESCRIPTOR(CONFIGURATION) for full descriptor. |
movzx ecx, [eax+usb_device_data.DeviceDescrSize] |
mov edx, [eax+usb_device_data.ConfigDataSize] |
lea eax, [eax+ecx+sizeof.usb_device_data] |
mov dword [eax], \ |
80h + \ ; device-to-host, standard, device-wide |
(USB_GET_DESCRIPTOR shl 8) + \ ; request |
(0 shl 16) + \ ; descriptor index: there is only one |
(USB_CONFIG_DESCR shl 24) ; descriptor type |
and dword [eax+4], 0 |
mov word [eax+6], dx ; data length |
stdcall usb_control_async, [pipe], eax, eax, edx, usb_set_config_callback, eax, 0 |
.nothing: |
; 5. Return. |
pop ebx ; restore used registers to be stdcall |
ret |
.error: |
dbgstr 'error with USB configuration descriptor' |
jmp .nothing |
.nomemory: |
dbgstr 'No memory for device data' |
jmp .nothing |
endp |
; This procedure is called by USB stack when GET_DESCRIPTOR(CONFIGURATION) |
; request initiated by usb_know_length_callback is completed, |
; either successfully or unsuccessfully. |
proc usb_set_config_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
; Note that the prolog is the same as in usb_know_length_callback. |
push ebx ; save used registers to be stdcall |
; 1. Check whether the operation was successful. |
; If not, say something to the debug board and stop the initialization. |
xor ecx, ecx |
mov ebx, [pipe] |
cmp [status], ecx |
jnz usb_know_length_callback.error |
; The full descriptor is known, dump it if specified by compile-time option. |
if USB_DUMP_DESCRIPTORS |
mov eax, [buffer] |
mov ecx, [length] |
sub ecx, 8 |
jbe .skip_debug |
DEBUGF 1,'K : config descriptor:' |
@@: |
DEBUGF 1,' %x',[eax]:2 |
inc eax |
dec ecx |
jnz @b |
DEBUGF 1,'\n' |
.skip_debug: |
xor ecx, ecx |
end if |
; 2. Issue control transfer SET_CONFIGURATION to activate this configuration. |
; Usually this is the only configuration. |
; Use extra bytes allocated by usb_know_length_callback; |
; offset from device data start is stored in Interfaces. |
mov eax, [ebx+usb_pipe.DeviceData] |
mov edx, [buffer] |
add eax, [eax+usb_device_data.Interfaces] |
mov dl, [edx+usb_config_descr.bConfigurationValue] |
mov dword [eax], USB_SET_CONFIGURATION shl 8 |
mov dword [eax+4], ecx |
mov byte [eax+2], dl |
stdcall usb_control_async, [pipe], eax, ecx, ecx, usb_got_config_callback, [buffer], ecx |
pop ebx ; restore used registers to be stdcall |
ret |
endp |
; This procedure is called by USB stack when SET_CONFIGURATION |
; request initiated by usb_set_config_callback is completed, |
; either successfully or unsuccessfully. |
; If successfully, the device is configured and ready to work, |
; pass the device to the corresponding driver(s). |
proc usb_got_config_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword |
locals |
InterfacesData dd ? |
NumInterfaces dd ? |
driver dd ? |
endl |
; 1. If there was an error, say something to the debug board and stop the |
; initialization. |
cmp [status], 0 |
jz @f |
dbgstr 'USB error in SET_CONFIGURATION' |
ret |
@@: |
push ebx edi ; save used registers to be stdcall |
; 2. Sanity checks: the total length must be the same as before (because we |
; have allocated memory assuming the old value), length of config descriptor |
; must be at least sizeof.usb_config_descr (we use fields from it), |
; there must be at least one interface. |
mov ebx, [pipe] |
mov ebx, [ebx+usb_pipe.DeviceData] |
mov eax, [calldata] |
mov edx, [ebx+usb_device_data.ConfigDataSize] |
cmp [eax+usb_config_descr.wTotalLength], dx |
jnz .invalid |
cmp [eax+usb_config_descr.bLength], 9 |
jb .invalid |
movzx edx, [eax+usb_config_descr.bNumInterfaces] |
test edx, edx |
jnz @f |
.invalid: |
dbgstr 'error: invalid configuration descriptor' |
jmp .nothing |
@@: |
; 3. Store the number of interfaces in device data structure. |
mov [ebx+usb_device_data.NumInterfaces], edx |
; 4. If there is only one interface (which happens quite often), |
; the memory allocated in usb_know_length_callback is sufficient. |
; Otherwise (which also happens quite often), reallocate device data. |
; 4a. Check whether there is only one interface. If so, skip this step. |
cmp edx, 1 |
jz .has_memory |
; 4b. Allocate new memory. |
mov eax, [ebx+usb_device_data.Interfaces] |
lea eax, [eax+edx*sizeof.usb_interface_data] |
call malloc |
; 4c. If failed, say something to the debug board and |
; stop the initialization. |
test eax, eax |
jnz @f |
dbgstr 'No memory for device data' |
jmp .nothing |
@@: |
; 4d. Copy data from old memory to new memory and switch the pointer in usb_pipe. |
push eax |
push esi |
mov ebx, [pipe] |
mov edi, eax |
mov esi, [ebx+usb_pipe.DeviceData] |
mov [ebx+usb_pipe.DeviceData], eax |
mov eax, esi |
mov ecx, [esi+usb_device_data.Interfaces] |
shr ecx, 2 |
rep movsd |
pop esi |
call usb_reinit_pipe_list |
; 4e. Free old memory. |
call free |
pop ebx |
.has_memory: |
; 5. Initialize interfaces table: zero all contents. |
mov edi, [ebx+usb_device_data.Interfaces] |
add edi, ebx |
mov [InterfacesData], edi |
mov ecx, [ebx+usb_device_data.NumInterfaces] |
if sizeof.usb_interface_data <> 8 |
You have changed sizeof.usb_interface_data? Modify this place too. |
end if |
add ecx, ecx |
xor eax, eax |
rep stosd |
; No interfaces are found yet. |
mov [NumInterfaces], eax |
; 6. Get the pointer to config descriptor data. |
; Note: if there was reallocation, [buffer] is not valid anymore, |
; so calculate value based on usb_device_data. |
movzx eax, [ebx+usb_device_data.DeviceDescrSize] |
lea eax, [eax+ebx+sizeof.usb_device_data] |
mov [calldata], eax |
mov ecx, [ebx+usb_device_data.ConfigDataSize] |
; 7. Loop over all descriptors, |
; scan for interface descriptors with bAlternateSetting = 0, |
; load the corresponding driver, call its AddDevice function. |
.descriptor_loop: |
; While in loop: eax points to the current descriptor, |
; ecx = number of bytes left, the iteration starts only if ecx is nonzero, |
; edx = size of the current descriptor. |
; 7a. The first byte is always accessible; it contains the length of |
; the current descriptor. Validate that the length is at least 2 bytes, |
; and the entire descriptor is readable (the length is at most number of |
; bytes left). |
movzx edx, [eax+usb_descr.bLength] |
cmp edx, sizeof.usb_descr |
jb .invalid |
cmp ecx, edx |
jb .invalid |
; 7b. Check descriptor type. Ignore all non-INTERFACE descriptor. |
cmp byte [eax+usb_descr.bDescriptorType], USB_INTERFACE_DESCR |
jz .interface |
.next_descriptor: |
; 7c. Advance pointer, decrease length left, if there is still something left, |
; continue the loop. |
add eax, edx |
sub ecx, edx |
jnz .descriptor_loop |
.done: |
.nothing: |
pop edi ebx ; restore used registers to be stdcall |
ret |
.interface: |
; 7d. Validate the descriptor length. |
cmp edx, sizeof.usb_interface_descr |
jb .next_descriptor |
; 7e. If bAlternateSetting is nonzero, this descriptor actually describes |
; another mode of already known interface and belongs to the already loaded |
; driver; amuse the user and continue to 7c. |
cmp byte [eax+usb_interface_descr.bAlternateSetting], 0 |
jz @f |
DEBUGF 1,'K : note: alternate setting with %x/%x/%x\n',\ |
[eax+usb_interface_descr.bInterfaceClass]:2,\ |
[eax+usb_interface_descr.bInterfaceSubClass]:2,\ |
[eax+usb_interface_descr.bInterfaceProtocol]:2 |
jmp .next_descriptor |
@@: |
; 7f. Check that the new interface does not overflow allocated table. |
mov edx, [NumInterfaces] |
inc edx |
cmp edx, [ebx+usb_device_data.NumInterfaces] |
ja .invalid |
; 7g. We have found a new interface. Advance bookkeeping vars. |
mov [NumInterfaces], edx |
add [InterfacesData], sizeof.usb_interface_data |
; 7h. Save length left and pointer to the current interface descriptor. |
push ecx eax |
; Amuse the user if she is watching the debug board. |
DEBUGF 1,'K : USB interface class/subclass/protocol = %x/%x/%x\n',\ |
[eax+usb_interface_descr.bInterfaceClass]:2,\ |
[eax+usb_interface_descr.bInterfaceSubClass]:2,\ |
[eax+usb_interface_descr.bInterfaceProtocol]:2 |
; 7i. Select the correct driver based on interface class. |
; For hubs, go to 7j. Otherwise, go to 7k. |
; Note: this should be rewritten as table-based lookup when more drivers will |
; be available. |
cmp byte [eax+usb_interface_descr.bInterfaceClass], 9 |
jz .found_hub |
mov edx, usb_hid_name |
cmp byte [eax+usb_interface_descr.bInterfaceClass], 3 |
jz .load_driver |
mov edx, usb_print_name |
cmp byte [eax+usb_interface_descr.bInterfaceClass], 7 |
jz .load_driver |
mov edx, usb_stor_name |
cmp byte [eax+usb_interface_descr.bInterfaceClass], 8 |
jz .load_driver |
mov edx, usb_other_name |
jmp .load_driver |
.found_hub: |
; 7j. Hubs are a part of USB stack, thus, integrated into the kernel. |
; Use the pointer to hub callbacks and go to 7m. |
mov eax, usb_hub_pseudosrv - USBSRV.usb_func |
jmp .driver_loaded |
.load_driver: |
; 7k. Load the corresponding driver. |
push ebx esi edi |
stdcall get_service, edx |
pop edi esi ebx |
; 7l. If failed, say something to the debug board and go to 7p. |
test eax, eax |
jnz .driver_loaded |
dbgstr 'failed to load class driver' |
jmp .next_descriptor2 |
.driver_loaded: |
; 7m. Call AddDevice function of the driver. |
; Note that top of stack contains a pointer to the current interface, |
; saved by step 7h. |
mov [driver], eax |
mov eax, [eax+USBSRV.usb_func] |
pop edx |
push edx |
; Note: usb_hub_init assumes that edx points to usb_interface_descr, |
; ecx = length rest; if you change the code, modify usb_hub_init also. |
stdcall [eax+USBFUNC.add_device], [pipe], [calldata], edx |
; 7n. If failed, say something to the debug board and go to 7p. |
test eax, eax |
jnz .store_data |
dbgstr 'USB device initialization failed' |
jmp .next_descriptor2 |
.store_data: |
; 7o. Store the returned value and the driver handle to InterfacesData. |
; Note that step 7g has already advanced InterfacesData. |
mov edx, [InterfacesData] |
mov [edx+usb_interface_data.DriverData-sizeof.usb_interface_data], eax |
mov eax, [driver] |
mov [edx+usb_interface_data.DriverFunc-sizeof.usb_interface_data], eax |
.next_descriptor2: |
; 7p. Restore registers saved in step 7h, get the descriptor length and |
; continue to 7c. |
pop eax ecx |
movzx edx, byte [eax+usb_descr.bLength] |
jmp .next_descriptor |
endp |
; Driver names, see step 7i of usb_got_config_callback. |
iglobal |
usb_hid_name db 'usbhid',0 |
usb_stor_name db 'usbstor',0 |
usb_print_name db 'usbprint',0 |
usb_other_name db 'usbother',0 |
endg |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
Added: svn:keywords |
+Revision |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/usb/common.inc |
---|
0,0 → 1,462 |
; Constants and structures that are shared between different parts of |
; USB subsystem and *HCI drivers. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; Version of all structures related to host controllers. |
; Must be the same in kernel and *hci-drivers. |
USBHC_VERSION = 2 |
; 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 |
; 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 |
USB_STATUS_CANCELLED = 17 ; transfer cancelled with USBAbortPipe |
; Possible speeds of USB devices |
USB_SPEED_FS = 0 ; full-speed |
USB_SPEED_LS = 1 ; low-speed |
USB_SPEED_HS = 2 ; high-speed |
; 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_DISABLED = 8 |
; The pipe is temporarily disabled so that it is not visible to hardware |
; but still remains in software list. Used for usb_abort_pipe. |
USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; Description of controller-specific data and functions. |
struct usb_hardware_func |
Version dd ? ; must be USBHC_VERSION |
ID dd ? ; '*HCI' |
DataSize dd ? ; sizeof(*hci_controller) |
BeforeInit dd ? |
; Early initialization: take ownership from BIOS. |
; in: [ebp-4] = (bus shl 8) + devfn |
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). |
DisablePipe dd ? |
; This procedure temporarily removes the given pipe from hardware queue. |
; esi -> usb_controller, ebx -> usb_pipe |
EnablePipe dd ? |
; This procedure reinserts the given pipe to hardware queue |
; after DisablePipe, with clearing transfer queue. |
; esi -> usb_controller, ebx -> usb_pipe |
; edx -> current descriptor, eax -> new last descriptor |
ends |
; pointers to kernel API functions that are called from *HCI-drivers |
struct usbhc_func |
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 dd ? |
ends |
; 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. |
PCICoordinates dd ? |
; Device:function and bus number from PCI. |
; |
; 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. |
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. |
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 |
; 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. |
BaseList dd ? |
; Pointer to head of the processing list. |
; |
; 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 |
; 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_*. |
Timer dd ? |
; Handle of timer that handles request timeout. |
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 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/branches/kolibri-lldw/bus/. |
---|
Property changes: |
Added: svn:ignore |
+*.mnt |
+lang.inc |
+*.bat |
+out.txt |
+scin* |
+*.obj |