/kernel/trunk/bus/usb/ehci.inc |
---|
0,0 → 1,1914 |
; Code for EHCI controllers. |
; Note: it should be moved to an external driver, |
; it was convenient to have this code compiled into the kernel during initial |
; development, but there are no reasons to keep it here. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; EHCI register declarations. |
; Part 1. Capability registers. |
; Base is MMIO from the PCI space. |
EhciCapLengthReg = 0 |
EhciVersionReg = 2 |
EhciStructParamsReg = 4 |
EhciCapParamsReg = 8 |
EhciPortRouteReg = 0Ch |
; Part 2. Operational registers. |
; Base is (base for part 1) + (value of EhciCapLengthReg). |
EhciCommandReg = 0 |
EhciStatusReg = 4 |
EhciInterruptReg = 8 |
EhciFrameIndexReg = 0Ch |
EhciCtrlDataSegReg = 10h |
EhciPeriodicListReg = 14h |
EhciAsyncListReg = 18h |
EhciConfigFlagReg = 40h |
EhciPortsReg = 44h |
; Possible values of ehci_pipe.NextQH.Type bitfield. |
EHCI_TYPE_ITD = 0 ; isochronous transfer descriptor |
EHCI_TYPE_QH = 1 ; queue head |
EHCI_TYPE_SITD = 2 ; split-transaction isochronous TD |
EHCI_TYPE_FSTN = 3 ; frame span traversal node |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; Hardware part of EHCI general transfer descriptor. |
struct ehci_hardware_td |
NextTD dd ? |
; Bit 0 is Terminate bit, 1 = there is no next TD. |
; Bits 1-4 must be zero. |
; With masked 5 lower bits, this is the physical address of the next TD, if any. |
AlternateNextTD dd ? |
; Similar to NextTD, used if the transfer terminates with a short packet. |
Token dd ? |
; 1. Lower byte is Status field: |
; bit 0 = ping state for USB2 endpoints, ERR handshake signal for USB1 endpoints |
; bit 1 = split transaction state, meaningless for USB2 endpoints |
; bit 2 = missed micro-frame |
; bit 3 = transaction error |
; bit 4 = babble detected |
; bit 5 = data buffer error |
; bit 6 = halted |
; bit 7 = active |
; 2. Next two bits (bits 8-9) are PID code, 0 = OUT, 1 = IN, 2 = SETUP. |
; 3. Next two bits (bits 10-11) is ErrorCounter. Initialized as 3, decremented |
; on each error; if it goes to zero, transaction is stopped. |
; 4. Next 3 bits (bits 12-14) are CurrentPage field. |
; 5. Next bit (bit 15) is InterruptOnComplete bit. |
; 6. Next 15 bits (bits 16-30) are TransferLength field, |
; number of bytes to transfer. |
; 7. Upper bit (bit 31) is DataToggle bit. |
BufferPointers rd 5 |
; The buffer to be transferred can be spanned on up to 5 physical pages. |
; The first item of this array is the physical address of the first byte in |
; the buffer, other items are physical addresses of next pages. Lower 12 bits |
; in other items must be set to zero; ehci_pipe.Overlay reuses some of them. |
BufferPointersHigh rd 5 |
; Upper dwords of BufferPointers for controllers with 64-bit memory access. |
; Always zero. |
ends |
; EHCI general transfer descriptor. |
; * The structure describes transfers to be performed on Control, Bulk or |
; Interrupt endpoints. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 52 bytes and corresponds to |
; the Queue Element Transfer Descriptor from EHCI specification. |
; * The hardware requires 32-bytes alignment of the hardware part, so |
; the entire descriptor must be 32-bytes aligned. Since the allocator |
; (usb_allocate_common) allocates memory sequentially from page start |
; (aligned on 0x1000 bytes), size of the structure must be divisible by 32. |
; * The hardware also requires that the hardware part must not cross page |
; boundary; the allocator satisfies this automatically. |
struct ehci_gtd ehci_hardware_td |
Flags dd ? |
; Copy of flags from the call to usb_*_transfer_async. |
SoftwarePart rd sizeof.usb_gtd/4 |
; Software part, common for all controllers. |
rd 3 ; padding |
ends |
if sizeof.ehci_gtd mod 32 |
.err ehci_gtd must be 32-bytes aligned |
end if |
; EHCI-specific part of a pipe descriptor. |
; * This structure corresponds to the Queue Head from the EHCI specification. |
; * The hardware requires 32-bytes alignment of the hardware part. |
; Since the allocator (usb_allocate_common) allocates memory sequentially |
; from page start (aligned on 0x1000 bytes), size of the structure must be |
; divisible by 32. |
; * The hardware requires also that the hardware part must not cross page |
; boundary; the allocator satisfies this automatically. |
struct ehci_pipe |
NextQH dd ? |
; 1. First bit (bit 0) is Terminate bit, 1 = there is no next QH. |
; 2. Next two bits (bits 1-2) are Type field of the next QH, |
; one of EHCI_TYPE_* constants. |
; 3. Next two bits (bits 3-4) are reserved, must be zero. |
; 4. With masked 5 lower bits, this is the physical address of the next object |
; to be processed, usually next QH. |
Token dd ? |
; 1. Lower 7 bits are DeviceAddress field. This is the address of the |
; target device on the USB bus. |
; 2. Next bit (bit 7) is Inactivate-on-next-transaction bit. Can be nonzero |
; only for interrupt/isochronous USB1 endpoints. |
; 3. Next 4 bits (bits 8-11) are Endpoint field. This is the target endpoint |
; number. |
; 4. Next 2 bits (bits 12-13) are EndpointSpeed field, one of EHCI_SPEED_*. |
; 5. Next bit (bit 14) is DataToggleControl bit, |
; 0 = use DataToggle bit from QH, 1 = from TD. |
; 6. Next bit (bit 15) is Head-of-reclamation-list. The head of Control list |
; has 1 here, all other QHs have zero. |
; 7. Next 11 bits (bits 16-26) are MaximumPacketLength field for the target |
; endpoint. |
; 8. Next bit (bit 27) is ControlEndpoint bit, must be 1 for USB1 control |
; endpoints and 0 for all others. |
; 9. Upper 4 bits (bits 28-31) are NakCountReload field. |
; Zero for USB1 endpoints, zero for periodic endpoints. |
; For control/bulk USB2 endpoints, the code sets it to 4, |
; which is rather arbitrary. |
Flags dd ? |
; 1. Lower byte is S-mask, each bit corresponds to one microframe per frame; |
; bit is set <=> enable transactions in this microframe. |
; 2. Next byte is C-mask, each bit corresponds to one microframe per frame; |
; bit is set <=> enable complete-split transactions in this microframe. |
; Meaningful only for USB1 endpoints. |
; 3. Next 14 bits give address of the target device as hub:port, bits 16-22 |
; are the USB address of the hub, bits 23-29 are the port number. |
; Meaningful only for USB1 endpoints. |
; 4. Upper 2 bits define number of consequetive transactions per micro-frame |
; which host is allowed to permit for this endpoint. |
; For control/bulk endpoints, it must be 1. |
; For periodic endpoints, the value is taken from the endpoint descriptor. |
HeadTD dd ? |
; The physical address of the first TD for this pipe. |
; Lower 5 bits must be zero. |
Overlay ehci_hardware_td ? |
; Working area for the current TD, if there is any. |
; When TD is retired, it is written to that TD and Overlay is loaded |
; from the new TD, if any. |
BaseList dd ? |
; Pointer to head of the corresponding pipe list. |
SoftwarePart rd sizeof.usb_pipe/4 |
; Software part, common for all controllers. |
rd 2 ; padding |
ends |
if sizeof.ehci_pipe mod 32 |
.err ehci_pipe must be 32-bytes aligned |
end if |
; This structure describes the static head of every list of pipes. |
; The hardware requires 32-bytes alignment of this structure. |
; All instances of this structure are located sequentially in ehci_controller, |
; ehci_controller is page-aligned, so it is sufficient to make this structure |
; 32-bytes aligned and verify that the first instance is 32-bytes aligned |
; inside ehci_controller. |
; The hardware also requires that 44h bytes (size of 64-bit Queue Head |
; Descriptor) starting at the beginning of this structure must not cross page |
; boundary. If not, most hardware still behaves correctly (in fact, the last |
; dword can have any value and this structure is never written), but on some |
; hardware some things just break in mysterious ways. |
struct ehci_static_ep |
; Hardware fields are the same as in ehci_pipe. |
; Only NextQH and Overlay.Token are actually used. |
; NB: some emulators ignore Token.Halted bit (probably assuming that it is set |
; only when device fails and emulation never fails) and always follow |
; [Alternate]NextTD when they see that OverlayToken.Active bit is zero; |
; so it is important to also set [Alternate]NextTD to 1. |
NextQH dd ? |
Token dd ? |
Flags dd ? |
HeadTD dd ? |
NextTD dd ? |
AlternateNextTD dd ? |
OverlayToken dd ? |
NextList dd ? |
SoftwarePart rd sizeof.usb_static_ep/4 |
Bandwidths rw 8 |
dd ? |
ends |
if sizeof.ehci_static_ep mod 32 |
.err ehci_static_ep must be 32-bytes aligned |
end if |
if ehci_static_ep.OverlayToken <> ehci_pipe.Overlay.Token |
.err ehci_static_ep.OverlayToken misplaced |
end if |
; EHCI-specific part of controller data. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 4096 bytes and corresponds to |
; the Periodic Frame List from the EHCI specification. |
; * The hardware requires page-alignment of the hardware part, so |
; the entire descriptor must be page-aligned. |
; This structure is allocated with kernel_alloc (see usb_init_controller), |
; this gives page-aligned data. |
; * The controller is described by both ehci_controller and usb_controller |
; structures, for each controller there is one ehci_controller and one |
; usb_controller structure. These structures are located sequentially |
; in the memory: beginning from some page start, there is ehci_controller |
; structure - this enforces hardware alignment requirements - and then |
; usb_controller structure. |
; * The code keeps pointer to usb_controller structure. The ehci_controller |
; structure is addressed as [ptr + ehci_controller.field - sizeof.ehci_controller]. |
struct ehci_controller |
; ------------------------------ hardware fields ------------------------------ |
FrameList rd 1024 |
; Entry n corresponds to the head of the frame list to be executed in |
; the frames n,n+1024,n+2048,n+3096,... |
; The first bit of each entry is Terminate bit, 1 = the frame is empty. |
; Bits 1-2 are Type field, one of EHCI_TYPE_* constants. |
; Bits 3-4 must be zero. |
; With masked 5 lower bits, the entry is a physical address of the first QH/TD |
; to be executed. |
; ------------------------------ software fields ------------------------------ |
; Every list has the static head, which is an always halted QH. |
; The following fields are static heads, one per list: |
; 32+16+8+4+2+1 = 63 for Periodic lists, 1 for Control list and 1 for Bulk list. |
IntEDs ehci_static_ep |
rb 62 * sizeof.ehci_static_ep |
; Beware. |
; Two following strings ensure that 44h bytes at any static head |
; do not cross page boundary. Without that, the code "works on my machine"... |
; but fails on some hardware in seemingly unrelated ways. |
; One hardware TD (without any software fields) fit in the rest of the page. |
ehci_controller.ControlDelta = 2000h - (ehci_controller.IntEDs + 63 * sizeof.ehci_static_ep) |
StopQueueTD ehci_hardware_td |
; Used as AlternateNextTD for transfers when short packet is considered |
; as an error; short packet must stop the queue in this case, not advance |
; to the next transfer. |
rb ehci_controller.ControlDelta - sizeof.ehci_hardware_td |
; Padding for page-alignment. |
ControlED ehci_static_ep |
BulkED ehci_static_ep |
MMIOBase1 dd ? |
; Virtual address of memory-mapped area with part 1 of EHCI registers EhciXxxReg. |
MMIOBase2 dd ? |
; Pointer inside memory-mapped area MMIOBase1; points to part 2 of EHCI registers. |
StructuralParams dd ? |
; Copy of EhciStructParamsReg value. |
CapabilityParams dd ? |
; Copy of EhciCapParamsReg value. |
DeferredActions dd ? |
; Bitmask of events from EhciStatusReg which were observed by the IRQ handler |
; and needs to be processed in the IRQ thread. |
ends |
if ehci_controller.IntEDs mod 32 |
.err Static endpoint descriptors must be 32-bytes aligned inside ehci_controller |
end if |
; Description of #HCI-specific data and functions for |
; controller-independent code. |
; Implements the structure usb_hardware_func from hccommon.inc for EHCI. |
iglobal |
align 4 |
ehci_hardware_func: |
dd 'EHCI' |
dd sizeof.ehci_controller |
dd ehci_init |
dd ehci_process_deferred |
dd ehci_set_device_address |
dd ehci_get_device_address |
dd ehci_port_disable |
dd ehci_new_port.reset |
dd ehci_set_endpoint_packet_size |
dd ehci_alloc_pipe |
dd ehci_free_pipe |
dd ehci_init_pipe |
dd ehci_unlink_pipe |
dd ehci_alloc_td |
dd ehci_free_td |
dd ehci_alloc_transfer |
dd ehci_insert_transfer |
dd ehci_new_device |
endg |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Controller-specific initialization function. |
; Called from usb_init_controller. Initializes the hardware and |
; EHCI-specific parts of software structures. |
; eax = pointer to ehci_controller to be initialized |
; [ebp-4] = pcidevice |
proc ehci_init |
; inherit some variables from the parent (usb_init_controller) |
.devfn equ ebp - 4 |
.bus equ ebp - 3 |
; 1. Store pointer to ehci_controller for further use. |
push eax |
mov edi, eax |
mov esi, eax |
; 2. Initialize ehci_controller.FrameList. |
; Note that FrameList is located in the beginning of ehci_controller, |
; so esi and edi now point to ehci_controller.FrameList. |
; First 32 entries of FrameList contain physical addresses |
; of first 32 Periodic static heads, further entries duplicate these. |
; See the description of structures for full info. |
; 2a. Get physical address of first static head. |
; Note that 1) it is located in the beginning of a page |
; and 2) first 32 static heads fit in the same page, |
; so one call to get_phys_addr without correction of lower 12 bits |
; is sufficient. |
if (ehci_controller.IntEDs / 0x1000) <> ((ehci_controller.IntEDs + 32 * sizeof.ehci_static_ep) / 0x1000) |
.err assertion failed |
end if |
if (ehci_controller.IntEDs mod 0x1000) <> 0 |
.err assertion failed |
end if |
add eax, ehci_controller.IntEDs |
call get_phys_addr |
; 2b. Fill first 32 entries. |
inc eax |
inc eax ; set Type to EHCI_TYPE_QH |
push 32 |
pop ecx |
mov edx, ecx |
@@: |
stosd |
add eax, sizeof.ehci_static_ep |
loop @b |
; 2c. Fill the rest entries. |
mov ecx, 1024 - 32 |
rep movsd |
; 3. Initialize static heads ehci_controller.*ED. |
; Use the loop over groups: first group consists of first 32 Periodic |
; descriptors, next group consists of next 16 Periodic descriptors, |
; ..., last group consists of the last Periodic descriptor. |
; 3a. Prepare for the loop. |
; make esi point to the second group, other registers are already set. |
add esi, 32*4 + 32*sizeof.ehci_static_ep |
; 3b. Loop over groups. On every iteration: |
; edx = size of group, edi = pointer to the current group, |
; esi = pointer to the next group. |
.init_static_eds: |
; 3c. Get the size of next group. |
shr edx, 1 |
; 3d. Exit the loop if there is no next group. |
jz .init_static_eds_done |
; 3e. Initialize the first half of the current group. |
; Advance edi to the second half. |
push esi |
call ehci_init_static_ep_group |
pop esi |
; 3f. Initialize the second half of the current group |
; with the same values. |
; Advance edi to the next group, esi/eax to the next of the next group. |
call ehci_init_static_ep_group |
jmp .init_static_eds |
.init_static_eds_done: |
; 3g. Initialize the last static head. |
xor esi, esi |
call ehci_init_static_endpoint |
; While we are here, initialize StopQueueTD. |
if (ehci_controller.StopQueueTD <> ehci_controller.IntEDs + 63 * sizeof.ehci_static_ep) |
.err assertion failed |
end if |
inc [edi+ehci_hardware_td.NextTD] ; 0 -> 1 |
inc [edi+ehci_hardware_td.AlternateNextTD] ; 0 -> 1 |
; leave other fields as zero, including Active bit |
; 3i. Initialize the head of Control list. |
add edi, ehci_controller.ControlDelta |
lea esi, [edi+sizeof.ehci_static_ep] |
call ehci_init_static_endpoint |
or byte [edi-sizeof.ehci_static_ep+ehci_static_ep.Token+1], 80h |
; 3j. Initialize the head of Bulk list. |
sub esi, sizeof.ehci_static_ep |
call ehci_init_static_endpoint |
; 4. Create a virtual memory area to talk with the controller. |
; 4a. Enable memory & bus master access. |
mov ch, [.bus] |
mov cl, 1 |
mov eax, ecx |
mov bh, [.devfn] |
mov bl, 4 |
call pci_read_reg |
or al, 6 |
xchg eax, ecx |
call pci_write_reg |
; 4b. Read memory base address. |
mov ah, [.bus] |
mov al, 2 |
mov bl, 10h |
call pci_read_reg |
; DEBUGF 1,'K : phys MMIO %x\n',eax |
and al, not 0Fh |
; 4c. Create mapping for physical memory. 200h bytes are always sufficient. |
stdcall map_io_mem, eax, 200h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .fail |
; DEBUGF 1,'K : MMIO %x\n',eax |
if ehci_controller.MMIOBase1 <> ehci_controller.BulkED + sizeof.ehci_static_ep |
.err assertion failed |
end if |
stosd ; fill ehci_controller.MMIOBase1 |
movzx ecx, byte [eax+EhciCapLengthReg] |
mov edx, [eax+EhciCapParamsReg] |
mov ebx, [eax+EhciStructParamsReg] |
add eax, ecx |
if ehci_controller.MMIOBase2 <> ehci_controller.MMIOBase1 + 4 |
.err assertion failed |
end if |
stosd ; fill ehci_controller.MMIOBase2 |
if ehci_controller.StructuralParams <> ehci_controller.MMIOBase2 + 4 |
.err assertion failed |
end if |
if ehci_controller.CapabilityParams <> ehci_controller.StructuralParams + 4 |
.err assertion failed |
end if |
mov [edi], ebx ; fill ehci_controller.StructuralParams |
mov [edi+4], edx ; fill ehci_controller.CapabilityParams |
DEBUGF 1,'K : HCSPARAMS=%x, HCCPARAMS=%x\n',ebx,edx |
and ebx, 15 |
mov [edi+usb_controller.NumPorts+sizeof.ehci_controller-ehci_controller.StructuralParams], ebx |
mov edi, eax |
; now edi = MMIOBase2 |
; 6. Transfer the controller to a known state. |
; 6b. Stop the controller if it is running. |
push 10 |
pop ecx |
test dword [edi+EhciStatusReg], 1 shl 12 |
jnz .stopped |
and dword [edi+EhciCommandReg], not 1 |
@@: |
push 1 |
pop esi |
call delay_ms |
test dword [edi+EhciStatusReg], 1 shl 12 |
jnz .stopped |
loop @b |
dbgstr 'Failed to stop EHCI controller' |
jmp .fail_unmap |
.stopped: |
; 6c. Reset the controller. Wait up to 50 ms checking status every 1 ms. |
or dword [edi+EhciCommandReg], 2 |
push 50 |
pop ecx |
@@: |
push 1 |
pop esi |
call delay_ms |
test dword [edi+EhciCommandReg], 2 |
jz .reset_ok |
loop @b |
dbgstr 'Failed to reset EHCI controller' |
jmp .fail_unmap |
.reset_ok: |
; 7. Configure the controller. |
pop esi ; restore the pointer saved at step 1 |
add esi, sizeof.ehci_controller |
; 7a. If the controller is 64-bit, say to it that all structures are located |
; in first 4G. |
test byte [esi+ehci_controller.CapabilityParams-sizeof.ehci_controller], 1 |
jz @f |
mov dword [edi+EhciCtrlDataSegReg], 0 |
@@: |
; 7b. Hook interrupt and enable appropriate interrupt sources. |
mov ah, [.bus] |
mov al, 0 |
mov bh, [.devfn] |
mov bl, 3Ch |
call pci_read_reg |
; al = IRQ |
DEBUGF 1,'K : attaching to IRQ %x\n',al |
movzx eax, al |
stdcall attach_int_handler, eax, ehci_irq, esi |
; mov dword [edi+EhciStatusReg], 111111b ; clear status |
; disable Frame List Rollover interrupt, enable all other sources |
mov dword [edi+EhciInterruptReg], 110111b |
; 7c. Inform the controller of the address of periodic lists head. |
lea eax, [esi-sizeof.ehci_controller] |
call get_phys_addr |
mov dword [edi+EhciPeriodicListReg], eax |
; 7d. Inform the controller of the address of asynchronous lists head. |
lea eax, [esi+ehci_controller.ControlED-sizeof.ehci_controller] |
call get_phys_addr |
mov dword [edi+EhciAsyncListReg], eax |
; 7e. Configure operational details and run the controller. |
mov dword [edi+EhciCommandReg], \ |
(1 shl 16) + \ ; interrupt threshold = 1 microframe = 0.125ms |
(0 shl 11) + \ ; disable Async Park Mode |
(0 shl 8) + \ ; zero Async Park Mode Count |
(1 shl 5) + \ ; Async Schedule Enable |
(1 shl 4) + \ ; Periodic Schedule Enable |
(0 shl 2) + \ ; 1024 elements in FrameList |
1 ; Run |
; 7f. Route all ports to this controller, not companion controllers. |
mov dword [edi+EhciConfigFlagReg], 1 |
DEBUGF 1,'K : EHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] |
; 8. Apply port power, if needed, and disable all ports. |
xor ecx, ecx |
@@: |
mov dword [edi+EhciPortsReg+ecx*4], 1000h ; Port Power enabled, all other bits disabled |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb @b |
test byte [esi+ehci_controller.StructuralParams-sizeof.ehci_controller], 10h |
jz @f |
push esi |
push 20 |
pop esi |
call delay_ms |
pop esi |
@@: |
DEBUGF 1,'K : EHCI %x: command = %x, status = %x\n',esi,[edi+EhciCommandReg],[edi+EhciStatusReg] |
; 9. Return pointer to usb_controller. |
xchg eax, esi |
ret |
; On error, pop the pointer saved at step 1 and return zero. |
; Note that the main code branch restores the stack at step 7 and never fails |
; after step 7. |
.fail_unmap: |
pop eax |
push eax |
stdcall free_kernel_space, [eax+ehci_controller.MMIOBase1] |
.fail: |
pop ecx |
xor eax, eax |
ret |
endp |
; Helper procedure for step 3 of ehci_init, see comments there. |
; Initializes the static head of one list. |
; esi = pointer to the "next" list, edi = pointer to head to initialize. |
; Advances edi to the next head, keeps esi. |
proc ehci_init_static_endpoint |
xor eax, eax |
inc eax ; set Terminate bit |
mov [edi+ehci_static_ep.NextTD], eax |
mov [edi+ehci_static_ep.AlternateNextTD], eax |
test esi, esi |
jz @f |
mov eax, esi |
call get_phys_addr |
inc eax |
inc eax ; set Type to EHCI_TYPE_QH |
@@: |
mov [edi+ehci_static_ep.NextQH], eax |
mov [edi+ehci_static_ep.NextList], esi |
mov byte [edi+ehci_static_ep.OverlayToken], 1 shl 6 ; halted |
add edi, ehci_static_ep.SoftwarePart |
call usb_init_static_endpoint |
add edi, sizeof.ehci_static_ep - ehci_static_ep.SoftwarePart |
ret |
endp |
; Helper procedure for step 3 of ehci_init, see comments there. |
; Initializes one half of group of static heads. |
; edx = size of the next group = half of size of the group, |
; edi = pointer to the group, esi = pointer to the next group. |
; Advances esi, edi to next group, keeps edx. |
proc ehci_init_static_ep_group |
push edx |
@@: |
call ehci_init_static_endpoint |
add esi, sizeof.ehci_static_ep |
dec edx |
jnz @b |
pop edx |
ret |
endp |
; Controller-specific pre-initialization function: take ownership from BIOS. |
; Some BIOSes, although not all of them, use USB controllers themselves |
; to support USB flash drives. In this case, |
; we must notify the BIOS that we don't need that emulation and know how to |
; deal with USB devices. |
proc ehci_kickoff_bios |
; 1. Get the physical address of MMIO registers. |
mov ah, [esi+PCIDEV.bus] |
mov bh, [esi+PCIDEV.devfn] |
mov al, 2 |
mov bl, 10h |
call pci_read_reg |
and al, not 0Fh |
; 2. Create mapping for physical memory. 200h bytes are always sufficient. |
stdcall map_io_mem, eax, 200h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .nothing |
push eax ; push argument for step 8 |
; 3. Some BIOSes enable controller interrupts as a result of giving |
; controller away. At this point the system knows nothing about how to serve |
; EHCI interrupts, so such an interrupt will send the system into an infinite |
; loop handling the same IRQ again and again. Thus, we need to block EHCI |
; interrupts. We can't do this at the controller level until step 5, |
; because the controller is currently owned by BIOS, so we block all hardware |
; interrupts on this processor until step 5. |
pushf |
cli |
; 4. Take the ownership over the controller. |
; 4a. Locate take-ownership capability in the PCI configuration space. |
; Limit the loop with 100h iterations; since the entire configuration space is |
; 100h bytes long, hitting this number of iterations means that something is |
; corrupted. |
; Use a value from MMIO as a starting point. |
mov edx, [eax+EhciCapParamsReg] |
DEBUGF 1,'K : edx=%x\n',edx |
movzx edi, byte [eax+EhciCapLengthReg] |
add edi, eax |
push 0 |
mov bl, dh ; get Extended Capabilities Pointer |
test bl, bl |
jz .has_ownership2 |
cmp bl, 40h |
jb .no_capability |
.look_bios_handoff: |
test bl, 3 |
jnz .no_capability |
; In each iteration, read the current dword, |
mov ah, [esi+PCIDEV.bus] |
mov al, 2 |
mov bh, [esi+PCIDEV.devfn] |
call pci_read_reg |
; check, whether the capability ID is take-ownership ID = 1, |
cmp al, 1 |
jz .found_bios_handoff |
; if not, advance to next-capability link and continue loop. |
dec byte [esp] |
jz .no_capability |
mov bl, ah |
cmp bl, 40h |
jae .look_bios_handoff |
.no_capability: |
dbgstr 'warning: cannot locate take-ownership capability' |
jmp .has_ownership2 |
.found_bios_handoff: |
; 4b. Check whether BIOS has ownership. |
; Some BIOSes release ownership before loading OS, but forget to unwatch for |
; change-ownership requests; they cannot handle ownership request, so |
; such a request sends the system into infinite loop of handling the same SMI |
; over and over. Avoid this. |
inc ebx |
inc ebx |
test eax, 0x10000 |
jz .has_ownership |
; 4c. Request ownership. |
inc ebx |
mov cl, 1 |
mov ah, [esi+PCIDEV.bus] |
mov al, 0 |
call pci_write_reg |
; 4d. Some BIOSes set ownership flag, but forget to watch for change-ownership |
; requests; if so, there is no sense in waiting. |
inc ebx |
mov ah, [esi+PCIDEV.bus] |
mov al, 2 |
call pci_read_reg |
dec ebx |
dec ebx |
test ah, 20h |
jz .force_ownership |
; 4e. Wait for result no more than 1 s, checking for status every 1 ms. |
; If successful, go to 5. |
mov dword [esp], 1000 |
@@: |
mov ah, [esi+PCIDEV.bus] |
mov al, 0 |
call pci_read_reg |
test al, 1 |
jz .has_ownership |
push esi |
push 1 |
pop esi |
call delay_ms |
pop esi |
dec dword [esp] |
jnz @b |
dbgstr 'warning: taking EHCI ownership from BIOS timeout' |
.force_ownership: |
; 4f. BIOS has not responded within the timeout. |
; Let's just clear BIOS ownership flag and hope that everything will be ok. |
mov ah, [esi+PCIDEV.bus] |
mov al, 0 |
mov cl, 0 |
call pci_write_reg |
.has_ownership: |
; 5. Just in case clear all SMI event sources except change-ownership. |
dbgstr 'has_ownership' |
inc ebx |
inc ebx |
mov ah, [esi+PCIDEV.bus] |
mov al, 2 |
mov ecx, eax |
call pci_read_reg |
and ax, 2000h |
xchg eax, ecx |
call pci_write_reg |
.has_ownership2: |
pop ecx |
; 6. Disable all controller interrupts until the system will be ready to |
; process them. |
mov dword [edi+EhciInterruptReg], 0 |
; 7. Now we can unblock interrupts in the processor. |
popf |
; 8. Release memory mapping created in step 2 and return. |
call free_kernel_space |
.nothing: |
ret |
endp |
; IRQ handler for EHCI controllers. |
ehci_irq.noint: |
spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock] |
; Not our interrupt: restore registers and return zero. |
xor eax, eax |
pop edi esi ebx |
ret |
proc ehci_irq |
push ebx esi edi ; save registers to be cdecl |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.controller dd ? |
end virtual |
; 1. ebx will hold whether some deferred processing is needed, |
; that cannot be done from the interrupt handler. Initialize to zero. |
xor ebx, ebx |
; 2. Get the mask of events which should be processed. |
mov esi, [.controller] |
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
spin_lock_irqsave [esi+usb_controller.WaitSpinlock] |
mov eax, [edi+EhciStatusReg] |
mov ecx, eax |
; DEBUGF 1,'K : [%d] EHCI status %x\n',[timer_ticks],eax |
; 3. Check whether that interrupt has been generated by our controller. |
; (One IRQ can be shared by several devices.) |
and eax, [edi+EhciInterruptReg] |
jz .noint |
; 4. Clear the events we know of. |
; Note that this should be done before processing of events: |
; new events could arise while we are processing those, this way we won't lose |
; them (the controller would generate another interrupt after completion |
; of this one). |
DEBUGF 1,'K : EHCI %x interrupt: status = %x, enable = %x\n',esi,ecx,[edi+EhciInterruptReg] |
; DEBUGF 1,'K : EHCI interrupt: status = %x\n',eax |
mov [edi+EhciStatusReg], eax |
; 5. Sanity check. |
test al, 10h |
jz @f |
DEBUGF 1,'K : something terrible happened with EHCI %x (%x)\n',esi,al |
@@: |
; We can't do too much from an interrupt handler. Inform the processing thread |
; that it should perform appropriate actions. |
or [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], eax |
spin_unlock_irqrestore [esi+usb_controller.WaitSpinlock] |
inc ebx |
call usb_wakeup_if_needed |
; 6. Interrupt processed; return non-zero. |
mov al, 1 |
pop edi esi ebx ; restore used registers to be cdecl |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; and stores USB device address in the ehci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
proc ehci_set_device_address |
mov byte [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart], cl |
call usb_subscribe_control |
ret |
endp |
; This procedure returns USB device address from the ehci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = endpoint address |
proc ehci_get_device_address |
mov eax, [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart] |
and eax, 7Fh |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; if the device does not accept SET_ADDRESS command and needs |
; to be disabled at the port level. |
; in: esi -> usb_controller, ecx = port (zero-based) |
proc ehci_port_disable |
mov eax, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
and dword [eax+EhciPortsReg+ecx*4], not (4 or 2Ah) |
ret |
endp |
; This procedure is called from usb_get_descr8_callback when |
; the packet size for zero endpoint becomes known and |
; stores the packet size in ehci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
proc ehci_set_endpoint_packet_size |
mov eax, [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart] |
and eax, not (0x7FF shl 16) |
shl ecx, 16 |
or eax, ecx |
mov [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart], eax |
; Wait until hardware cache is evicted. |
call usb_subscribe_control |
ret |
endp |
uglobal |
align 4 |
; Data for memory allocator, see memory.inc. |
ehci_ep_first_page dd ? |
ehci_ep_mutex MUTEX |
ehci_gtd_first_page dd ? |
ehci_gtd_mutex MUTEX |
endg |
; This procedure allocates memory for pipe. |
; Both hardware+software parts must be allocated, returns pointer to usb_pipe |
; (software part). |
proc ehci_alloc_pipe |
push ebx |
mov ebx, ehci_ep_mutex |
stdcall usb_allocate_common, sizeof.ehci_pipe |
test eax, eax |
jz @f |
add eax, ehci_pipe.SoftwarePart |
@@: |
pop ebx |
ret |
endp |
; This procedure frees memory for pipe allocated by ehci_alloc_pipe. |
; void stdcall with one argument = pointer to usb_pipe. |
proc ehci_free_pipe |
virtual at esp |
dd ? ; return address |
.ptr dd ? |
end virtual |
sub [.ptr], ehci_pipe.SoftwarePart |
jmp usb_free_common |
endp |
; This procedure is called from API usb_open_pipe and processes |
; the controller-specific part of this API. See docs. |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
proc ehci_init_pipe |
virtual at ebp+8 |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; 1. Zero all fields in the hardware part. |
push eax ecx |
sub edi, ehci_pipe.SoftwarePart |
xor eax, eax |
push ehci_pipe.SoftwarePart/4 |
pop ecx |
rep stosd |
pop ecx eax |
; 2. Setup PID in the first TD and make sure that the it is not active. |
xor edx, edx |
test byte [.endpoint], 80h |
setnz dh |
mov [eax+ehci_gtd.Token-ehci_gtd.SoftwarePart], edx |
mov [eax+ehci_gtd.NextTD-ehci_gtd.SoftwarePart], 1 |
mov [eax+ehci_gtd.AlternateNextTD-ehci_gtd.SoftwarePart], 1 |
; 3. Store physical address of the first TD. |
sub eax, ehci_gtd.SoftwarePart |
call get_phys_addr |
mov [edi+ehci_pipe.Overlay.NextTD-ehci_pipe.SoftwarePart], eax |
; 4. Fill ehci_pipe.Flags except for S- and C-masks. |
; Copy location from the config pipe. |
mov eax, [ecx+ehci_pipe.Flags-ehci_pipe.SoftwarePart] |
and eax, 3FFF0000h |
; Use 1 requests per microframe for control/bulk endpoints, |
; use value from the endpoint descriptor for periodic endpoints |
push 1 |
pop edx |
test [.type], 1 |
jz @f |
mov edx, [.maxpacket] |
shr edx, 11 |
inc edx |
@@: |
shl edx, 30 |
or eax, edx |
mov [edi+ehci_pipe.Flags-ehci_pipe.SoftwarePart], eax |
; 5. Fill ehci_pipe.Token. |
mov eax, [ecx+ehci_pipe.Token-ehci_pipe.SoftwarePart] |
; copy following fields from the config pipe: |
; DeviceAddress, EndpointSpeed, ControlEndpoint if new type is control |
mov ecx, eax |
and eax, 307Fh |
and ecx, 8000000h |
or ecx, 4000h |
mov edx, [.endpoint] |
and edx, 15 |
shl edx, 8 |
or eax, edx |
mov edx, [.maxpacket] |
shl edx, 16 |
or eax, edx |
; for control endpoints, use DataToggle from TD, otherwise use DataToggle from QH |
cmp [.type], CONTROL_PIPE |
jnz @f |
or eax, ecx |
@@: |
; for control/bulk USB2 endpoints, set NakCountReload to 4 |
test eax, USB_SPEED_HS shl 12 |
jz .nonak |
cmp [.type], CONTROL_PIPE |
jz @f |
cmp [.type], BULK_PIPE |
jnz .nonak |
@@: |
or eax, 40000000h |
.nonak: |
mov [edi+ehci_pipe.Token-ehci_pipe.SoftwarePart], eax |
; 5. Select the corresponding list and insert to the list. |
; 5a. Use Control list for control pipes, Bulk list for bulk pipes. |
lea edx, [esi+ehci_controller.ControlED.SoftwarePart-sizeof.ehci_controller] |
cmp [.type], BULK_PIPE |
jb .insert ; control pipe |
lea edx, [esi+ehci_controller.BulkED.SoftwarePart-sizeof.ehci_controller] |
jz .insert ; bulk pipe |
.interrupt_pipe: |
; 5b. For interrupt pipes, let the scheduler select the appropriate list |
; and the appropriate microframe(s) (which goes to S-mask and C-mask) |
; based on the current bandwidth distribution and the requested bandwidth. |
; There are two schedulers, one for high-speed devices, |
; another for split transactions. |
; This could fail if the requested bandwidth is not available; |
; if so, return an error. |
test word [edi+ehci_pipe.Flags-ehci_pipe.SoftwarePart+2], 3FFFh |
jnz .interrupt_fs |
call ehci_select_hs_interrupt_list |
jmp .interrupt_common |
.interrupt_fs: |
call ehci_select_fs_interrupt_list |
.interrupt_common: |
test edx, edx |
jz .return0 |
mov word [edi+ehci_pipe.Flags-ehci_pipe.SoftwarePart], ax |
.insert: |
mov [edi+ehci_pipe.BaseList-ehci_pipe.SoftwarePart], edx |
; Insert to the head of the corresponding list. |
; Note: inserting to the head guarantees that the list traverse in |
; ehci_process_updated_schedule, once started, will not interact with new pipes. |
; However, we still need to ensure that links in the new pipe (edi.NextVirt) |
; are initialized before links to the new pipe (edx.NextVirt). |
; 5c. Insert in the list of virtual addresses. |
mov ecx, [edx+usb_pipe.NextVirt] |
mov [edi+usb_pipe.NextVirt], ecx |
mov [edi+usb_pipe.PrevVirt], edx |
mov [ecx+usb_pipe.PrevVirt], edi |
mov [edx+usb_pipe.NextVirt], edi |
; 5d. Insert in the hardware list: copy previous NextQH to the new pipe, |
; store the physical address of the new pipe to previous NextQH. |
mov ecx, [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart] |
mov [edi+ehci_pipe.NextQH-ehci_pipe.SoftwarePart], ecx |
lea eax, [edi-ehci_pipe.SoftwarePart] |
call get_phys_addr |
inc eax |
inc eax |
mov [edx+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], eax |
; 6. Return with nonzero eax. |
ret |
.return0: |
xor eax, eax |
ret |
endp |
; This function is called from ehci_process_deferred when |
; a new device was connected at least USB_CONNECT_DELAY ticks |
; and therefore is ready to be configured. |
; ecx = port, esi -> ehci_controller, edi -> EHCI MMIO |
proc ehci_new_port |
; 1. If the device operates at low-speed, just release it to a companion. |
mov eax, [edi+EhciPortsReg+ecx*4] |
DEBUGF 1,'K : [%d] EHCI %x port %d state is %x\n',[timer_ticks],esi,ecx,eax |
mov edx, eax |
and ah, 0Ch |
cmp ah, 4 |
jz .low_speed |
; 2. Devices operating at full-speed and high-speed must now have ah == 8. |
; Some broken hardware asserts both D+ and D- even after initial decoupling; |
; if so, stop initialization here, no sense in further actions. |
cmp ah, 0Ch |
jz .se1 |
; 3. If another port is resetting right now, mark this port as 'reset pending' |
; and return. |
bts [esi+usb_controller.PendingPorts], ecx |
cmp [esi+usb_controller.ResettingPort], -1 |
jnz .nothing |
btr [esi+usb_controller.PendingPorts], ecx |
; Otherwise, fall through to ohci_new_port.reset. |
; This function is called from ehci_new_port and usb_test_pending_port. |
; It starts reset signalling for the port. Note that in USB first stages |
; of configuration can not be done for several ports in parallel. |
.reset: |
push edi |
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
mov eax, [edi+EhciPortsReg+ecx*4] |
; 1. Store information about resetting hub (roothub) and port. |
and [esi+usb_controller.ResettingHub], 0 |
mov [esi+usb_controller.ResettingPort], cl |
; 2. Initiate reset signalling. |
or ah, 1 |
and al, not (4 or 2Ah) |
mov [edi+EhciPortsReg+ecx*4], eax |
; 3. Store the current time and set status to 1 = reset signalling active. |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 1 |
; dbgstr 'high-speed or full-speed device, resetting' |
DEBUGF 1,'K : [%d] EHCI %x: port %d has HS or FS device, resetting\n',[timer_ticks],esi,ecx |
pop edi |
.nothing: |
ret |
.low_speed: |
; dbgstr 'low-speed device, releasing' |
DEBUGF 1,'K : [%d] EHCI %x: port %d has LS device, releasing\n',[timer_ticks],esi,ecx |
or dh, 20h |
and dl, not 2Ah |
mov [edi+EhciPortsReg+ecx*4], edx |
ret |
.se1: |
dbgstr 'SE1 after connect debounce. Broken hardware?' |
ret |
endp |
; This procedure is called from several places in main USB code |
; and allocates required packets for the given transfer. |
; ebx = pipe, other parameters are passed through the stack: |
; buffer,size = data to transfer |
; flags = same as in usb_open_pipe: bit 0 = allow short transfer, other bits reserved |
; td = pointer to the current end-of-queue descriptor |
; direction = |
; 0000b for normal transfers, |
; 1000b for control SETUP transfer, |
; 1101b for control OUT transfer, |
; 1110b for control IN transfer |
; returns eax = pointer to the new end-of-queue descriptor |
; (not included in the queue itself) or 0 on error |
proc ehci_alloc_transfer stdcall uses edi, \ |
buffer:dword, size:dword, flags:dword, td:dword, direction:dword |
locals |
origTD dd ? |
packetSize dd ? ; must be last variable, see usb_init_transfer |
endl |
; 1. Save original value of td: |
; it will be useful for rollback if something would fail. |
mov eax, [td] |
mov [origTD], eax |
; One transfer descriptor can describe up to 5 pages. |
; In the worst case (when the buffer is something*1000h+0FFFh) |
; this corresponds to 4001h bytes. If the requested size is |
; greater, we should split the transfer into several descriptors. |
; Boundaries to split must be multiples of endpoint transfer size |
; to avoid short packets except in the end of the transfer, |
; 4000h is always a good value. |
; 2. While the remaining data cannot fit in one descriptor, |
; allocate full descriptors (of maximal possible size). |
mov edi, 4000h |
mov [packetSize], edi |
.fullpackets: |
cmp [size], edi |
jbe .lastpacket |
call ehci_alloc_packet |
test eax, eax |
jz .fail |
mov [td], eax |
add [buffer], edi |
sub [size], edi |
jmp .fullpackets |
; 3. The remaining data can fit in one packet; |
; allocate the last descriptor with size = size of remaining data. |
.lastpacket: |
mov eax, [size] |
mov [packetSize], eax |
call ehci_alloc_packet |
test eax, eax |
jz .fail |
; 9. Update flags in the last packet. |
mov edx, [flags] |
mov [ecx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], edx |
; 10. Fill AlternateNextTD field in all allocated TDs. |
; If the caller says that short transfer is ok, the queue must advance to |
; the next descriptor, which is in eax. |
; Otherwise, the queue should stop, so make AlternateNextTD point to |
; always-inactive descriptor StopQueueTD. |
push eax |
test dl, 1 |
jz .disable_short |
sub eax, ehci_gtd.SoftwarePart |
jmp @f |
.disable_short: |
mov eax, [ebx+usb_pipe.Controller] |
add eax, ehci_controller.StopQueueTD - sizeof.ehci_controller |
@@: |
call get_phys_addr |
mov edx, [origTD] |
@@: |
cmp edx, [esp] |
jz @f |
mov [edx+ehci_gtd.AlternateNextTD-ehci_gtd.SoftwarePart], eax |
mov edx, [edx+usb_gtd.NextVirt] |
jmp @b |
@@: |
pop eax |
ret |
.fail: |
mov edi, ehci_hardware_func |
mov eax, [td] |
stdcall usb_undo_tds, [origTD] |
xor eax, eax |
ret |
endp |
; Helper procedure for ehci_alloc_transfer. |
; Allocates and initializes one transfer descriptor. |
; ebx = pipe, other parameters are passed through the stack; |
; fills the current last descriptor and |
; returns eax = next descriptor (not filled). |
proc ehci_alloc_packet |
; inherit some variables from the parent ehci_alloc_transfer |
virtual at ebp-8 |
.origTD dd ? |
.packetSize dd ? |
rd 2 |
.buffer dd ? |
.transferSize dd ? |
.Flags dd ? |
.td dd ? |
.direction dd ? |
end virtual |
; 1. Allocate the next TD. |
call ehci_alloc_td |
test eax, eax |
jz .nothing |
; 2. Initialize controller-independent parts of both TDs. |
push eax |
call usb_init_transfer |
pop eax |
; 3. Copy PID to the new descriptor. |
mov edx, [ecx+ehci_gtd.Token-ehci_gtd.SoftwarePart] |
mov [eax+ehci_gtd.Token-ehci_gtd.SoftwarePart], edx |
mov [eax+ehci_gtd.NextTD-ehci_gtd.SoftwarePart], 1 |
mov [eax+ehci_gtd.AlternateNextTD-ehci_gtd.SoftwarePart], 1 |
; 4. Save the returned value (next descriptor). |
push eax |
; 5. Store the physical address of the next descriptor. |
sub eax, ehci_gtd.SoftwarePart |
call get_phys_addr |
mov [ecx+ehci_gtd.NextTD-ehci_gtd.SoftwarePart], eax |
; 6. For zero-length transfers, store zero in all fields for buffer addresses. |
; Otherwise, fill them with real values. |
xor eax, eax |
mov [ecx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], eax |
repeat 10 |
mov [ecx+ehci_gtd.BufferPointers-ehci_gtd.SoftwarePart+(%-1)*4], eax |
end repeat |
cmp [.packetSize], eax |
jz @f |
mov eax, [.buffer] |
call get_phys_addr |
mov [ecx+ehci_gtd.BufferPointers-ehci_gtd.SoftwarePart], eax |
and eax, 0xFFF |
mov edx, [.packetSize] |
add edx, eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x1000 |
call get_pg_addr |
mov [ecx+ehci_gtd.BufferPointers+4-ehci_gtd.SoftwarePart], eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x2000 |
call get_pg_addr |
mov [ecx+ehci_gtd.BufferPointers+8-ehci_gtd.SoftwarePart], eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x3000 |
call get_pg_addr |
mov [ecx+ehci_gtd.BufferPointers+12-ehci_gtd.SoftwarePart], eax |
sub edx, 0x1000 |
jbe @f |
mov eax, [.buffer] |
add eax, 0x4000 |
call get_pg_addr |
mov [ecx+ehci_gtd.BufferPointers+16-ehci_gtd.SoftwarePart], eax |
@@: |
; 7. Fill Token field: |
; set Status = 0 (inactive, ehci_insert_transfer would mark everything active); |
; keep current PID if [.direction] is zero, use two lower bits of [.direction] |
; otherwise shifted as (0|1|2) -> (2|0|1); |
; set error counter to 3; |
; set current page to 0; |
; do not interrupt on complete (ehci_insert_transfer sets this bit where needed); |
; set DataToggle to bit 2 of [.direction]. |
mov eax, [ecx+ehci_gtd.Token-ehci_gtd.SoftwarePart] |
and eax, 300h ; keep PID code |
mov edx, [.direction] |
test edx, edx |
jz .haspid |
and edx, 3 |
dec edx |
jns @f |
add edx, 3 |
@@: |
mov ah, dl |
mov edx, [.direction] |
and edx, not 3 |
shl edx, 29 |
or eax, edx |
.haspid: |
or eax, 0C00h |
mov edx, [.packetSize] |
shl edx, 16 |
or eax, edx |
mov [ecx+ehci_gtd.Token-ehci_gtd.SoftwarePart], eax |
; 4. Restore the returned value saved in step 2. |
pop eax |
.nothing: |
ret |
endp |
; This procedure is called from several places in main USB code |
; and activates the transfer which was previously allocated by |
; ehci_alloc_transfer. |
; ecx -> last descriptor for the transfer, ebx -> usb_pipe |
proc ehci_insert_transfer |
or byte [ecx+ehci_gtd.Token+1-ehci_gtd.SoftwarePart], 80h ; set IOC bit |
mov eax, [esp+4] |
.activate: |
or byte [eax+ehci_gtd.Token-ehci_gtd.SoftwarePart], 80h ; set Active bit |
cmp eax, ecx |
mov eax, [eax+usb_gtd.NextVirt] |
jnz .activate |
ret |
endp |
; This function is called from ehci_process_deferred when |
; reset signalling for a new device needs to be finished. |
proc ehci_port_reset_done |
movzx ecx, [esi+usb_controller.ResettingPort] |
and dword [edi+EhciPortsReg+ecx*4], not 12Ah |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 2 |
DEBUGF 1,'K : [%d] EHCI %x: reset port %d done\n',[timer_ticks],esi,ecx |
ret |
endp |
; This function is called from ehci_process_deferred when |
; a new device has been reset, recovered after reset and needs to be configured. |
proc ehci_port_init |
; 1. Get the status and set it to zero. |
; If reset has been failed (device disconnected during reset), |
; continue to next device (if there is one). |
xor eax, eax |
xchg al, [esi+usb_controller.ResettingStatus] |
test al, al |
js usb_test_pending_port |
; 2. Get the port status. High-speed devices should be now enabled, |
; full-speed devices are left disabled; |
; if the port is disabled, release it to a companion and continue to |
; next device (if there is one). |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov eax, [edi+EhciPortsReg+ecx*4] |
DEBUGF 1,'K : [%d] EHCI %x status of port %d is %x\n',[timer_ticks],esi,ecx,eax |
test al, 4 |
jnz @f |
; DEBUGF 1,'K : USB port disabled after reset, status = %x\n',eax |
dbgstr 'releasing to companion' |
or ah, 20h |
mov [edi+EhciPortsReg+ecx*4], eax |
jmp usb_test_pending_port |
@@: |
; 3. Call the worker procedure to notify the protocol layer |
; about new EHCI device. It is high-speed. |
push USB_SPEED_HS |
pop eax |
call ehci_new_device |
test eax, eax |
jnz .nothing |
; 4. If something at the protocol layer has failed |
; (no memory, no bus address), disable the port and stop the initialization. |
.disable_exit: |
and dword [edi+EhciPortsReg+ecx*4], not (4 or 2Ah) |
jmp usb_test_pending_port |
.nothing: |
ret |
endp |
; This procedure is called from ehci_port_init and from hub support code |
; when a new device is connected and has been reset. |
; It calls usb_new_device at the protocol layer with correct parameters. |
; in: esi -> usb_controller, eax = speed. |
proc ehci_new_device |
push ebx ecx ; save used registers (ecx is important for ehci_port_init) |
; 1. Store the speed for the protocol layer. |
mov [esi+usb_controller.ResettingSpeed], al |
; 2. Shift speed bits to the proper place in ehci_pipe.Token. |
shl eax, 12 |
; 3. For high-speed devices, go to step 5 with edx = 0. |
xor edx, edx |
cmp ah, USB_SPEED_HS shl (12-8) |
jz .common |
; 4. For low-speed and full-speed devices, fill address:port |
; of the last high-speed hub (the closest to the device hub) |
; for split transactions, and set ControlEndpoint bit in eax; |
; ehci_init_pipe assumes that the parent pipe is a control pipe. |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov edx, [esi+usb_controller.ResettingHub] |
push eax |
.find_hs_hub: |
mov eax, [edx+usb_hub.ConfigPipe] |
mov eax, [eax+usb_pipe.DeviceData] |
cmp [eax+usb_device_data.Speed], USB_SPEED_HS |
jz .found_hs_hub |
movzx ecx, [eax+usb_device_data.Port] |
mov edx, [eax+usb_device_data.Hub] |
jmp .find_hs_hub |
.found_hs_hub: |
mov edx, [edx+usb_hub.ConfigPipe] |
inc ecx |
mov edx, [edx+ehci_pipe.Token-ehci_pipe.SoftwarePart] |
shl ecx, 23 |
and edx, 7Fh |
shl edx, 16 |
or edx, ecx ; ehci_pipe.Flags |
pop eax |
or eax, 1 shl 27 ; ehci_pipe.Token |
.common: |
; 5. Create pseudo-pipe in the stack. |
; See ehci_init_pipe: only .Controller, .Token, .Flags fields are used. |
push esi ; ehci_pipe.SoftwarePart.Controller |
mov ecx, esp |
sub esp, ehci_pipe.SoftwarePart - ehci_pipe.Flags - 4 |
push edx ; ehci_pipe.Flags |
push eax ; ehci_pipe.Token |
; 6. Notify the protocol layer. |
call usb_new_device |
; 7. Cleanup the stack after step 5 and return. |
add esp, ehci_pipe.SoftwarePart - ehci_pipe.Flags + 8 |
pop ecx ebx ; restore used registers |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; processes regular actions and those actions which can't be safely done |
; from interrupt handler. |
; Returns maximal time delta before the next call. |
proc ehci_process_deferred |
push ebx edi ; save used registers to be stdcall |
mov edi, [esi+ehci_controller.MMIOBase2-sizeof.ehci_controller] |
; 1. Get the mask of events to process. |
xor eax, eax |
xchg eax, [esi+ehci_controller.DeferredActions-sizeof.ehci_controller] |
push eax |
; 2. Initialize the return value. |
push -1 |
; Handle roothub events. |
; 3a. Test whether there are such events. |
test al, 4 |
jz .skip_roothub |
; Status of some port has changed. Loop over all ports. |
; 3b. Prepare for the loop: start from port 0. |
xor ecx, ecx |
.portloop: |
; 3c. Get the port status and changes of it. |
; If there are no changes, just continue to the next port. |
mov eax, [edi+EhciPortsReg+ecx*4] |
test al, 2Ah |
jz .nextport |
; 3d. Clear change bits and read the status again. |
; (It is possible, although quite unlikely, that some event occurs between |
; the first read and the clearing, invalidating the old status. If an event |
; occurs after the clearing, we will not miss it, looking in the next scan. |
mov [edi+EhciPortsReg+ecx*4], eax |
mov ebx, eax |
mov eax, [edi+EhciPortsReg+ecx*4] |
DEBUGF 1,'K : [%d] EHCI %x: status of port %d changed to %x\n',[timer_ticks],esi,ecx,ebx |
; 3e. Handle overcurrent. |
; Note: that needs work. |
test bl, 20h ; overcurrent change |
jz .noovercurrent |
test al, 10h ; overcurrent active |
jz .noovercurrent |
DEBUGF 1,'K : overcurrent at port %d\n',ecx |
.noovercurrent: |
; 3f. Handle changing of connection status. |
test bl, 2 |
jz .nocsc |
; There was a connect or disconnect event at this port. |
; 3g. Disconnect the old device on this port, if any. |
; If the port was resetting, indicate fail; later stages will process it. |
cmp [esi+usb_controller.ResettingHub], 0 |
jnz @f |
cmp cl, [esi+usb_controller.ResettingPort] |
jnz @f |
mov [esi+usb_controller.ResettingStatus], -1 |
@@: |
bts [esi+usb_controller.NewDisconnected], ecx |
; 3h. Change connected status. For the connection event, also store |
; the connection time; any further processing is permitted only after |
; USB_CONNECT_DELAY ticks. |
test al, 1 |
jz .disconnect |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
bts [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.disconnect: |
btr [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.nocsc: |
; 3i. Handle port disabling. |
; Note: that needs work. |
test al, 8 |
jz @f |
test al, 4 |
jz @f |
DEBUGF 1,'K : port %d disabled\n',ecx |
@@: |
; 3j. Continue the loop for the next port. |
.nextport: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_roothub: |
; 4. Process disconnect events. This should be done after step 3 |
; (which includes the first stage of disconnect processing). |
call usb_disconnect_stage2 |
; 5. Check for previously connected devices. |
; If there is a connected device which was connected less than |
; USB_CONNECT_DELAY ticks ago, plan to wake up when the delay will be over. |
; Otherwise, call ehci_new_port. |
; This should be done after step 3. |
xor ecx, ecx |
cmp [esi+usb_controller.NewConnected], ecx |
jz .skip_newconnected |
.portloop2: |
bt [esi+usb_controller.NewConnected], ecx |
jnc .noconnect |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ConnectedTime+ecx*4] |
sub eax, USB_CONNECT_DELAY |
jge .connected |
neg eax |
cmp [esp], eax |
jb .nextport2 |
mov [esp], eax |
jmp .nextport2 |
.connected: |
btr [esi+usb_controller.NewConnected], ecx |
call ehci_new_port |
jmp .portloop2 |
.noconnect: |
.nextport2: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop2 |
.skip_newconnected: |
; 6. Process wait lists. |
; 6a. Periodic endpoints. |
; If a request is pending >8 microframes, satisfy it. |
; If a request is pending <=8 microframes, schedule next wakeup in 0.01s. |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
cmp eax, [esi+usb_controller.ReadyPipeHeadPeriodic] |
jz .noperiodic |
mov edx, [edi+EhciFrameIndexReg] |
sub edx, [esi+usb_controller.StartWaitFrame] |
and edx, 0x3FFF |
cmp edx, 8 |
jbe @f |
mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax |
jmp .noperiodic |
@@: |
pop eax |
push 1 ; wakeup in 0.01 sec for next test |
.noperiodic: |
; 6b. Asynchronous endpoints. |
; Satisfy a request when InterruptOnAsyncAdvance fired. |
test byte [esp+4], 20h |
jz @f |
dbgstr 'async advance int' |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
@@: |
; Some hardware in some (rarely) conditions set the status bit, |
; but just does not generate the corresponding interrupt. |
; Force checking the status here. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
cmp [esi+usb_controller.ReadyPipeHeadAsync], eax |
jz .noasync |
spin_lock_irq [esi+usb_controller.WaitSpinlock] |
mov edx, [edi+EhciStatusReg] |
test dl, 20h |
jz @f |
mov dword [edi+EhciStatusReg], 20h |
and dword [esi+ehci_controller.DeferredActions-sizeof.ehci_controller], not 20h |
dbgstr 'warning: async advance int missed' |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
jmp .async_unlock |
@@: |
cmp dword [esp], 100 |
jb .async_unlock |
mov dword [esp], 100 |
.async_unlock: |
spin_unlock_irq [esi+usb_controller.WaitSpinlock] |
.noasync: |
; 7. Finalize transfers processed by hardware. |
; It is better to perform this step after step 4 (disconnect events), |
; although not strictly obligatory. This way, an active transfer aborted |
; due to disconnect would be handled with more specific USB_STATUS_CLOSED, |
; not USB_STATUS_NORESPONSE. |
test byte [esp+4], 3 |
jz @f |
call ehci_process_updated_schedule |
@@: |
; 8. Test whether reset signalling has been started and should be stopped now. |
; This must be done after step 7, because completion of some transfer could |
; result in resetting a new port. |
.test_reset: |
; 8a. Test whether reset signalling is active. |
cmp [esi+usb_controller.ResettingStatus], 1 |
jnz .no_reset_in_progress |
; 8b. Yep. Test whether it should be stopped. |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_TIME |
jge .reset_done |
; 8c. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_done: |
; 8d. Yep, call the worker function and proceed to 8e. |
call ehci_port_reset_done |
.no_reset_in_progress: |
; 8e. Test whether reset process is done, either successful or failed. |
cmp [esi+usb_controller.ResettingStatus], 0 |
jz .skip_reset |
; 8f. Yep. Test whether it should be stopped. |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_recovery_done |
; 8g. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_recovery_done: |
; 8h. Yep, call the worker function. This could initiate another reset, |
; so return to the beginning of this step. |
call ehci_port_init |
jmp .test_reset |
.skip_reset: |
; 9. Process wait-done notifications, test for new wait requests. |
; Note: that must be done after steps 4 and 7 which could create new requests. |
; 9a. Call the worker function. |
call usb_process_wait_lists |
; 9b. If it reports that an asynchronous endpoint should be removed, |
; doorbell InterruptOnAsyncAdvance and schedule wakeup in 1s |
; (sometimes it just does not fire). |
test al, 1 shl CONTROL_PIPE |
jz @f |
mov edx, [esi+usb_controller.WaitPipeListAsync] |
mov [esi+usb_controller.WaitPipeRequestAsync], edx |
or dword [edi+EhciCommandReg], 1 shl 6 |
dbgstr 'async advance doorbell' |
cmp dword [esp], 100 |
jb @f |
mov dword [esp], 100 |
@@: |
; 9c. If it reports that a periodic endpoint should be removed, |
; save the current frame and schedule wakeup in 0.01 sec. |
test al, 1 shl INTERRUPT_PIPE |
jz @f |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [esi+usb_controller.WaitPipeRequestPeriodic], eax |
mov edx, [edi+EhciFrameIndexReg] |
mov [esi+usb_controller.StartWaitFrame], edx |
mov dword [esp], 1 ; wakeup in 0.01 sec for next test |
@@: |
; 10. Pop the return value, restore the stack after step 1 and return. |
pop eax |
pop ecx |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; This procedure is called in the USB thread from ehci_process_deferred |
; when EHCI IRQ handler has signalled that new IOC-packet was processed. |
; It scans all lists for completed packets and calls ehci_process_finalized_td |
; for those packets. |
proc ehci_process_updated_schedule |
; Important note: we cannot hold the list lock during callbacks, |
; because callbacks sometimes open and/or close pipes and thus acquire/release |
; the corresponding lock itself. |
; Fortunately, pipes can be finally freed only by another step of |
; ehci_process_deferred, so all pipes existing at the start of this function |
; will be valid while this function is running. Some pipes can be removed |
; from the corresponding list, some pipes can be inserted; insert/remove |
; functions guarantee that traversing one list yields all pipes that were in |
; that list at the beginning of the traversing (possibly with some new pipes, |
; possibly without some new pipes, that doesn't matter). |
push edi |
; 1. Process all Periodic lists. |
lea edi, [esi+ehci_controller.IntEDs-sizeof.ehci_controller+ehci_static_ep.SoftwarePart] |
lea ebx, [esi+ehci_controller.IntEDs+63*sizeof.ehci_static_ep-sizeof.ehci_controller+ehci_static_ep.SoftwarePart] |
@@: |
call ehci_process_updated_list |
cmp edi, ebx |
jnz @b |
; 2. Process the Control list. |
add edi, ehci_controller.ControlDelta |
call ehci_process_updated_list |
; 3. Process the Bulk list. |
call ehci_process_updated_list |
; 4. Return. |
pop edi |
ret |
endp |
; This procedure is called from ehci_process_updated_schedule, see comments there. |
; It processes one list, esi -> usb_controller, edi -> usb_static_ep, |
; and advances edi to next head. |
proc ehci_process_updated_list |
push ebx |
; 1. Perform the external loop over all pipes. |
mov ebx, [edi+usb_static_ep.NextVirt] |
.loop: |
cmp ebx, edi |
jz .done |
; store pointer to the next pipe in the stack |
push [ebx+usb_static_ep.NextVirt] |
; 2. For every pipe, perform the internal loop over all descriptors. |
; All descriptors are organized in the queue; we process items from the start |
; of the queue until a) the last descriptor (not the part of the queue itself) |
; or b) an active (not yet processed by the hardware) descriptor is reached. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
mov ebx, [ebx+usb_pipe.LastTD] |
push ebx |
mov ebx, [ebx+usb_gtd.NextVirt] |
.tdloop: |
; 3. For every descriptor, test active flag and check for end-of-queue; |
; if either of conditions holds, exit from the internal loop. |
cmp ebx, [esp] |
jz .tddone |
cmp byte [ebx+ehci_gtd.Token-ehci_gtd.SoftwarePart], 0 |
js .tddone |
; Release the queue lock while processing one descriptor: |
; callback function could (and often would) schedule another transfer. |
push ecx |
call mutex_unlock |
call ehci_process_updated_td |
pop ecx |
call mutex_lock |
jmp .tdloop |
.tddone: |
call mutex_unlock |
pop ebx |
; End of internal loop, restore pointer to the next pipe |
; and continue the external loop. |
pop ebx |
jmp .loop |
.done: |
pop ebx |
add edi, sizeof.ehci_static_ep |
ret |
endp |
; This procedure is called from ehci_process_updated_list, which is itself |
; called from ehci_process_updated_schedule, see comments there. |
; It processes one completed descriptor. |
; in: ebx -> usb_gtd, out: ebx -> next usb_gtd. |
proc ehci_process_updated_td |
; mov eax, [ebx+usb_gtd.Pipe] |
; cmp [eax+usb_pipe.Type], INTERRUPT_PIPE |
; jnz @f |
; DEBUGF 1,'K : finalized TD for pipe %x:\n',eax |
; lea eax, [ebx-ehci_gtd.SoftwarePart] |
; DEBUGF 1,'K : %x %x %x %x\n',[eax],[eax+4],[eax+8],[eax+12] |
; DEBUGF 1,'K : %x %x %x %x\n',[eax+16],[eax+20],[eax+24],[eax+28] |
;@@: |
; 1. Remove this descriptor from the list of descriptors for this pipe. |
call usb_unlink_td |
; 2. Calculate actual number of bytes transferred. |
mov eax, [ebx+ehci_gtd.Token-ehci_gtd.SoftwarePart] |
lea edx, [eax+eax] |
shr edx, 17 |
sub edx, [ebx+usb_gtd.Length] |
neg edx |
; 3. Check whether we need some special processing beyond notifying the driver. |
; Transfer errors require special processing. |
; Short packets require special processing if |
; a) this is not the last descriptor for transfer stage |
; (in this case we need to process subsequent descriptors for the stage too) |
; or b) the caller considers short transfers to be an error. |
; ehci_alloc_transfer sets bit 0 of ehci_gtd.Flags to 0 if short packet |
; in this descriptor requires special processing and to 1 otherwise. |
; If special processing is not needed, advance to 4 with ecx = 0. |
; Otherwise, go to 6. |
xor ecx, ecx |
test al, 40h |
jnz .error |
test byte [ebx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], 1 |
jnz .notify |
cmp edx, [ebx+usb_gtd.Length] |
jnz .special |
.notify: |
; 4. Either the descriptor in ebx was processed without errors, |
; or all necessary error actions were taken and ebx points to the last |
; related descriptor. |
; 4a. Test whether it is the last descriptor in the transfer |
; <=> it has an associated callback. |
mov eax, [ebx+usb_gtd.Callback] |
test eax, eax |
jz .nocallback |
; 4b. It has an associated callback; call it with corresponding parameters. |
stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \ |
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] |
jmp .callback |
.nocallback: |
; 4c. It is an intermediate descriptor. Add its length to the length |
; in the following descriptor. |
mov eax, [ebx+usb_gtd.NextVirt] |
add [eax+usb_gtd.Length], edx |
.callback: |
; 5. Free the current descriptor and return the next one. |
push [ebx+usb_gtd.NextVirt] |
stdcall ehci_free_td, ebx |
pop ebx |
ret |
.error: |
push ebx |
sub ebx, ehci_gtd.SoftwarePart |
DEBUGF 1,'K : TD failed:\n' |
DEBUGF 1,'K : %x %x %x %x\n',[ebx],[ebx+4],[ebx+8],[ebx+12] |
DEBUGF 1,'K : %x %x %x %x\n',[ebx+16],[ebx+20],[ebx+24],[ebx+28] |
pop ebx |
DEBUGF 1,'K : pipe now:\n' |
mov ecx, [ebx+usb_gtd.Pipe] |
sub ecx, ehci_pipe.SoftwarePart |
DEBUGF 1,'K : %x %x %x %x\n',[ecx],[ecx+4],[ecx+8],[ecx+12] |
DEBUGF 1,'K : %x %x %x %x\n',[ecx+16],[ecx+20],[ecx+24],[ecx+28] |
DEBUGF 1,'K : %x %x %x %x\n',[ecx+32],[ecx+36],[ecx+40],[ecx+44] |
.special: |
; 6. Special processing is needed. |
; 6a. Save the status and length. |
push edx |
push eax |
; 6b. Traverse the list of descriptors looking for the final descriptor |
; for this transfer. Free and unlink non-final descriptors. |
; Final descriptor will be freed in step 5. |
.look_final: |
call usb_is_final_packet |
jnc .found_final |
push [ebx+usb_gtd.NextVirt] |
stdcall ehci_free_td, ebx |
pop ebx |
call usb_unlink_td |
jmp .look_final |
.found_final: |
; 6c. Restore the status saved in 6a and transform it to the error code. |
; Notes: |
; * any USB transaction error results in Halted bit; if it is not set, |
; but we are here, it must be due to short packet; |
; * babble is considered a fatal USB transaction error, |
; other errors just lead to retrying the transaction; |
; if babble is detected, return the corresponding error; |
; * if several non-fatal errors have occured during transaction retries, |
; all corresponding bits are set. In this case, return some error code, |
; the order is quite arbitrary. |
pop eax ; status |
push USB_STATUS_UNDERRUN |
pop ecx |
test al, 40h ; not Halted? |
jz .know_error |
mov cl, USB_STATUS_OVERRUN |
test al, 10h ; Babble detected? |
jnz .know_error |
mov cl, USB_STATUS_BUFOVERRUN |
test al, 20h ; Data Buffer error? |
jnz .know_error |
mov cl, USB_STATUS_NORESPONSE |
test al, 8 ; Transaction Error? |
jnz .know_error |
mov cl, USB_STATUS_STALL |
.know_error: |
; 6d. If error code is USB_STATUS_UNDERRUN and the last TD allows short packets, |
; it is not an error; in this case, go to 4 with ecx = 0. |
cmp ecx, USB_STATUS_UNDERRUN |
jnz @f |
test byte [ebx+ehci_gtd.Flags-ehci_gtd.SoftwarePart], 1 |
jz @f |
xor ecx, ecx |
pop edx ; length |
jmp .notify |
@@: |
; 6e. Abort the entire transfer. |
; There are two cases: either there is only one transfer stage |
; (everything except control transfers), then ebx points to the last TD and |
; all previous TD were unlinked and dismissed (if possible), |
; or there are several stages (a control transfer) and ebx points to the last |
; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, |
; because Setup stage can not produce short packets); for Data stage, we need |
; to unlink and free (if possible) one more TD and advance ebx to the next one. |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .normal |
push ecx |
push [ebx+usb_gtd.NextVirt] |
stdcall ehci_free_td, ebx |
pop ebx |
call usb_unlink_td |
pop ecx |
.normal: |
; 6f. For bulk/interrupt transfers we have no choice but halt the queue, |
; the driver should intercede (through some API which is not written yet). |
; Control pipes normally recover at the next SETUP transaction (first stage |
; of any control transfer), so we hope on the best and just advance the queue |
; to the next transfer. (According to the standard, "A control pipe may also |
; support functional stall as well, but this is not recommended."). |
mov edx, [ebx+usb_gtd.Pipe] |
mov eax, [ebx+ehci_gtd.NextTD-ehci_gtd.SoftwarePart] |
or al, 1 |
mov [edx+ehci_pipe.Overlay.NextTD-ehci_pipe.SoftwarePart], eax |
mov [edx+ehci_pipe.Overlay.AlternateNextTD-ehci_pipe.SoftwarePart], eax |
cmp [edx+usb_pipe.Type], CONTROL_PIPE |
jz .control |
; Bulk/interrupt transfer; halt the queue. |
mov [edx+ehci_pipe.Overlay.Token-ehci_pipe.SoftwarePart], 40h |
pop edx |
jmp .notify |
; Control transfer. |
.control: |
and [edx+ehci_pipe.Overlay.Token-ehci_pipe.SoftwarePart], 0 |
dec [edx+ehci_pipe.Overlay.NextTD-ehci_pipe.SoftwarePart] |
pop edx |
jmp .notify |
endp |
; This procedure unlinks the pipe from the corresponding pipe list. |
; esi -> usb_controller, ebx -> usb_pipe |
proc ehci_unlink_pipe |
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE |
jnz @f |
test word [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart+2], 3FFFh |
jnz .interrupt_fs |
call ehci_hs_interrupt_list_unlink |
jmp .interrupt_common |
.interrupt_fs: |
call ehci_fs_interrupt_list_unlink |
.interrupt_common: |
@@: |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
mov edx, esi |
sub edx, eax |
cmp edx, sizeof.ehci_controller |
mov edx, [ebx+ehci_pipe.NextQH-ehci_pipe.SoftwarePart] |
jb .prev_is_static |
mov [eax+ehci_pipe.NextQH-ehci_pipe.SoftwarePart], edx |
ret |
.prev_is_static: |
mov [eax+ehci_static_ep.NextQH-ehci_static_ep.SoftwarePart], edx |
ret |
endp |
proc ehci_alloc_td |
push ebx |
mov ebx, ehci_gtd_mutex |
stdcall usb_allocate_common, sizeof.ehci_gtd |
test eax, eax |
jz @f |
add eax, ehci_gtd.SoftwarePart |
@@: |
pop ebx |
ret |
endp |
; This procedure is called from several places from main USB code and |
; frees all additional data associated with the transfer descriptor. |
; EHCI has no additional data, so just free ehci_gtd structure. |
proc ehci_free_td |
sub dword [esp+4], ehci_gtd.SoftwarePart |
jmp usb_free_common |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/bus/usb/hccommon.inc |
---|
0,0 → 1,472 |
; USB Host Controller support code: hardware-independent part, |
; common for all controller types. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; USB device must have at least 100ms of stable power before initializing can |
; proceed; one timer tick is 10ms, so enforce delay in 10 ticks |
USB_CONNECT_DELAY = 10 |
; USB requires at least 10 ms for reset signalling. Normally, this is one timer |
; tick. However, it is possible that we start reset signalling in the end of |
; interval between timer ticks and then we test time in the start of the next |
; interval; in this case, the delta between [timer_ticks] is 1, but the real |
; time passed is significantly less than 10 ms. To avoid this, we add an extra |
; tick; this guarantees that at least 10 ms have passed. |
USB_RESET_TIME = 2 |
; USB requires at least 10 ms of reset recovery, a delay between reset |
; signalling and any commands to device. Add an extra tick for the same reasons |
; as with the previous constant. |
USB_RESET_RECOVERY_TIME = 2 |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; Controller descriptor. |
; This structure represents the common (controller-independent) part |
; of a controller for the USB code. The corresponding controller-dependent |
; part *hci_controller is located immediately before usb_controller. |
struct usb_controller |
; Two following fields organize all controllers in the global linked list. |
Next dd ? |
Prev dd ? |
HardwareFunc dd ? |
; Pointer to usb_hardware_func structure with controller-specific functions. |
NumPorts dd ? |
; Number of ports in the root hub. |
SetAddressBuffer rb 8 |
; Buffer for USB control command SET_ADDRESS. |
ExistingAddresses rd 128/32 |
; Bitmask for 128 bits; bit i is cleared <=> address i is free for allocating |
; for new devices. Bit 0 is always set. |
; |
; The hardware is allowed to cache some data from hardware structures. |
; Regular operations are designed considering this, |
; but sometimes it is required to wait for synchronization of hardware cache |
; with modified structures in memory. |
; The code keeps two queues of pipes waiting for synchronization, |
; one for asynchronous (bulk/control) pipes, one for periodic pipes, hardware |
; cache is invalidated under different conditions for those types. |
; Both queues are organized in the same way, as single-linked lists. |
; There are three special positions: the head of list (new pipes are added |
; here), the first pipe to be synchronized at the current iteration, |
; the tail of list (all pipes starting from here are synchronized). |
WaitPipeListAsync dd ? |
WaitPipeListPeriodic dd ? |
; List heads. |
WaitPipeRequestAsync dd ? |
WaitPipeRequestPeriodic dd ? |
; Pending request to hardware to refresh cache for items from WaitPipeList*. |
; (Pointers to some items in WaitPipeList* or NULLs). |
ReadyPipeHeadAsync dd ? |
ReadyPipeHeadPeriodic dd ? |
; Items of RemovingList* which were released by hardware and are ready |
; for further processing. |
; (Pointers to some items in WaitPipeList* or NULLs). |
NewConnected dd ? |
; bit mask of recently connected ports of the root hub, |
; bit set = a device was recently connected to the corresponding port; |
; after USB_CONNECT_DELAY ticks of stable status these ports are moved to |
; PendingPorts |
NewDisconnected dd ? |
; bit mask of disconnected ports of the root hub, |
; bit set = a device in the corresponding port was disconnected, |
; disconnect processing is required. |
PendingPorts dd ? |
; bit mask of ports which are ready to be initialized |
ControlLock MUTEX ? |
; mutex which guards all operations with control queue |
BulkLock MUTEX ? |
; mutex which guards all operations with bulk queue |
PeriodicLock MUTEX ? |
; mutex which guards all operations with periodic queues |
WaitSpinlock: |
; spinlock guarding WaitPipeRequest/ReadyPipeHead (but not WaitPipeList) |
StartWaitFrame dd ? |
; USB frame number when WaitPipeRequest* was registered. |
ResettingHub dd ? |
; Pointer to usb_hub responsible for the currently resetting port, if any. |
; NULL for the root hub. |
ResettingPort db ? |
; Port that is currently resetting, 0-based. |
ResettingSpeed db ? |
; Speed of currently resetting device. |
ResettingStatus db ? |
; Status of port reset. 0 = no port is resetting, -1 = reset failed, |
; 1 = reset in progress, 2 = reset recovery in progress. |
rb 1 ; alignment |
ResetTime dd ? |
; Time when reset signalling or reset recovery has been started. |
ConnectedTime rd 16 |
; Time, in timer ticks, when the port i has signalled the connect event. |
; Valid only if bit i in NewConnected is set. |
DevicesByPort rd 16 |
; Pointer to usb_pipe for zero endpoint (which serves as device handle) |
; for each port. |
ends |
; Interface-specific data. Several interfaces of one device can operate |
; independently, each is controlled by some driver and is identified by |
; some driver-specific data passed as is to the driver. |
struct usb_interface_data |
DriverData dd ? |
; Passed as is to the driver. |
DriverFunc dd ? |
; Pointer to USBSRV structure for the driver. |
ends |
; Device-specific data. |
struct usb_device_data |
PipeListLock MUTEX |
; Lock guarding OpenedPipeList. Must be the first item of the structure, |
; the code passes pointer to usb_device_data as is to mutex_lock/unlock. |
OpenedPipeList rd 2 |
; List of all opened pipes for the device. |
; Used when the device is disconnected, so all pipes should be closed. |
ClosedPipeList rd 2 |
; List of all closed, but still valid pipes for the device. |
; A pipe closed with USBClosePipe is just deallocated, |
; but a pipe closed due to disconnect must remain valid until driver-provided |
; disconnect handler returns; this list links all such pipes to deallocate them |
; after disconnect processing. |
NumPipes dd ? |
; Number of not-yet-closed pipes. |
Hub dd ? |
; NULL if connected to the root hub, pointer to usb_hub otherwise. |
Port db ? |
; Port on the hub, zero-based. |
DeviceDescrSize db ? |
; Size of device descriptor. |
NumInterfaces db ? |
; Number of interfaces. |
Speed db ? |
; Device speed, one of USB_SPEED_*. |
ConfigDataSize dd ? |
; Total size of data associated with the configuration descriptor |
; (including the configuration descriptor itself); |
Interfaces dd ? |
; Offset from the beginning of this structure to Interfaces field. |
; Variable-length fields: |
; DeviceDescriptor: |
; device descriptor starts here |
; ConfigDescriptor = DeviceDescriptor + DeviceDescrSize |
; configuration descriptor with all associated data |
; Interfaces = ALIGN_UP(ConfigDescriptor + ConfigDataSize, 4) |
; array of NumInterfaces elements of type usb_interface_data |
ends |
usb_device_data.DeviceDescriptor = sizeof.usb_device_data |
; Description of controller-specific data and functions. |
struct usb_hardware_func |
ID dd ? ; '*HCI' |
DataSize dd ? ; sizeof(*hci_controller) |
Init dd ? |
; Initialize controller-specific part of controller data. |
; in: eax -> *hci_controller to initialize, [ebp-4] = (bus shl 8) + devfn |
; out: eax = 0 <=> failed, otherwise eax -> usb_controller |
ProcessDeferred dd ? |
; Called regularly from the main loop of USB thread |
; (either due to timeout from a previous call, or due to explicit wakeup). |
; in: esi -> usb_controller |
; out: eax = maximum timeout for next call (-1 = infinity) |
SetDeviceAddress dd ? |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
GetDeviceAddress dd ? |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = address |
PortDisable dd ? |
; Disable the given port in the root hub. |
; in: esi -> usb_controller, ecx = port (zero-based) |
InitiateReset dd ? |
; Start reset signalling on the given port. |
; in: esi -> usb_controller, ecx = port (zero-based) |
SetEndpointPacketSize dd ? |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
AllocPipe dd ? |
; out: eax = pointer to allocated usb_pipe |
FreePipe dd ? |
; void stdcall with one argument = pointer to previously allocated usb_pipe |
InitPipe dd ? |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
UnlinkPipe dd ? |
; esi -> usb_controller, ebx -> usb_pipe |
AllocTD dd ? |
; out: eax = pointer to allocated usb_gtd |
FreeTD dd ? |
; void stdcall with one argument = pointer to previously allocated usb_gtd |
AllocTransfer dd ? |
; Allocate and initialize one stage of a transfer. |
; ebx -> usb_pipe, other parameters are passed through the stack: |
; buffer,size = data to transfer |
; flags = same as in usb_open_pipe: |
; bit 0 = allow short transfer, other bits reserved |
; td = pointer to the current end-of-queue descriptor |
; direction = |
; 0000b for normal transfers, |
; 1000b for control SETUP transfer, |
; 1101b for control OUT transfer, |
; 1110b for control IN transfer |
; returns eax = pointer to the new end-of-queue descriptor |
; (not included in the queue itself) or 0 on error |
InsertTransfer dd ? |
; Activate previously initialized transfer (maybe with multiple stages). |
; esi -> usb_controller, ebx -> usb_pipe, |
; [esp+4] -> first usb_gtd for the transfer, |
; ecx -> last descriptor for the transfer |
NewDevice dd ? |
; Initiate configuration of a new device (create pseudo-pipe describing that |
; device and call usb_new_device). |
; esi -> usb_controller, eax = speed (one of USB_SPEED_* constants). |
ends |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Initializes one controller, called by usb_init for every controller. |
; edi -> usb_hardware_func, 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 |
; 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 |
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 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 |
; 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 |
@@: |
push 4 |
pop edx |
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. |
or [ebx+usb_pipe.NextWait], -1 |
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED |
jz .nodisconnect |
call usb_pipe_closed |
jmp .continue |
.nodisconnect: |
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 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/bus/usb/hub.inc |
---|
0,0 → 1,1237 |
; 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. |
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 |
; Hubs use one IN interrupt endpoint for polling the device |
; 1. Locate the descriptor of the interrupt endpoint. |
; Loop over all descriptors owned by this interface. |
.lookep: |
; 1a. Skip the current descriptor. |
movzx eax, [edx+usb_descr.bLength] |
add edx, eax |
sub ecx, eax |
jb .errorep |
; 1b. Length of data left must be at least sizeof.usb_endpoint_descr. |
cmp ecx, sizeof.usb_endpoint_descr |
jb .errorep |
; 1c. 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 |
; 1d. Ignore all interface-related descriptors except endpoint descriptor. |
cmp [edx+usb_endpoint_descr.bDescriptorType], USB_ENDPOINT_DESCR |
jnz .lookep |
; 1e. Length of endpoint descriptor must be at least sizeof.usb_endpoint_descr. |
cmp [edx+usb_endpoint_descr.bLength], sizeof.usb_endpoint_descr |
jb .errorep |
; 1f. 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. |
; 2. Allocate memory for the hub descriptor. |
; Maximum length (assuming 255 downstream ports) is 40 bytes. |
; 2a. Save registers. |
push edx |
; 2b. Call the allocator. |
push 40 |
pop eax |
call malloc |
; 2c. Restore registers. |
pop ecx |
; 2d. If failed, say something to the debug board and return error. |
test eax, eax |
jz .nomemory |
; 2e. Store the pointer in esi. xchg eax,r32 is one byte shorter than mov. |
xchg esi, eax |
; 3. Open a pipe for the status endpoint with descriptor found in step 1. |
mov ebx, [.pipe] |
movzx eax, [ecx+usb_endpoint_descr.bEndpointAddress] |
movzx edx, [ecx+usb_endpoint_descr.bInterval] |
movzx ecx, [ecx+usb_endpoint_descr.wMaxPacketSize] |
stdcall usb_open_pipe, ebx, eax, ecx, INTERRUPT_PIPE, edx |
; If failed, free the memory allocated in step 2, |
; say something to the debug board and return error. |
test eax, eax |
jz .free |
; 4. Send control query for the hub descriptor, |
; pass status pipe as a callback parameter, |
; allow short packets. |
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 |
; 5. If failed, free the memory allocated in step 2, |
; 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(2*(floor(NumPorts/8)+1),4)+8*NumPorts. |
lea edx, [sizeof.usb_hub+(edx-sizeof.usb_hub_descr)*2+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] |
; 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 |
mov ecx, [eax+usb_hub.NumPorts] |
shr ecx, 3 |
inc ecx |
stdcall usb_normal_transfer_async, [eax+usb_hub.StatusPipe], \ |
[eax+usb_hub.StatusChangePtr], ecx, 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] |
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 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/bus/usb/init.inc |
---|
0,0 → 1,250 |
; 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. |
; Initializes the USB subsystem. |
proc usb_init |
; 1. Initialize all locks. |
mov ecx, usb_controllers_list_mutex |
call mutex_init |
mov ecx, usb1_ep_mutex |
call mutex_init |
mov ecx, usb_gtd_mutex |
call mutex_init |
mov ecx, ehci_ep_mutex |
call mutex_init |
mov ecx, ehci_gtd_mutex |
call mutex_init |
; 2. Kick off BIOS from all USB controllers, calling the corresponding function |
; *hci_kickoff_bios. Also count USB controllers for the next step. |
; Note: USB1 companion(s) must go before the corresponding EHCI controller, |
; 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 eax, uhci_kickoff_bios |
cmp byte [esi+PCIDEV.class], 0x00 |
jz .do_kickoff |
mov eax, ohci_kickoff_bios |
cmp byte [esi+PCIDEV.class], 0x10 |
jz .do_kickoff |
mov eax, ehci_kickoff_bios |
cmp byte [esi+PCIDEV.class], 0x20 |
jnz .kickoff |
.do_kickoff: |
inc dword [esp] |
call eax |
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 |
mov edi, ehci_hardware_func |
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 |
mov edi, uhci_hardware_func |
cmp [eax+PCIDEV.class], 0x0C0300 |
jz @f |
mov edi, ohci_hardware_func |
cmp [eax+PCIDEV.class], 0x0C0310 |
jnz .scan_usb1 |
@@: |
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 |
push 1 |
pop ebx |
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: |
mov [check_idle_semaphore], 5 ; we really, really need a normal scheduler |
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 "hccommon.inc" |
include "pipe.inc" |
include "ohci.inc" |
include "uhci.inc" |
include "ehci.inc" |
include "protocol.inc" |
include "hub.inc" |
include "scheduler.inc" |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/bus/usb/memory.inc |
---|
0,0 → 1,215 |
; 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, the specific allocator is quite easy to write: |
; allocate one page, split into blocks, maintain the single-linked |
; list of all free blocks in each page. |
; Note: size must be a multiple of required alignment. |
; Data for one pool: dd pointer to the first page, MUTEX lock. |
uglobal |
; Structures in UHCI and OHCI have equal sizes. |
; Thus, functions and data for allocating/freeing can be shared; |
; we keep them here rather than in controller-specific files. |
align 4 |
; Data for UHCI and OHCI endpoints pool. |
usb1_ep_first_page dd ? |
usb1_ep_mutex MUTEX |
; Data for UHCI and OHCI general transfer descriptors pool. |
usb_gtd_first_page dd ? |
usb_gtd_mutex MUTEX |
endg |
; sanity check: structures in UHCI and OHCI should be the same for allocation |
if (sizeof.ohci_pipe=sizeof.uhci_pipe)&(ohci_pipe.SoftwarePart=uhci_pipe.SoftwarePart) |
; Allocates one endpoint structure for UHCI/OHCI. |
; Returns pointer to software part (usb_pipe) in eax. |
proc usb1_allocate_endpoint |
push ebx |
mov ebx, usb1_ep_mutex |
stdcall usb_allocate_common, sizeof.ohci_pipe |
test eax, eax |
jz @f |
add eax, ohci_pipe.SoftwarePart |
@@: |
pop ebx |
ret |
endp |
; Free one endpoint structure for UHCI/OHCI. |
; Stdcall with one argument, pointer to software part (usb_pipe). |
proc usb1_free_endpoint |
sub dword [esp+4], ohci_pipe.SoftwarePart |
jmp usb_free_common |
endp |
else |
; sanity check continued |
.err allocate_endpoint/free_endpoint must be different for OHCI and UHCI |
end if |
; sanity check: structures in UHCI and OHCI should be the same for allocation |
if (sizeof.ohci_gtd=sizeof.uhci_gtd)&(ohci_gtd.SoftwarePart=uhci_gtd.SoftwarePart) |
; Allocates one general transfer descriptor structure for UHCI/OHCI. |
; Returns pointer to software part (usb_gtd) in eax. |
proc usb1_allocate_general_td |
push ebx |
mov ebx, usb_gtd_mutex |
stdcall usb_allocate_common, sizeof.ohci_gtd |
test eax, eax |
jz @f |
add eax, ohci_gtd.SoftwarePart |
@@: |
pop ebx |
ret |
endp |
; Free one general transfer descriptor structure for UHCI/OHCI. |
; Stdcall with one argument, pointer to software part (usb_gtd). |
proc usb1_free_general_td |
sub dword [esp+4], ohci_gtd.SoftwarePart |
jmp usb_free_common |
endp |
else |
; sanity check continued |
.err allocate_general_td/free_general_td must be different for OHCI and UHCI |
end if |
; Allocator for fixed-size blocks: allocate a block. |
; [ebx-4] = pointer to the first page, ebx = pointer to MUTEX structure. |
proc usb_allocate_common |
push edi ; save used register to be stdcall |
virtual at esp |
dd ? ; saved edi |
dd ? ; return address |
.size dd ? |
end virtual |
; 1. Take the lock. |
mov ecx, ebx |
call mutex_lock |
; 2. Find the first allocated page with a free block, if any. |
; 2a. Initialize for the loop. |
mov edx, ebx |
.pageloop: |
; 2b. Get the next page, keeping the current in eax. |
mov eax, edx |
mov edx, [edx-4] |
; 2c. If there is no next page, we're out of luck; go to 4. |
test edx, edx |
jz .newpage |
add edx, 0x1000 |
@@: |
; 2d. Get the pointer to the first free block on this page. |
; If there is no free block, continue to 2b. |
mov eax, [edx-8] |
test eax, eax |
jz .pageloop |
; 2e. Get the pointer to the next free block. |
mov ecx, [eax] |
; 2f. Update the pointer to the first free block from eax to ecx. |
; Normally [edx-8] still contains eax, if so, atomically set it to ecx |
; and proceed to 3. |
; However, the price of simplicity of usb_free_common (in particular, it |
; doesn't take the lock) is that [edx-8] could (rarely) be changed while |
; we processed steps 2d+2e. If so, return to 2d and retry. |
lock cmpxchg [edx-8], ecx |
jnz @b |
.return: |
; 3. Release the lock taken in step 1 and return. |
push eax |
mov ecx, ebx |
call mutex_unlock |
pop eax |
pop edi ; restore used register to be stdcall |
ret 4 |
.newpage: |
; 4. Allocate a new page. |
push eax |
stdcall kernel_alloc, 0x1000 |
pop edx |
; If failed, say something to the debug board and return zero. |
test eax, eax |
jz .nomemory |
; 5. Add the new page to the tail of list of allocated pages. |
mov [edx-4], eax |
; 6. Initialize two service dwords in the end of page: |
; first free block is (start of page) + (block size) |
; (we will return first block at (start of page), so consider it allocated), |
; no next page. |
mov edx, eax |
lea edi, [eax+0x1000-8] |
add edx, [.size] |
mov [edi], edx |
and dword [edi+4], 0 |
; 7. All blocks starting from edx are free; join them in a single-linked list. |
@@: |
mov ecx, edx |
add edx, [.size] |
mov [ecx], edx |
cmp edx, edi |
jbe @b |
sub ecx, [.size] |
and dword [ecx], 0 |
; 8. Return (start of page). |
jmp .return |
.nomemory: |
dbgstr 'no memory for USB descriptor' |
xor eax, eax |
jmp .return |
endp |
; Allocator for fixed-size blocks: free a block. |
proc usb_free_common |
push ecx edx |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.block dd ? |
end virtual |
; Insert the given block to the head of free blocks in this page. |
mov ecx, [.block] |
mov edx, ecx |
or edx, 0xFFF |
@@: |
mov eax, [edx+1-8] |
mov [ecx], eax |
lock cmpxchg [edx+1-8], ecx |
jnz @b |
pop edx ecx |
ret 4 |
endp |
; Helper procedure for OHCI: translate physical address in ecx |
; of some transfer descriptor to linear address. |
proc usb_td_to_virt |
; Traverse all pages used for transfer descriptors, looking for the one |
; with physical address as in ecx. |
mov eax, [usb_gtd_first_page] |
@@: |
test eax, eax |
jz .zero |
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 |
/kernel/trunk/bus/usb/ohci.inc |
---|
0,0 → 1,1601 |
; Code for OHCI controllers. |
; Note: it should be moved to an external driver, |
; it was convenient to have this code compiled into the kernel during initial |
; development, but there are no reasons to keep it here. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; OHCI register declarations |
; All of the registers should be read and written as Dwords. |
; Partition 1. Control and Status registers. |
OhciRevisionReg = 0 |
OhciControlReg = 4 |
OhciCommandStatusReg = 8 |
OhciInterruptStatusReg = 0Ch |
OhciInterruptEnableReg = 10h |
OhciInterruptDisableReg = 14h |
; Partition 2. Memory Pointer registers. |
OhciHCCAReg = 18h |
OhciPeriodCurrentEDReg = 1Ch |
OhciControlHeadEDReg = 20h |
OhciControlCurrentEDReg = 24h |
OhciBulkHeadEDReg = 28h |
OhciBulkCurrentEDReg = 2Ch |
OhciDoneHeadReg = 30h |
; Partition 3. Frame Counter registers. |
OhciFmIntervalReg = 34h |
OhciFmRemainingReg = 38h |
OhciFmNumberReg = 3Ch |
OhciPeriodicStartReg = 40h |
OhciLSThresholdReg = 44h |
; Partition 4. Root Hub registers. |
OhciRhDescriptorAReg = 48h |
OhciRhDescriptorBReg = 4Ch |
OhciRhStatusReg = 50h |
OhciRhPortStatusReg = 54h |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; OHCI-specific part of a pipe descriptor. |
; * This structure corresponds to the Endpoint Descriptor aka ED from the OHCI |
; specification. |
; * The hardware requires 16-bytes alignment of the hardware part. |
; Since the allocator (usb_allocate_common) allocates memory sequentially |
; from page start (aligned on 0x1000 bytes), size of the structure must be |
; divisible by 16. |
struct ohci_pipe |
; All addresses are physical. |
Flags dd ? |
; 1. Lower 7 bits (bits 0-6) are FunctionAddress. This is the USB address of |
; the function containing the endpoint that this ED controls. |
; 2. Next 4 bits (bits 7-10) are EndpointNumber. This is the USB address of |
; the endpoint within the function. |
; 3. Next 2 bits (bits 11-12) are Direction. This 2-bit field indicates the |
; direction of data flow: 1 = IN, 2 = OUT. If neither IN nor OUT is |
; specified, then the direction is determined from the PID field of the TD. |
; For CONTROL endpoints, the transfer direction is different |
; for different transfers, so the value of this field is 0 |
; (3 would have the same effect) and the actual direction |
; of one transfer is encoded in the Transfer Descriptor. |
; 4. Next bit (bit 13) is Speed bit. It indicates the speed of the endpoint: |
; full-speed (S = 0) or low-speed (S = 1). |
; 5. Next bit (bit 14) is sKip bit. When this bit is set, the hardware |
; continues on to the next ED on the list without attempting access |
; to the TD queue or issuing any USB token for the endpoint. |
; Always cleared. |
; 6. Next bit (bit 15) is Format bit. It must be 0 for Control, Bulk and |
; Interrupt endpoints and 1 for Isochronous endpoints. |
; 7. Next 11 bits (bits 16-26) are MaximumPacketSize. This field indicates |
; the maximum number of bytes that can be sent to or received from the |
; endpoint in a single data packet. |
TailP dd ? |
; Physical address of the tail descriptor in the TD queue. |
; The descriptor itself is not in the queue. See also HeadP. |
HeadP dd ? |
; 1. First bit (bit 0) is Halted bit. This bit is set by the hardware to |
; indicate that processing of the TD queue on the endpoint is halted. |
; 2. Second bit (bit 1) is toggleCarry bit. Whenever a TD is retired, this |
; bit is written to contain the last data toggle value from the retired TD. |
; 3. Next two bits (bits 2-3) are reserved and always zero. |
; 4. With masked 4 lower bits, this is HeadP itself: physical address of the |
; head descriptor in the TD queue, that is, next TD to be processed for this |
; endpoint. Note that a TD must be 16-bytes aligned. |
; Empty queue is characterized by the condition HeadP == TailP. |
NextED dd ? |
; If nonzero, then this entry is a physical address of the next ED to be |
; processed. See also the description before NextVirt field of the usb_pipe |
; structure. Additionally to that description, the following is specific for |
; the OHCI controller: |
; * n=5, N=32, there are 32 "leaf" periodic lists. |
; * The 1ms periodic list also serves Isochronous endpoints, which should be |
; in the end of the list. |
; * There is no "next" list for Bulk and Control lists, they are processed |
; separately from others. |
; * There is no "next" list for Periodic list for 1ms interval. |
SoftwarePart rd sizeof.usb_pipe/4 |
; Software part, common for all controllers. |
ends |
if sizeof.ohci_pipe mod 16 |
.err ohci_pipe must be 16-bytes aligned |
end if |
; This structure describes the static head of every list of pipes. |
; The hardware requires 16-bytes alignment of this structure. |
; All instances of this structure are located sequentially in uhci_controller, |
; uhci_controller is page-aligned, so it is sufficient to make this structure |
; 16-bytes aligned and verify that the first instance is 16-bytes aligned |
; inside uhci_controller. |
struct ohci_static_ep |
Flags dd ? |
; Same as ohci_pipe.Flags. |
; sKip bit is set, so the hardware ignores other fields except NextED. |
dd ? |
; Corresponds to ohci_pipe.TailP. Not used. |
NextList dd ? |
; Virtual address of the next list. |
NextED dd ? |
; Same as ohci_pipe.NextED. |
SoftwarePart rd sizeof.usb_static_ep/4 |
; Software part, common for all controllers. |
dd ? |
; Padding for 16-bytes alignment. |
ends |
if sizeof.ohci_static_ep mod 16 |
.err ohci_static_ep must be 16-bytes aligned |
end if |
; OHCI-specific part of controller data. |
; * The structure describes the memory area used for controller data, |
; additionally to the registers of the controller. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 256 bytes and corresponds to |
; the HCCA from OHCI specification. |
; * The hardware requires 256-bytes alignment of the hardware part, so |
; the entire descriptor must be 256-bytes aligned. |
; This structure is allocated with kernel_alloc (see usb_init_controller), |
; this gives page-aligned data. |
; * The controller is described by both ohci_controller and usb_controller |
; structures, for each controller there is one ohci_controller and one |
; usb_controller structure. These structures are located sequentially |
; in the memory: beginning from some page start, there is ohci_controller |
; structure - this enforces hardware alignment requirements - and then |
; usb_controller structure. |
; * The code keeps pointer to usb_controller structure. The ohci_controller |
; structure is addressed as [ptr + ohci_controller.field - sizeof.ohci_controller]. |
struct ohci_controller |
; ------------------------------ hardware fields ------------------------------ |
InterruptTable rd 32 |
; Pointers to interrupt EDs. The hardware starts processing of periodic lists |
; within the frame N from the ED pointed to by [InterruptTable+(N and 31)*4]. |
; See also the description of periodic lists inside ohci_pipe structure. |
FrameNumber dw ? |
; The current frame number. This field is written by hardware only. |
; This field is read by ohci_process_deferred and ohci_irq to |
; communicate when control/bulk processing needs to be temporarily |
; stopped/restarted. |
dw ? |
; Padding. Written as zero at every update of FrameNumber. |
DoneHead dd ? |
; Physical pointer to the start of Done Queue. |
; When the hardware updates this field, it sets bit 0 to one if there is |
; unmasked interrupt pending. |
rb 120 |
; Reserved for the hardware. |
; ------------------------------ software fields ------------------------------ |
IntEDs ohci_static_ep |
rb 62 * sizeof.ohci_static_ep |
; Heads of 63 Periodic lists, see the description in usb_pipe. |
ControlED ohci_static_ep |
; Head of Control list, see the description in usb_pipe. |
BulkED ohci_static_ep |
; Head of Bulk list, see the description in usb_pipe. |
MMIOBase dd ? |
; Virtual address of memory-mapped area with OHCI registers OhciXxxReg. |
PoweredUp db ? |
; 1 in normal work, 0 during early phases of the initialization. |
; This field is initialized to zero during memory allocation |
; (see usb_init_controller), set to one by ohci_init when ports of the root hub |
; are powered up, so connect/disconnect events can be handled. |
rb 3 ; alignment |
DoneList dd ? |
; List of descriptors which were processed by the controller and now need |
; to be finalized. |
DoneListEndPtr dd ? |
; Pointer to dword which should receive a pointer to the next item in DoneList. |
; If DoneList is empty, this is a pointer to DoneList itself; |
; otherwise, this is a pointer to NextTD field of the last item in DoneList. |
ends |
if ohci_controller.IntEDs mod 16 |
.err Static endpoint descriptors must be 16-bytes aligned inside ohci_controller |
end if |
; OHCI general transfer descriptor. |
; * The structure describes transfers to be performed on Control, Bulk or |
; Interrupt endpoints. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 16 bytes and corresponds to |
; the General Transfer Descriptor aka general TD from OHCI specification. |
; * The hardware requires 16-bytes alignment of the hardware part, so |
; the entire descriptor must be 16-bytes aligned. Since the allocator |
; (usb_allocate_common) allocates memory sequentially from page start |
; (aligned on 0x1000 bytes), size of the structure must be divisible by 16. |
struct ohci_gtd |
; ------------------------------ hardware fields ------------------------------ |
; All addresses in this part are physical. |
Flags dd ? |
; 1. Lower 18 bits (bits 0-17) are ignored and not modified by the hardware. |
; 2. Next bit (bit 18) is bufferRounding bit. If this bit is 0, then the last |
; data packet must exactly fill the defined data buffer. If this bit is 1, |
; then the last data packet may be smaller than the defined buffer without |
; causing an error condition on the TD. |
; 3. Next 2 bits (bits 19-20) are Direction field. This field indicates the |
; direction of data flow. If the Direction field in the ED is OUT or IN, |
; this field is ignored and the direction from the ED is used instead. |
; Otherwise, 0 = SETUP, 1 = OUT, 2 = IN, 3 is reserved. |
; 4. Next 3 bits (bits 21-23) are DelayInterrupt field. This field contains |
; the interrupt delay count for this TD. When a TD is complete, the hardware |
; may wait up to DelayInterrupt frames before generating an interrupt. |
; If DelayInterrupt is 7 (maximum possible), then there is no interrupt |
; associated with completion of this TD. |
; 5. Next 2 bits (bits 24-25) are DataToggle field. This field is used to |
; generate/compare the data PID value (DATA0 or DATA1). It is updated after |
; each successful transmission/reception of a data packet. The bit 25 |
; is 0 when the data toggle value is acquired from the toggleCarry field in |
; the ED and 1 when the data toggle value is taken from the bit 24. |
; 6. Next 2 bits (bits 26-27) are ErrorCount field. For each transmission |
; error, this value is incremented. If ErrorCount is 2 and another error |
; occurs, the TD is retired with error. When a transaction completes without |
; error, ErrorCount is reset to 0. |
; 7. Upper 4 bits (bits 28-31) are ConditionCode field. This field contains |
; the status of the last attempted transaction, one of USB_STATUS_* values. |
CurBufPtr dd ? |
; Physical address of the next memory location that will be accessed for |
; transfer to/from the endpoint. 0 means zero-length data packet or that all |
; bytes have been transferred. |
NextTD dd ? |
; This field has different meanings depending on the status of the descriptor. |
; When the descriptor is queued for processing, but not yet processed: |
; Physical address of the next TD for the endpoint. |
; When the descriptor is processed by hardware, but not yet by software: |
; Physical address of the previous processed TD. |
; When the descriptor is processed by the IRQ handler, but not yet completed: |
; Virtual pointer to the next processed TD. |
BufEnd dd ? |
; Physical address of the last byte in the buffer for this TD. |
dd ? ; padding for 16-bytes alignment |
SoftwarePart rd sizeof.usb_gtd/4 |
; Common part for all controllers. |
ends |
if sizeof.ohci_gtd mod 16 |
.err ohci_gtd must be 16-bytes aligned |
end if |
; OHCI isochronous transfer descriptor. |
; * The structure describes transfers to be performed on Isochronous endpoints. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 32 bytes and corresponds to |
; the Isochronous Transfer Descriptor aka isochronous TD from OHCI |
; specification. |
; * The hardware requires 32-bytes alignment of the hardware part, so |
; the entire descriptor must be 32-bytes aligned. |
; * The isochronous endpoints are not supported yet, so only hardware part is |
; defined at the moment. |
struct ohci_itd |
StartingFrame dw ? |
; This field contains the low order 16 bits of the frame number in which the |
; first data packet of the Isochronous TD is to be sent. |
Flags dw ? |
; 1. Lower 5 bits (bits 0-4) are ignored and not modified by the hardware. |
; 2. Next 3 bits (bits 5-7) are DelayInterrupt field. |
; 3. Next 3 bits (bits 8-10) are FrameCount field. The TD describes |
; FrameCount+1 data packets. |
; 4. Next bit (bit 11) is ignored and not modified by the hardware. |
; 5. Upper 4 bits (bits 12-15) are ConditionCode field. This field contains |
; the completion code, one of USB_STATUS_* values, when the TD is moved to |
; the Done Queue. |
BufPage0 dd ? |
; Lower 12 bits are ignored and not modified by the hardware. |
; With masked 12 bits this field is the physical page containing all buffers. |
NextTD dd ? |
; Physical address of the next TD in the transfer queue. |
BufEnd dd ? |
; Physical address of the last byte in the buffer. |
OffsetArray rw 8 |
; Initialized by software, read by hardware: Offset for packet 0..7. |
; Used to determine size and starting address of an isochronous data packet. |
; Written by hardware, read by software: PacketStatusWord for packet 0..7. |
; Contains completion code and, if applicable, size received for an isochronous |
; data packet. |
ends |
; Description of OHCI-specific data and functions for |
; controller-independent code. |
; Implements the structure usb_hardware_func from hccommon.inc for OHCI. |
iglobal |
align 4 |
ohci_hardware_func: |
dd 'OHCI' |
dd sizeof.ohci_controller |
dd ohci_init |
dd ohci_process_deferred |
dd ohci_set_device_address |
dd ohci_get_device_address |
dd ohci_port_disable |
dd ohci_new_port.reset |
dd ohci_set_endpoint_packet_size |
dd usb1_allocate_endpoint |
dd usb1_free_endpoint |
dd ohci_init_pipe |
dd ohci_unlink_pipe |
dd usb1_allocate_general_td |
dd usb1_free_general_td |
dd ohci_alloc_transfer |
dd ohci_insert_transfer |
dd ohci_new_device |
endg |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Controller-specific initialization function. |
; Called from usb_init_controller. Initializes the hardware and |
; OHCI-specific parts of software structures. |
; eax = pointer to ohci_controller to be initialized |
; [ebp-4] = pcidevice |
proc ohci_init |
; inherit some variables from the parent (usb_init_controller) |
.devfn equ ebp - 4 |
.bus equ ebp - 3 |
; 1. Store pointer to ohci_controller for further use. |
push eax |
mov edi, eax |
; 2. Initialize hardware fields of ohci_controller. |
; Namely, InterruptTable needs to be initialized with |
; physical addresses of heads of first 32 Periodic lists. |
; Note that all static heads fit in one page, so one call |
; to get_pg_addr is sufficient. |
if (ohci_controller.IntEDs / 0x1000) <> (ohci_controller.BulkED / 0x1000) |
.err assertion failed |
end if |
if ohci_controller.IntEDs >= 0x1000 |
.err assertion failed |
end if |
lea esi, [eax+ohci_controller.IntEDs+32*sizeof.ohci_static_ep] |
call get_pg_addr |
add eax, ohci_controller.IntEDs |
push 32 |
pop ecx |
mov edx, ecx |
@@: |
stosd |
add eax, sizeof.ohci_static_ep |
loop @b |
; 3. Initialize static heads ohci_controller.IntEDs, .ControlED, .BulkED. |
; Use the loop over groups: first group consists of first 32 Periodic |
; descriptors, next group consists of next 16 Periodic descriptors, |
; ..., last group consists of the last Periodic descriptor. |
; 3a. Prepare for the loop. |
; make edi point to start of ohci_controller.IntEDs, |
; other registers are already set. |
; -128 fits in one byte, +128 does not fit. |
sub edi, -128 |
; 3b. Loop over groups. On every iteration: |
; edx = size of group, edi = pointer to the current group, |
; esi = pointer to the next group, eax = physical address of the next group. |
.init_static_eds: |
; 3c. Get the size of the next group. |
shr edx, 1 |
; 3d. Exit the loop if there is no next group. |
jz .init_static_eds_done |
; 3e. Initialize the first half of the current group. |
; Advance edi to the second half. |
push eax esi |
call ohci_init_static_ep_group |
pop esi eax |
; 3f. Initialize the second half of the current group |
; with the same values. |
; Advance edi to the next group, esi/eax to the next of the next group. |
call ohci_init_static_ep_group |
jmp .init_static_eds |
.init_static_eds_done: |
; 3g. Initialize the head of the last Periodic list. |
xor eax, eax |
xor esi, esi |
call ohci_init_static_endpoint |
; 3i. Initialize the heads of Control and Bulk lists. |
call ohci_init_static_endpoint |
call ohci_init_static_endpoint |
; 4. Create a virtual memory area to talk with the controller. |
; 4a. Enable memory & bus master access. |
mov ch, [.bus] |
mov cl, 0 |
mov eax, ecx |
mov bh, [.devfn] |
mov bl, 4 |
call pci_read_reg |
or al, 6 |
xchg eax, ecx |
call pci_write_reg |
; 4b. Read memory base address. |
mov ah, [.bus] |
mov al, 2 |
mov bl, 10h |
call pci_read_reg |
and al, not 0Fh |
; 4c. Create mapping for physical memory. 256 bytes are sufficient. |
stdcall map_io_mem, eax, 100h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .fail |
stosd ; fill ohci_controller.MMIOBase |
xchg eax, edi |
; now edi = MMIOBase |
; 5. Reset the controller if needed. |
; 5a. Check operational state. |
; 0 = reset, 1 = resume, 2 = operational, 3 = suspended |
mov eax, [edi+OhciControlReg] |
and al, 3 shl 6 |
cmp al, 2 shl 6 |
jz .operational |
; 5b. State is not operational, reset is needed. |
.reset: |
; 5c. Save FmInterval register. |
pushd [edi+OhciFmIntervalReg] |
; 5d. Issue software reset and wait up to 10ms, checking status every 1 ms. |
push 1 |
pop ecx |
push 10 |
pop edx |
mov [edi+OhciCommandStatusReg], ecx |
@@: |
mov esi, ecx |
call delay_ms |
test [edi+OhciCommandStatusReg], ecx |
jz .resetdone |
dec edx |
jnz @b |
pop eax |
dbgstr 'controller reset timeout' |
jmp .fail_unmap |
.resetdone: |
; 5e. Restore FmInterval register. |
pop eax |
mov edx, eax |
and edx, 3FFFh |
jz .setfminterval |
cmp dx, 2EDFh ; default value? |
jnz @f ; assume that BIOS has configured the value |
.setfminterval: |
mov eax, 27792EDFh ; default value |
@@: |
mov [edi+OhciFmIntervalReg], eax |
; 5f. Set PeriodicStart to 90% of FmInterval. |
movzx eax, ax |
; Two following lines are equivalent to eax = floor(eax * 0.9) |
; for any 0 <= eax < 1C71C71Dh, which of course is far from maximum 0FFFFh. |
mov edx, 0E6666667h |
mul edx |
mov [edi+OhciPeriodicStartReg], edx |
.operational: |
; 6. Setup controller registers. |
pop esi ; restore pointer to ohci_controller saved in step 1 |
; 6a. Physical address of HCCA. |
mov eax, esi |
call get_pg_addr |
mov [edi+OhciHCCAReg], eax |
; 6b. Transition to operational state and clear all Enable bits. |
mov cl, 2 shl 6 |
mov [edi+OhciControlReg], ecx |
; 6c. Physical addresses of head of Control and Bulk lists. |
if ohci_controller.BulkED >= 0x1000 |
.err assertion failed |
end if |
add eax, ohci_controller.ControlED |
mov [edi+OhciControlHeadEDReg], eax |
add eax, ohci_controller.BulkED - ohci_controller.ControlED |
mov [edi+OhciBulkHeadEDReg], eax |
; 6d. Zero Head registers: there are no active Control and Bulk descriptors yet. |
xor eax, eax |
; mov [edi+OhciPeriodCurrentEDReg], eax |
mov [edi+OhciControlCurrentEDReg], eax |
mov [edi+OhciBulkCurrentEDReg], eax |
; mov [edi+OhciDoneHeadReg], eax |
; 6e. Enable processing of all lists with control:bulk ratio = 1:1. |
mov dword [edi+OhciControlReg], 10111100b |
; 7. Get number of ports. |
add esi, sizeof.ohci_controller |
mov eax, [edi+OhciRhDescriptorAReg] |
and eax, 0xF |
mov [esi+usb_controller.NumPorts], eax |
; 8. Initialize DoneListEndPtr to point to DoneList. |
lea eax, [esi+ohci_controller.DoneList-sizeof.ohci_controller] |
mov [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], eax |
; 9. Hook interrupt. |
mov ah, [.bus] |
mov al, 0 |
mov bh, [.devfn] |
mov bl, 3Ch |
call pci_read_reg |
; al = IRQ |
movzx eax, al |
stdcall attach_int_handler, eax, ohci_irq, esi |
; 10. Enable controller interrupt on HcDoneHead writeback and RootHubStatusChange. |
mov dword [edi+OhciInterruptEnableReg], 80000042h |
DEBUGF 1,'K : OHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] |
; 11. Initialize ports of the controller. |
; 11a. Initiate power up, disable all ports, clear all "changed" bits. |
mov dword [edi+OhciRhStatusReg], 10000h ; SetGlobalPower |
xor ecx, ecx |
@@: |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1F0101h ; SetPortPower+ClearPortEnable+clear "changed" bits |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb @b |
; 11b. Wait for power up. |
; VirtualBox has AReg == 0, delay_ms doesn't like zero value; ignore zero delay |
push esi |
mov esi, [edi+OhciRhDescriptorAReg] |
shr esi, 24 |
add esi, esi |
jz @f |
call delay_ms |
@@: |
pop esi |
; 11c. Ports are powered up; now it is ok to process connect/disconnect events. |
mov [esi+ohci_controller.PoweredUp-sizeof.ohci_controller], 1 |
; IRQ handler doesn't accept connect/disconnect events before this point |
; 11d. We could miss some events while waiting for powering up; |
; scan all ports manually and check for connected devices. |
xor ecx, ecx |
.port_loop: |
test dword [edi+OhciRhPortStatusReg+ecx*4], 1 |
jz .next_port |
; There is a connected device; mark the port as 'connected' |
; and save the connected time. |
; Note that ConnectedTime must be set before 'connected' mark, |
; otherwise the code in ohci_process_deferred could use incorrect time. |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
lock bts [esi+usb_controller.NewConnected], ecx |
.next_port: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .port_loop |
; 12. Return pointer to usb_controller. |
xchg eax, esi |
ret |
.fail_unmap: |
; On error after step 4, release the virtual memory area. |
stdcall free_kernel_space, edi |
.fail: |
; On error, free the ohci_controller structure and return zero. |
; Note that the pointer was placed in the stack at step 1. |
; Note also that there can be no errors after step 8, |
; where that pointer is popped from the stack. |
pop ecx |
.nothing: |
xor eax, eax |
ret |
endp |
; Helper procedure for step 3 of ohci_init. |
; Initializes the static head of one list. |
; eax = physical address of the "next" list, esi = pointer to the "next" list, |
; edi = pointer to head to initialize. |
; Advances edi to the next head, keeps eax/esi. |
proc ohci_init_static_endpoint |
mov byte [edi+ohci_static_ep.Flags+1], 1 shl (14 - 8) ; sKip this endpoint |
mov [edi+ohci_static_ep.NextED], eax |
mov [edi+ohci_static_ep.NextList], esi |
add edi, ohci_static_ep.SoftwarePart |
call usb_init_static_endpoint |
add edi, sizeof.ohci_static_ep - ohci_static_ep.SoftwarePart |
ret |
endp |
; Helper procedure for step 3 of ohci_init. |
; Initializes one half of group of static heads. |
; edx = size of the next group = half of size of the group, |
; edi = pointer to the group, eax = physical address of the next group, |
; esi = pointer to the next group. |
; Advances eax, esi, edi to next group, keeps edx. |
proc ohci_init_static_ep_group |
push edx |
@@: |
call ohci_init_static_endpoint |
add eax, sizeof.ohci_static_ep |
add esi, sizeof.ohci_static_ep |
dec edx |
jnz @b |
pop edx |
ret |
endp |
; Controller-specific pre-initialization function: take ownership from BIOS. |
; Some BIOSes, although not all of them, provide legacy emulation |
; for USB keyboard and/or mice as PS/2-devices. In this case, |
; we must notify the BIOS that we don't need that emulation and know how to |
; deal with USB devices. |
proc ohci_kickoff_bios |
; 1. Get the physical address of MMIO registers. |
mov ah, [esi+PCIDEV.bus] |
mov bh, [esi+PCIDEV.devfn] |
mov al, 2 |
mov bl, 10h |
call pci_read_reg |
and al, not 0Fh |
; 2. Create mapping for physical memory. 256 bytes are sufficient. |
stdcall map_io_mem, eax, 100h, PG_SW+PG_NOCACHE |
test eax, eax |
jz .nothing |
; 3. Some BIOSes enable controller interrupts as a result of giving |
; controller away. At this point the system knows nothing about how to serve |
; OHCI interrupts, so such an interrupt will send the system into an infinite |
; loop handling the same IRQ again and again. Thus, we need to block OHCI |
; interrupts. We can't do this at the controller level until step 5, |
; because the controller is currently owned by BIOS, so we block all hardware |
; interrupts on this processor until step 5. |
pushf |
cli |
; 4. Take the ownership over the controller. |
; 4a. Check whether BIOS handles this controller at all. |
mov edx, 100h |
test dword [eax+OhciControlReg], edx |
jz .has_ownership |
; 4b. Send "take ownership" command to the BIOS. |
; (This should generate SMI, BIOS should release its ownership in SMI handler.) |
mov dword [eax+OhciCommandStatusReg], 8 |
; 4c. Wait for result no more than 50 ms, checking for status every 1 ms. |
push 50 |
pop ecx |
@@: |
test dword [eax+OhciControlReg], edx |
jz .has_ownership |
push esi |
push 1 |
pop esi |
call delay_ms |
pop esi |
loop @b |
dbgstr 'warning: taking OHCI ownership from BIOS timeout' |
.has_ownership: |
; 5. Disable all controller interrupts until the system will be ready to |
; process them. |
mov dword [eax+OhciInterruptDisableReg], 0C000007Fh |
; 6. Now we can unblock interrupts in the processor. |
popf |
; 7. Release memory mapping created in step 2 and return. |
stdcall free_kernel_space, eax |
.nothing: |
ret |
endp |
; IRQ handler for OHCI controllers. |
ohci_irq.noint: |
; Not our interrupt: restore registers and return zero. |
xor eax, eax |
pop edi esi ebx |
ret |
proc ohci_irq |
push ebx esi edi ; save used registers to be cdecl |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.controller dd ? |
end virtual |
; 1. ebx will hold whether some deferred processing is needed, |
; that cannot be done from the interrupt handler. Initialize to zero. |
xor ebx, ebx |
; 2. Get the mask of events which should be processed. |
mov esi, [.controller] |
mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
mov eax, [edi+OhciInterruptStatusReg] |
; 3. Check whether that interrupt has been generated by our controller. |
; (One IRQ can be shared by several devices.) |
and eax, [edi+OhciInterruptEnableReg] |
jz .noint |
; 4. Get the physical pointer to the last processed descriptor. |
; All processed descriptors form single-linked list from last to first |
; with the help of NextTD field. The list is restarted every time when |
; the controller writes to DoneHead, so grab the pointer now (before the next |
; step) or it could be lost (the controller could write new value to DoneHead |
; any time after WorkDone bit is cleared in OhciInterruptStatusReg). |
mov ecx, [esi+ohci_controller.DoneHead-sizeof.ohci_controller] |
and ecx, not 1 |
; 5. Clear the events we know of. |
; Note that this should be done before processing of events: |
; new events could arise while we are processing those, this way we won't lose |
; them (the controller would generate another interrupt |
; after completion of this one). |
mov [edi+OhciInterruptStatusReg], eax |
; 6. Save the mask of events for further reference. |
push eax |
; 7. Handle 'transfer is done' events. |
; 7a. Test whether there are such events. |
test al, 2 |
jz .skip_donehead |
; There are some 'transfer is done' events, processed descriptors are linked |
; through physical addresses in the reverse order. |
; We can't do much in an interrupt handler, since callbacks could require |
; waiting for locks and that can't be done in an interrupt handler. |
; However, we can't also just defer all work to the USB thread, since |
; it is possible that previous lists are not yet processed and it is hard |
; to store unlimited number of list heads. Thus, we reverse the current list, |
; append it to end of the previous list (if there is one) and defer other |
; processing to the USB thread; this way there always is no more than one list |
; (possibly joined from several controller-reported lists). |
; The list traversal requires converting physical addresses to virtual pointers, |
; so we may as well store pointers instead of physical addresses. |
; 7b. Prepare for the reversing loop. |
push ebx |
xor ebx, ebx |
test ecx, ecx |
jz .tddone |
call usb_td_to_virt |
test eax, eax |
jz .tddone |
lea edx, [eax+ohci_gtd.NextTD] |
; 7c. Reverse the list, converting physical to virtual. On every iteration: |
; ecx = physical address of the current item |
; eax = virtual pointer to the current item |
; edx = virtual pointer to the last item.NextTD (first in the reverse list) |
; ebx = virtual pointer to the next item (previous in the reverse list) |
.tdloop: |
mov ecx, [eax+ohci_gtd.NextTD] |
mov [eax+ohci_gtd.NextTD], ebx |
lea ebx, [eax+ohci_gtd.SoftwarePart] |
test ecx, ecx |
jz .tddone |
call usb_td_to_virt |
test eax, eax |
jnz .tdloop |
.tddone: |
mov ecx, ebx |
pop ebx |
; 7d. The list is reversed, |
; ecx = pointer to the first item, edx = pointer to the last item.NextTD. |
; If the list is empty (unusual case), step 7 is done. |
test ecx, ecx |
jz .skip_donehead |
; 7e. Otherwise, append this list to the end of previous one. |
; Note that in theory the interrupt handler and the USB thread |
; could execute in parallel. |
.append_restart: |
; Atomically get DoneListEndPtr in eax and set it to edx. |
mov eax, [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller] |
lock cmpxchg [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], edx |
jnz .append_restart |
; Store pointer to the new list. |
; Note: we cannot perform any operations with [DoneListEndPtr] |
; until we switch DoneListEndPtr to a new descriptor: |
; it is possible that after first line of .append_restart loop |
; ohci_process_deferred obtains the control, finishes processing |
; of the old list, sets DoneListEndPtr to address of DoneList, |
; frees all old descriptors, so eax would point to invalid location. |
; This way, .append_restart loop would detect that DoneListEndPtr |
; has changed, so eax needs to be re-read. |
mov [eax], ecx |
; 7f. Notify the USB thread that there is new work. |
inc ebx |
.skip_donehead: |
; 8. Handle start-of-frame events. |
; 8a. Test whether there are such events. |
test byte [esp], 4 |
jz .skip_sof |
; We enable SOF interrupt only when some pipes are waiting after changes. |
spin_lock_irqsave [esi+usb_controller.WaitSpinlock] |
; 8b. Make sure that there was at least one frame update |
; since the request. If not, wait for the next SOF. |
movzx eax, [esi+ohci_controller.FrameNumber-sizeof.ohci_controller] |
cmp eax, [esi+usb_controller.StartWaitFrame] |
jz .sof_unlock |
; 8c. Copy WaitPipeRequest* to ReadyPipeHead*. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax |
; 8d. It is possible that pipe change is due to removal and |
; Control/BulkCurrentED registers still point to one of pipes to be removed. |
; The code responsible for disconnect events has temporarily stopped |
; Control/Bulk processing, so it is safe to clear Control/BulkCurrentED. |
; After that, restart processing. |
xor edx, edx |
mov [edi+OhciControlCurrentEDReg], edx |
mov [edi+OhciBulkCurrentEDReg], edx |
mov dword [edi+OhciCommandStatusReg], 6 |
or dword [edi+OhciControlReg], 30h |
; 8e. Disable further interrupts on SOF. |
; Note: OhciInterruptEnableReg/OhciInterruptDisableReg have unusual semantics. |
mov dword [edi+OhciInterruptDisableReg], 4 |
; Notify the USB thread that there is new work (with pipes from ReadyPipeHead*). |
inc ebx |
.sof_unlock: |
spin_unlock_irqrestore [esi+usb_controller.RemoveSpinlock] |
.skip_sof: |
; Handle roothub events. |
; 9. Test whether there are such events. |
test byte [esp], 40h |
jz .skip_roothub |
; 10. Check the status of the roothub itself. |
; 10a. Global overcurrent? |
test dword [edi+OhciRhStatusReg], 2 |
jz @f |
; Note: this needs work. |
dbgstr 'global overcurrent' |
@@: |
; 10b. Clear roothub events. |
mov dword [edi+OhciRhStatusReg], 80020000h |
; 11. Check the status of individual ports. |
; Look for connect/disconnect and reset events. |
; 11a. Prepare for the loop: start from port 0. |
xor ecx, ecx |
.portloop: |
; 11b. Get the port status and changes of it. |
; Accumulate change information. |
; Look to "11.12.3 Port Change Information Processing" of the USB2 spec. |
xor eax, eax |
.accloop: |
mov edx, [edi+OhciRhPortStatusReg+ecx*4] |
xor ax, ax |
or eax, edx |
test edx, 1F0000h |
jz .accdone |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1F0000h |
jmp .accloop |
.accdone: |
; debugging output, not needed for work |
; test eax, 1F0000h |
; jz @f |
; DEBUGF 1,'K : ohci irq [%d] status of port %d is %x\n',[timer_ticks],ecx,eax |
;@@: |
; 11c. Ignore any events until all ports are powered up. |
; They will be processed by ohci_init. |
cmp [esi+ohci_controller.PoweredUp-sizeof.ohci_controller], 0 |
jz .nextport |
; Handle changing of connection status. |
test eax, 10000h |
jz .nocsc |
; There was a connect or disconnect event at this port. |
; 11d. Disconnect the old device on this port, if any. |
; if the port was resetting, indicate fail and signal |
cmp cl, [esi+usb_controller.ResettingPort] |
jnz @f |
mov [esi+usb_controller.ResettingStatus], -1 |
inc ebx |
@@: |
lock bts [esi+usb_controller.NewDisconnected], ecx |
; notify the USB thread that new work is waiting |
inc ebx |
; 11e. Change connected status. For the connection event, also |
; store the connection time; any further processing is permitted only |
; after USB_CONNECT_DELAY ticks. |
test al, 1 |
jz .disconnect |
; Note: ConnectedTime must be stored before setting the 'connected' bit, |
; otherwise ohci_process_deferred could use an old time. |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
lock bts [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.disconnect: |
lock btr [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.nocsc: |
; 11f. Process 'reset done' events. |
test eax, 100000h |
jz .nextport |
test al, 10h |
jnz .nextport |
mov edx, [timer_ticks] |
mov [esi+usb_controller.ResetTime], edx |
mov [esi+usb_controller.ResettingStatus], 2 |
inc ebx |
.nextport: |
; 11g. Continue the loop for the next port. |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_roothub: |
; 12. Restore the stack after step 6. |
pop eax |
; 13. Notify the USB thread if some deferred processing is required. |
call usb_wakeup_if_needed |
; 14. Interrupt processed; return something non-zero. |
mov al, 1 |
pop edi esi ebx ; restore used registers to be stdcall |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; and stores USB device address in the ohci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
proc ohci_set_device_address |
mov byte [ebx+ohci_pipe.Flags-ohci_pipe.SoftwarePart], cl |
; Wait until the hardware will forget the old value. |
call usb_subscribe_control |
ret |
endp |
; This procedure returns USB device address from the usb_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = endpoint address |
proc ohci_get_device_address |
mov eax, [ebx+ohci_pipe.Flags-ohci_pipe.SoftwarePart] |
and eax, 7Fh |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; if the device does not accept SET_ADDRESS command and needs |
; to be disabled at the port level. |
; in: esi -> usb_controller, ecx = port |
proc ohci_port_disable |
mov edx, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
mov dword [edx+OhciRhPortStatusReg+ecx*4], 1 |
ret |
endp |
; This procedure is called from usb_get_descr8_callback when |
; the packet size for zero endpoint becomes known and |
; stores the packet size in ohci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
proc ohci_set_endpoint_packet_size |
mov byte [ebx+ohci_pipe.Flags+2-ohci_pipe.SoftwarePart], cl |
; Wait until the hardware will forget the old value. |
call usb_subscribe_control |
ret |
endp |
; This procedure is called from API usb_open_pipe and processes |
; the controller-specific part of this API. See docs. |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
proc ohci_init_pipe |
virtual at ebp+8 |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; 1. Initialize the queue of transfer descriptors: empty. |
sub eax, ohci_gtd.SoftwarePart |
call get_phys_addr |
mov [edi+ohci_pipe.TailP-ohci_pipe.SoftwarePart], eax |
mov [edi+ohci_pipe.HeadP-ohci_pipe.SoftwarePart], eax |
; 2. Generate ohci_pipe.Flags, see the description in ohci_pipe. |
mov eax, [ecx+ohci_pipe.Flags-ohci_pipe.SoftwarePart] |
and eax, 0x207F ; keep Speed bit and FunctionAddress |
mov edx, [.endpoint] |
and edx, 15 |
shl edx, 7 |
or eax, edx |
mov [edi+ohci_pipe.Flags-ohci_pipe.SoftwarePart], eax |
mov eax, [.maxpacket] |
mov word [edi+ohci_pipe.Flags+2-ohci_pipe.SoftwarePart], ax |
cmp [.type], CONTROL_PIPE |
jz @f |
test byte [.endpoint], 80h |
setnz al |
inc eax |
shl al, 3 |
or byte [edi+ohci_pipe.Flags+1-ohci_pipe.SoftwarePart], al |
@@: |
; 3. Insert the new pipe to the corresponding list of endpoints. |
; 3a. Use Control list for control pipes, Bulk list for bulk pipes. |
lea edx, [esi+ohci_controller.ControlED.SoftwarePart-sizeof.ohci_controller] |
cmp [.type], BULK_PIPE |
jb .insert ; control pipe |
lea edx, [esi+ohci_controller.BulkED.SoftwarePart-sizeof.ohci_controller] |
jz .insert ; bulk pipe |
.interrupt_pipe: |
; 3b. For interrupt pipes, let the scheduler select the appropriate list |
; based on the current bandwidth distribution and the requested bandwidth. |
; This could fail if the requested bandwidth is not available; |
; if so, return an error. |
lea edx, [esi + ohci_controller.IntEDs - sizeof.ohci_controller] |
lea eax, [esi + ohci_controller.IntEDs + 32*sizeof.ohci_static_ep - sizeof.ohci_controller] |
push 64 |
pop ecx |
call usb1_select_interrupt_list |
test edx, edx |
jz .return0 |
; 3c. Insert endpoint at edi to the head of list in edx. |
; Inserting to tail would work as well, |
; but let's be consistent with other controllers. |
.insert: |
mov ecx, [edx+usb_pipe.NextVirt] |
mov [edi+usb_pipe.NextVirt], ecx |
mov [edi+usb_pipe.PrevVirt], edx |
mov [ecx+usb_pipe.PrevVirt], edi |
mov [edx+usb_pipe.NextVirt], edi |
mov ecx, [edx+ohci_pipe.NextED-ohci_pipe.SoftwarePart] |
mov [edi+ohci_pipe.NextED-ohci_pipe.SoftwarePart], ecx |
lea eax, [edi-ohci_pipe.SoftwarePart] |
call get_phys_addr |
mov [edx+ohci_pipe.NextED-ohci_pipe.SoftwarePart], eax |
; 4. Return something non-zero. |
ret |
.return0: |
xor eax, eax |
ret |
endp |
; This function is called from ohci_process_deferred when |
; a new device was connected at least USB_CONNECT_DELAY ticks |
; and therefore is ready to be configured. |
; ecx = port, esi -> usb_controller |
proc ohci_new_port |
; test whether we are configuring another port |
; if so, postpone configuring and return |
bts [esi+usb_controller.PendingPorts], ecx |
cmp [esi+usb_controller.ResettingPort], -1 |
jnz .nothing |
btr [esi+usb_controller.PendingPorts], ecx |
; fall through to ohci_new_port.reset |
; This function is called from usb_test_pending_port. |
; It starts reset signalling for the port. Note that in USB first stages |
; of configuration can not be done for several ports in parallel. |
.reset: |
; reset port |
and [esi+usb_controller.ResettingHub], 0 |
mov [esi+usb_controller.ResettingPort], cl |
; Note: setting status must be the last action: |
; it is possible that the device has been disconnected |
; after timeout of USB_CONNECT_DELAY but before call to ohci_new_port. |
; In this case, ohci_irq would not set reset status to 'failed', |
; because ohci_irq would not know that this port is to be reset. |
; However, the hardware would generate another interrupt |
; in a response to reset a disconnected port, and this time |
; ohci_irq knows that it needs to generate 'reset failed' event |
; (because ResettingPort is now filled). |
push edi |
mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 10h |
pop edi |
.nothing: |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and allocates required packets for the given transfer. |
; ebx = pipe, other parameters are passed through the stack: |
; buffer,size = data to transfer |
; flags = same as in usb_open_pipe: bit 0 = allow short transfer, other bits reserved |
; td = pointer to the current end-of-queue descriptor |
; direction = |
; 0000b for normal transfers, |
; 1000b for control SETUP transfer, |
; 1101b for control OUT transfer, |
; 1110b for control IN transfer |
; returns eax = pointer to the new end-of-queue descriptor |
; (not included in the queue itself) or 0 on error |
proc ohci_alloc_transfer stdcall uses edi, \ |
buffer:dword, size:dword, flags:dword, td:dword, direction:dword |
locals |
origTD dd ? |
packetSize dd ? ; must be the last variable, see usb_init_transfer |
endl |
; 1. Save original value of td: |
; it will be useful for rollback if something would fail. |
mov eax, [td] |
mov [origTD], eax |
; One transfer descriptor can describe up to two pages. |
; In the worst case (when the buffer is something*1000h+0FFFh) |
; this corresponds to 1001h bytes. If the requested size is |
; greater, we should split the transfer into several descriptors. |
; Boundaries to split must be multiples of endpoint transfer size |
; to avoid short packets except in the end of the transfer, |
; 1000h is always a good value. |
; 2. While the remaining data cannot fit in one packet, |
; allocate page-sized descriptors. |
mov edi, 1000h |
mov [packetSize], edi |
.fullpackets: |
cmp [size], edi |
jbe .lastpacket |
call ohci_alloc_packet |
test eax, eax |
jz .fail |
mov [td], eax |
add [buffer], edi |
sub [size], edi |
jmp .fullpackets |
; 3. The remaining data can fit in one descriptor; |
; allocate the last descriptor with size = size of remaining data. |
.lastpacket: |
mov eax, [size] |
mov [packetSize], eax |
call ohci_alloc_packet |
test eax, eax |
jz .fail |
; 4. Enable an immediate interrupt on completion of the last packet. |
and byte [ecx+ohci_gtd.Flags+2-ohci_gtd.SoftwarePart], not (7 shl (21-16)) |
; 5. If a short transfer is ok for a caller, set the corresponding bit in |
; the last descriptor, but not in others. |
; Note: even if the caller says that short transfers are ok, |
; all packets except the last one are marked as 'must be complete': |
; if one of them will be short, the software intervention is needed |
; to skip remaining packets; ohci_process_finalized_td will handle this |
; transparently to the caller. |
test [flags], 1 |
jz @f |
or byte [ecx+ohci_gtd.Flags+2-ohci_gtd.SoftwarePart], 1 shl (18-16) |
@@: |
ret |
.fail: |
mov edi, ohci_hardware_func |
mov eax, [td] |
stdcall usb_undo_tds, [origTD] |
xor eax, eax |
ret |
endp |
; Helper procedure for ohci_alloc_transfer. |
; Allocates and initializes one transfer descriptor. |
; ebx = pipe, other parameters are passed through the stack; |
; fills the current last descriptor and |
; returns eax = next descriptor (not filled). |
proc ohci_alloc_packet |
; inherit some variables from the parent ohci_alloc_transfer |
virtual at ebp-8 |
.origTD dd ? |
.packetSize dd ? |
rd 2 |
.buffer dd ? |
.transferSize dd ? |
.Flags dd ? |
.td dd ? |
.direction dd ? |
end virtual |
; 1. Allocate the next TD. |
call usb1_allocate_general_td |
test eax, eax |
jz .nothing |
; 2. Initialize controller-independent parts of both TDs. |
push eax |
call usb_init_transfer |
pop eax |
; 3. Save the returned value (next descriptor). |
push eax |
; 4. Store the physical address of the next descriptor. |
sub eax, ohci_gtd.SoftwarePart |
call get_phys_addr |
mov [ecx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart], eax |
; 5. For zero-length transfers, store zero in both fields for buffer addresses. |
; Otherwise, fill them with real values. |
xor eax, eax |
mov [ecx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart], eax |
mov [ecx+ohci_gtd.BufEnd-ohci_gtd.SoftwarePart], eax |
cmp [.packetSize], eax |
jz @f |
mov eax, [.buffer] |
call get_phys_addr |
mov [ecx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart], eax |
mov eax, [.buffer] |
add eax, [.packetSize] |
dec eax |
call get_phys_addr |
mov [ecx+ohci_gtd.BufEnd-ohci_gtd.SoftwarePart], eax |
@@: |
; 6. Generate Flags field: |
; - set bufferRounding (bit 18) to zero = disallow short transfers; |
; for the last transfer in a row, ohci_alloc_transfer would set the real value; |
; - set Direction (bits 19-20) to lower 2 bits of [.direction]; |
; - set DelayInterrupt (bits 21-23) to 7 = do not generate interrupt; |
; for the last transfer in a row, ohci_alloc_transfer would set the real value; |
; - set DataToggle (bits 24-25) to next 2 bits of [.direction]; |
; - set ConditionCode (bits 28-31) to 1111b as a indicator that there was no |
; attempts to perform this transfer yet; |
; - zero all other bits. |
mov eax, [.direction] |
mov edx, eax |
and eax, 3 |
shl eax, 19 |
and edx, (3 shl 2) |
shl edx, 24 - 2 |
lea eax, [eax + edx + (7 shl 21) + (15 shl 28)] |
mov [ecx+ohci_gtd.Flags-ohci_gtd.SoftwarePart], eax |
; 7. Restore the returned value saved in step 3. |
pop eax |
.nothing: |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and activates the transfer which was previously allocated by |
; ohci_alloc_transfer. |
; ecx -> last descriptor for the transfer, ebx -> usb_pipe |
proc ohci_insert_transfer |
; 1. Advance the queue of transfer descriptors. |
mov eax, [ecx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart] |
mov [ebx+ohci_pipe.TailP-ohci_pipe.SoftwarePart], eax |
; 2. For control and bulk pipes, notify the controller that |
; there is new work in control/bulk queue respectively. |
ohci_notify_new_work: |
mov edx, [ebx+usb_pipe.Controller] |
mov edx, [edx+ohci_controller.MMIOBase-sizeof.ohci_controller] |
cmp [ebx+usb_pipe.Type], CONTROL_PIPE |
jz .control |
cmp [ebx+usb_pipe.Type], BULK_PIPE |
jnz .nothing |
.bulk: |
mov dword [edx+OhciCommandStatusReg], 4 |
jmp .nothing |
.control: |
mov dword [edx+OhciCommandStatusReg], 2 |
.nothing: |
ret |
endp |
; This function is called from ohci_process_deferred when |
; a new device has been reset and needs to be configured. |
proc ohci_port_after_reset |
; 1. Get the status. |
; If reset has been failed (device disconnected during reset), |
; continue to next device (if there is one). |
xor eax, eax |
xchg al, [esi+usb_controller.ResettingStatus] |
test al, al |
js usb_test_pending_port |
; If the controller has disabled the port (e.g. overcurrent), |
; continue to next device (if there is one). |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov eax, [edi+OhciRhPortStatusReg+ecx*4] |
test al, 2 |
jnz @f |
DEBUGF 1,'K : USB port disabled after reset, status=%x\n',eax |
jmp usb_test_pending_port |
@@: |
push ecx |
; 2. Get LowSpeed bit to bit 0 of eax and call the worker procedure |
; to notify the protocol layer about new OHCI device. |
mov eax, [edi+OhciRhPortStatusReg+ecx*4] |
DEBUGF 1,'K : port_after_reset [%d] status of port %d is %x\n',[timer_ticks],ecx,eax |
shr eax, 9 |
call ohci_new_device |
pop ecx |
; 3. If something at the protocol layer has failed |
; (no memory, no bus address), disable the port and stop the initialization. |
test eax, eax |
jnz .nothing |
.disable_exit: |
mov dword [edi+OhciRhPortStatusReg+ecx*4], 1 |
jmp usb_test_pending_port |
.nothing: |
ret |
endp |
; This procedure is called from uhci_port_init and from hub support code |
; when a new device is connected and has been reset. |
; It calls usb_new_device at the protocol layer with correct parameters. |
; in: esi -> usb_controller, eax = speed; |
; OHCI is USB1 device, so only low bit of eax (LowSpeed) is used. |
proc ohci_new_device |
; 1. Clear all bits of speed except bit 0. |
and eax, 1 |
; 2. Store the speed for the protocol layer. |
mov [esi+usb_controller.ResettingSpeed], al |
; 3. Create pseudo-pipe in the stack. |
; See ohci_init_pipe: only .Controller and .Flags fields are used. |
shl eax, 13 |
push esi ; .Controller |
mov ecx, esp |
sub esp, 12 ; ignored fields |
push eax ; .Flags |
; 4. Notify the protocol layer. |
call usb_new_device |
; 5. Cleanup the stack after step 3 and return. |
add esp, 20 |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; processes regular actions and those actions which can't be safely done |
; from interrupt handler. |
; Returns maximal time delta before the next call. |
proc ohci_process_deferred |
push ebx edi ; save used registers to be stdcall |
; 1. Initialize the return value. |
push -1 |
; 2. Process disconnect events. |
call usb_disconnect_stage2 |
; 3. Check for connected devices. |
; If there is a connected device which was connected less than |
; USB_CONNECT_DELAY ticks ago, plan to wake up when the delay will be over. |
; Otherwise, call ohci_new_port. |
mov edi, [esi+ohci_controller.MMIOBase-sizeof.ohci_controller] |
xor ecx, ecx |
cmp [esi+usb_controller.NewConnected], ecx |
jz .skip_newconnected |
.portloop: |
bt [esi+usb_controller.NewConnected], ecx |
jnc .noconnect |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ConnectedTime+ecx*4] |
sub eax, USB_CONNECT_DELAY |
jge .connected |
neg eax |
cmp [esp], eax |
jb .nextport |
mov [esp], eax |
jmp .nextport |
.connected: |
lock btr [esi+usb_controller.NewConnected], ecx |
jnc .nextport |
call ohci_new_port |
.noconnect: |
.nextport: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_newconnected: |
; 4. Check for end of reset signalling. If so, call ohci_port_after_reset. |
cmp [esi+usb_controller.ResettingStatus], 2 |
jnz .no_reset_recovery |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_done |
neg eax |
cmp [esp], eax |
jb .skip_roothub |
mov [esp], eax |
jmp .skip_roothub |
.no_reset_recovery: |
cmp [esi+usb_controller.ResettingStatus], 0 |
jz .skip_roothub |
.reset_done: |
call ohci_port_after_reset |
.skip_roothub: |
; 5. Finalize transfers processed by hardware. |
; It is better to perform this step after processing disconnect events, |
; although not strictly obligatory. This way, an active transfer aborted |
; due to disconnect would be handled with more specific USB_STATUS_CLOSED, |
; not USB_STATUS_NORESPONSE. |
; Loop over all items in DoneList, call ohci_process_finalized_td for each. |
xor ebx, ebx |
xchg ebx, [esi+ohci_controller.DoneList-sizeof.ohci_controller] |
.tdloop: |
test ebx, ebx |
jz .tddone |
call ohci_process_finalized_td |
jmp .tdloop |
.tddone: |
; 6. Process wait-done notifications, test for new wait requests. |
; Note: that must be done after steps 2 and 5 which could create new requests. |
; 6a. Call the worker function from main USB code. |
call usb_process_wait_lists |
; 6b. If no new requests, skip the rest of this step. |
test eax, eax |
jz @f |
; 6c. OHCI is not allowed to cache anything; we don't know what is |
; processed right now, but we can be sure that the controller will not |
; use any removed structure starting from the next frame. |
; Schedule SOF event. |
spin_lock_irq [esi+usb_controller.RemoveSpinlock] |
mov eax, [esi+usb_controller.WaitPipeListAsync] |
mov [esi+usb_controller.WaitPipeRequestAsync], eax |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [esi+usb_controller.WaitPipeRequestPeriodic], eax |
; temporarily stop bulk and interrupt processing; |
; this is required for handler of SOF event |
and dword [edi+OhciControlReg], not 30h |
; remember the frame number when processing has been stopped |
; (needs to be done after stopping) |
movzx eax, [esi+ohci_controller.FrameNumber-sizeof.ohci_controller] |
mov [esi+usb_controller.StartWaitFrame], eax |
; make sure that the next SOF will happen after the request |
mov dword [edi+OhciInterruptStatusReg], 4 |
; enable interrupt on SOF |
; Note: OhciInterruptEnableReg/OhciInterruptDisableReg have unusual semantics, |
; so there should be 'mov' here, not 'or' |
mov dword [edi+OhciInterruptEnableReg], 4 |
spin_unlock_irq [esi+usb_controller.RemoveSpinlock] |
@@: |
; 7. Restore the return value and return. |
pop eax |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; Helper procedure for ohci_process_deferred. Processes one completed TD. |
; in: esi -> usb_controller, ebx -> usb_gtd, out: ebx -> next usb_gtd. |
proc ohci_process_finalized_td |
; DEBUGF 1,'K : processing %x\n',ebx |
; 1. Check whether the pipe has been closed, either due to API call or due to |
; disconnect; if so, the callback will be called by usb_pipe_closed with |
; correct status, so go to step 6 with ebx = 0 (do not free the TD). |
mov edx, [ebx+usb_gtd.Pipe] |
test [edx+usb_pipe.Flags], USB_FLAG_CLOSED |
jz @f |
lea eax, [ebx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart] |
xor ebx, ebx |
jmp .next_td2 |
@@: |
; 2. Remove the descriptor from the descriptors queue. |
call usb_unlink_td |
; 3. Get number of bytes that remain to be transferred. |
; If CurBufPtr is zero, everything was transferred. |
xor edx, edx |
cmp [ebx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart], edx |
jz .gotlen |
; Otherwise, the remaining length is |
; (BufEnd and 0xFFF) - (CurBufPtr and 0xFFF) + 1, |
; plus 0x1000 if BufEnd and CurBufPtr are in different pages. |
mov edx, [ebx+ohci_gtd.BufEnd-ohci_gtd.SoftwarePart] |
mov eax, [ebx+ohci_gtd.CurBufPtr-ohci_gtd.SoftwarePart] |
mov ecx, edx |
and edx, 0xFFF |
inc edx |
xor ecx, eax |
and ecx, -0x1000 |
jz @f |
add edx, 0x1000 |
@@: |
and eax, 0xFFF |
sub edx, eax |
.gotlen: |
; The actual length is Length - (remaining length). |
sub edx, [ebx+usb_gtd.Length] |
neg edx |
; 4. Check for error. If so, go to 7. |
push ebx |
mov eax, [ebx+ohci_gtd.Flags-ohci_gtd.SoftwarePart] |
shr eax, 28 |
jnz .error |
.notify: |
; 5. Successful completion. |
; 5a. Check whether this descriptor has an associated callback. |
mov ecx, [ebx+usb_gtd.Callback] |
test ecx, ecx |
jz .ok_nocallback |
; 5b. If so, call the callback. |
stdcall_verify ecx, [ebx+usb_gtd.Pipe], eax, \ |
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] |
jmp .next_td |
.ok_nocallback: |
; 5c. Otherwise, add length of the current descriptor to the next descriptor. |
mov eax, [ebx+usb_gtd.NextVirt] |
add [eax+usb_gtd.Length], edx |
.next_td: |
; 6. Free the current descriptor and advance to the next item. |
; If the current item is the last in the list, |
; set DoneListEndPtr to pointer to DoneList. |
cmp ebx, [esp] |
jz @f |
stdcall usb1_free_general_td, ebx |
@@: |
pop ebx |
lea eax, [ebx+ohci_gtd.NextTD-ohci_gtd.SoftwarePart] |
.next_td2: |
push ebx |
mov ebx, eax |
lea edx, [esi+ohci_controller.DoneList-sizeof.ohci_controller] |
xor ecx, ecx ; no next item |
lock cmpxchg [esi+ohci_controller.DoneListEndPtr-sizeof.ohci_controller], edx |
jz .last |
; The current item is not the last. |
; It is possible, although very rare, that ohci_irq has already advanced |
; DoneListEndPtr, but not yet written NextTD. Wait until NextTD is nonzero. |
@@: |
mov ecx, [ebx] |
test ecx, ecx |
jz @b |
.last: |
pop ebx |
; ecx = the next item |
push ecx |
; Free the current item, set ebx to the next item, continue to 5a. |
test ebx, ebx |
jz @f |
stdcall usb1_free_general_td, ebx |
@@: |
pop ebx |
ret |
.error: |
; 7. There was an error while processing this descriptor. |
; The hardware has stopped processing the queue. |
; 7a. Save status and length. |
push eax |
push edx |
; DEBUGF 1,'K : TD failed:\n' |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-ohci_gtd.SoftwarePart],[ebx-ohci_gtd.SoftwarePart+4],[ebx-ohci_gtd.SoftwarePart+8],[ebx-ohci_gtd.SoftwarePart+12] |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-ohci_gtd.SoftwarePart+16],[ebx-ohci_gtd.SoftwarePart+20],[ebx-ohci_gtd.SoftwarePart+24],[ebx-ohci_gtd.SoftwarePart+28] |
; mov eax, [ebx+usb_gtd.Pipe] |
; DEBUGF 1,'K : pipe: %x %x %x %x\n',[eax-ohci_pipe.SoftwarePart],[eax-ohci_pipe.SoftwarePart+4],[eax-ohci_pipe.SoftwarePart+8],[eax-ohci_pipe.SoftwarePart+12] |
; 7b. Traverse the list of descriptors looking for the final packet |
; for this transfer. |
; Free and unlink non-final descriptors, except the current one. |
; Final descriptor will be freed in step 6. |
call usb_is_final_packet |
jnc .found_final |
mov ebx, [ebx+usb_gtd.NextVirt] |
virtual at esp |
.length dd ? |
.error_code dd ? |
.current_item dd ? |
end virtual |
.look_final: |
call usb_unlink_td |
call usb_is_final_packet |
jnc .found_final |
push [ebx+usb_gtd.NextVirt] |
stdcall usb1_free_general_td, ebx |
pop ebx |
jmp .look_final |
.found_final: |
; 7c. If error code is USB_STATUS_UNDERRUN and the last TD allows short packets, |
; it is not an error. |
; Note: all TDs except the last one in any transfer stage are marked |
; as short-packet-is-error to stop controller from further processing |
; of that stage; we need to restart processing from a TD following the last. |
; After that, go to step 5 with eax = 0 (no error). |
cmp dword [.error_code], USB_STATUS_UNDERRUN |
jnz .no_underrun |
test byte [ebx+ohci_gtd.Flags+2-ohci_gtd.SoftwarePart], 1 shl (18-16) |
jz .no_underrun |
and dword [.error_code], 0 |
mov ecx, [ebx+usb_gtd.Pipe] |
mov edx, [ecx+ohci_pipe.HeadP-ohci_pipe.SoftwarePart] |
and edx, 2 |
.advance_queue: |
mov eax, [ebx+usb_gtd.NextVirt] |
sub eax, ohci_gtd.SoftwarePart |
call get_phys_addr |
or eax, edx |
mov [ecx+ohci_pipe.HeadP-ohci_pipe.SoftwarePart], eax |
push ebx |
mov ebx, ecx |
call ohci_notify_new_work |
pop ebx |
pop edx eax |
jmp .notify |
; 7d. Abort the entire transfer. |
; There are two cases: either there is only one transfer stage |
; (everything except control transfers), then ebx points to the last TD and |
; all previous TD were unlinked and dismissed (if possible), |
; or there are several stages (a control transfer) and ebx points to the last |
; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, |
; because Setup stage can not produce short packets); for Data stage, we need |
; to unlink and free (if possible) one more TD and advance ebx to the next one. |
.no_underrun: |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .halted |
cmp ebx, [.current_item] |
push [ebx+usb_gtd.NextVirt] |
jz @f |
stdcall usb1_free_general_td, ebx |
@@: |
pop ebx |
call usb_unlink_td |
.halted: |
; 7e. For bulk/interrupt transfers we have no choice but halt the queue, |
; the driver should intercede (through some API which is not written yet). |
; Control pipes normally recover at the next SETUP transaction (first stage |
; of any control transfer), so we hope on the best and just advance the queue |
; to the next transfer. (According to the standard, "A control pipe may also |
; support functional stall as well, but this is not recommended."). |
; Advance the transfer queue to the next descriptor. |
mov ecx, [ebx+usb_gtd.Pipe] |
mov edx, [ecx+ohci_pipe.HeadP-ohci_pipe.SoftwarePart] |
and edx, 2 ; keep toggleCarry bit |
cmp [ecx+usb_pipe.Type], CONTROL_PIPE |
jnz @f |
inc edx ; set Halted bit |
@@: |
jmp .advance_queue |
endp |
; This procedure is called when a pipe is closing (either due to API call |
; or due to disconnect); it unlinks the pipe from the corresponding list. |
; esi -> usb_controller, ebx -> usb_pipe |
proc ohci_unlink_pipe |
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE |
jnz @f |
mov eax, [ebx+ohci_pipe.Flags-ohci_pipe.SoftwarePart] |
bt eax, 13 |
setc cl |
bt eax, 11 |
setc ch |
shr eax, 16 |
stdcall usb1_interrupt_list_unlink, eax, ecx |
@@: |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
mov edx, [ebx+ohci_pipe.NextED-ohci_pipe.SoftwarePart] |
mov [eax+ohci_pipe.NextED-ohci_pipe.SoftwarePart], edx |
ret |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/bus/usb/pipe.inc |
---|
0,0 → 1,813 |
; Functions for USB pipe manipulation: opening/closing, sending data etc. |
; |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; USB pipe types |
CONTROL_PIPE = 0 |
ISOCHRONOUS_PIPE = 1 |
BULK_PIPE = 2 |
INTERRUPT_PIPE = 3 |
; Status codes for transfer callbacks. |
; Taken from OHCI as most verbose controller in this sense. |
USB_STATUS_OK = 0 ; no error |
USB_STATUS_CRC = 1 ; CRC error |
USB_STATUS_BITSTUFF = 2 ; bit stuffing violation |
USB_STATUS_TOGGLE = 3 ; data toggle mismatch |
USB_STATUS_STALL = 4 ; device returned STALL |
USB_STATUS_NORESPONSE = 5 ; device not responding |
USB_STATUS_PIDCHECK = 6 ; invalid PID check bits |
USB_STATUS_WRONGPID = 7 ; unexpected PID value |
USB_STATUS_OVERRUN = 8 ; too many data from endpoint |
USB_STATUS_UNDERRUN = 9 ; too few data from endpoint |
USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer |
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer |
USB_STATUS_CLOSED = 16 ; pipe closed |
; either explicitly with USBClosePipe |
; or implicitly due to device disconnect |
; flags for usb_pipe.Flags |
USB_FLAG_CLOSED = 1 ; pipe is closed, no new transfers |
; pipe is closed, return error instead of submitting any new transfer |
USB_FLAG_CAN_FREE = 2 |
; pipe is closed via explicit call to USBClosePipe, so it can be freed without |
; any driver notification; if this flag is not set, then the pipe is closed due |
; to device disconnect, so it must remain valid until return from disconnect |
; callback provided by the driver |
USB_FLAG_EXTRA_WAIT = 4 |
; The pipe was in wait list, while another event occured; |
; when the first wait will be done, reinsert the pipe to wait list |
USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; Pipe descriptor. |
; * An USB pipe is described by two structures, for hardware and for software. |
; * This is the software part. The hardware part is defined in a driver |
; of the corresponding controller. |
; * The hardware part is located immediately before usb_pipe, |
; both are allocated at once by controller-specific code |
; (it knows the total length, which depends on the hardware part). |
struct usb_pipe |
Controller dd ? |
; Pointer to usb_controller structure corresponding to this pipe. |
; Must be the first dword after hardware part, see *hci_new_device. |
; |
; Every endpoint is included into one of processing lists: |
; * Bulk list contains all Bulk endpoints. |
; * Control list contains all Control endpoints. |
; * Several Periodic lists serve Interrupt endpoints with different interval. |
; - There are N=2^n "leaf" periodic lists for N ms interval, one is processed |
; in the frames 0,N,2N,..., another is processed in the frames |
; 1,1+N,1+2N,... and so on. The hardware starts processing of periodic |
; endpoints in every frame from the list identified by lower n bits of the |
; frame number; the addresses of these N lists are written to the |
; controller data area during the initialization. |
; - We assume that n=5, N=32 to simplify the code and compact the data. |
; OHCI works in this way. UHCI and EHCI actually have n=10, N=1024, |
; but this is an overkill for interrupt endpoints; the large value of N is |
; useful only for isochronous transfers in UHCI and EHCI. UHCI/EHCI code |
; initializes "leaf" lists k,k+32,k+64,...,k+(1024-32) to the same value, |
; giving essentially N=32. |
; This restriction means that the actual maximum interval of polling any |
; interrupt endpoint is 32ms, which seems to be a reasonable value. |
; - Similarly, there are 16 lists for 16-ms interval, 8 lists for 8-ms |
; interval and so on. Finally, there is one list for 1ms interval. Their |
; addresses are not directly known to the controller. |
; - The hardware serves endpoints following a physical link from the hardware |
; part. |
; - The hardware links are organized as follows. If the list item is not the |
; last, it's hardware link points to the next item. The hardware link of |
; the last item points to the first item of the "next" list. |
; - The "next" list for k-th and (k+M)-th periodic lists for interval 2M ms |
; is the k-th periodic list for interval M ms, M >= 1. In this scheme, |
; if two "previous" lists are served in the frames k,k+2M,k+4M,... |
; and k+M,k+3M,k+5M,... correspondingly, the "next" list is served in |
; the frames k,k+M,k+2M,k+3M,k+4M,k+5M,..., which is exactly what we want. |
; - The links between Periodic, Control, Bulk lists and the processing of |
; Isochronous endpoints are controller-specific. |
; * The head of every processing list is a static entry which does not |
; correspond to any real pipe. It is described by usb_static_ep |
; structure, not usb_pipe. For OHCI and UHCI, sizeof.usb_static_ep plus |
; sizeof hardware part is 20h, the total number of lists is |
; 32+16+8+4+2+1+1+1 = 65, so all these structures fit in one page, |
; leaving space for other data. This is another reason for 32ms limit. |
; * Static endpoint descriptors are kept in *hci_controller structure. |
; * All items in every processing list, including the static head, are |
; organized in a double-linked list using .NextVirt and .PrevVirt fields. |
; * [[item.NextVirt].PrevVirt] = [[item.PrevVirt].NextVirt] for all items. |
NextVirt dd ? |
; Next endpoint in the processing list. |
; See also PrevVirt field and the description before NextVirt field. |
PrevVirt dd ? |
; Previous endpoint in the processing list. |
; See also NextVirt field and the description before NextVirt field. |
; |
; Every pipe has the associated transfer queue, that is, the double-linked |
; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt |
; endpoints this list consists of usb_gtd structures |
; (GTD = General Transfer Descriptors), for Isochronous endpoints |
; this list consists of usb_itd structures, which are not developed yet. |
; The pipe needs to know only the last TD; the first TD can be |
; obtained as [[pipe.LastTD].NextVirt]. |
LastTD dd ? |
; Last TD in the transfer queue. |
; |
; All opened pipes corresponding to the same physical device are organized in |
; the double-linked list using .NextSibling and .PrevSibling fields. |
; The head of this list is kept in usb_device_data structure (OpenedPipeList). |
; This list is used when the device is disconnected and all pipes for the |
; device should be closed. |
; Also, all pipes closed due to disconnect must remain valid at least until |
; driver-provided disconnect function returns; all should-be-freed-but-not-now |
; pipes for one device are organized in another double-linked list with |
; the head in usb_device_data.ClosedPipeList; this list uses the same link |
; fields, one pipe can never be in both lists. |
NextSibling dd ? |
; Next pipe for the physical device. |
PrevSibling dd ? |
; Previous pipe for the physical device. |
; |
; When hardware part of pipe is changed, some time is needed before further |
; actions so that hardware reacts on this change. During that time, |
; all changed pipes are organized in single-linked list with the head |
; usb_controller.WaitPipeList* and link field NextWait. |
; Currently there are two possible reasons to change: |
; change of address/packet size in initial configuration, |
; close of the pipe. They are distinguished by USB_FLAG_CLOSED. |
NextWait dd ? |
Lock MUTEX |
; Mutex that guards operations with transfer queue for this pipe. |
Type db ? |
; Type of pipe, one of {CONTROL,ISOCHRONOUS,BULK,INTERRUPT}_PIPE. |
Flags db ? |
; Combination of flags, USB_FLAG_*. |
rb 2 ; dword alignment |
DeviceData dd ? |
; Pointer to usb_device_data, common for all pipes for one device. |
ends |
; This structure describes the static head of every list of pipes. |
struct usb_static_ep |
; software fields |
Bandwidth dd ? |
; valid only for interrupt/isochronous USB1 lists |
; The offsets of the following two fields must be the same in this structure |
; and in usb_pipe. |
NextVirt dd ? |
PrevVirt dd ? |
ends |
; This structure represents one transfer descriptor |
; ('g' stands for "general" as opposed to isochronous usb_itd). |
; Note that one transfer can have several descriptors: |
; a control transfer has three stages. |
; Additionally, every controller has a limit on transfer length with |
; one descriptor (packet size for UHCI, 1K for OHCI, 4K for EHCI), |
; large transfers must be split into individual packets according to that limit. |
struct usb_gtd |
Callback dd ? |
; Zero for intermediate descriptors, pointer to callback function |
; for final descriptor. See the docs for description of the callback. |
UserData dd ? |
; Dword which is passed to Callback as is, not used by USB code itself. |
; Two following fields organize all descriptors for one pipe in |
; the linked list. |
NextVirt dd ? |
PrevVirt dd ? |
Pipe dd ? |
; Pointer to the parent usb_pipe. |
Buffer dd ? |
; Pointer to data for this descriptor. |
Length dd ? |
; Length of data for this descriptor. |
ends |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
USB_STDCALL_VERIFY = 1 |
macro stdcall_verify [arg] |
{ |
common |
if USB_STDCALL_VERIFY |
pushad |
stdcall arg |
call verify_regs |
popad |
else |
stdcall arg |
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 |
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. |
mov eax, [esi+usb_controller.HardwareFunc] |
call [eax+usb_hardware_func.UnlinkPipe] |
; 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 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. Loop over all transfers, calling the driver with USB_STATUS_CLOSED |
; and freeing all descriptors. |
mov edx, [ebx+usb_pipe.LastTD] |
test edx, edx |
jz .no_transfer |
mov edx, [edx+usb_gtd.NextVirt] |
.transfer_loop: |
cmp edx, [ebx+usb_pipe.LastTD] |
jz .transfer_done |
mov ecx, [edx+usb_gtd.Callback] |
test ecx, ecx |
jz .no_callback |
push edx |
stdcall_verify ecx, ebx, USB_STATUS_CLOSED, \ |
[edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData] |
pop edx |
.no_callback: |
push [edx+usb_gtd.NextVirt] |
stdcall [edi+usb_hardware_func.FreeTD], edx |
pop edx |
jmp .transfer_loop |
.transfer_done: |
stdcall [edi+usb_hardware_func.FreeTD], edx |
.no_transfer: |
; 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 |
movzx 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. 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' |
; 7. 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 |
; 8. Free the usb_device_data structure. |
sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling |
call free |
; 9. Return. |
.nothing: |
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 |
; 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 |
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 |
/kernel/trunk/bus/usb/protocol.inc |
---|
0,0 → 1,926 |
; 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 |
; Possible speeds of USB devices |
USB_SPEED_FS = 0 ; full-speed |
USB_SPEED_LS = 1 ; low-speed |
USB_SPEED_HS = 2 ; high-speed |
; Compile-time setting. If set, the code will dump all descriptors as they are |
; read to the debug board. |
USB_DUMP_DESCRIPTORS = 1 |
; ============================================================================= |
; ================================ 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. 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. |
; 1a. Allocate a bus address. |
push ecx |
call usb_set_address_request |
pop ecx |
; 1b. If failed, just return zero. |
test eax, eax |
jz .nothing |
; 1c. 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. |
push sizeof.usb_device_data + 8 |
pop eax |
push ecx |
call malloc |
pop ecx |
; 1d. If failed, free the bus address and return zero. |
test eax, eax |
jz .nomemory |
; 1e. 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 |
; 1f. If failed, free the memory, the bus address and return zero. |
test ebx, ebx |
jz .freememory |
; 2. Store pointer to device data in the pipe structure. |
mov [ebx+usb_pipe.DeviceData], eax |
; 3. Init device data, using usb_controller.Resetting* variables. |
mov [eax+usb_device_data.NumPipes], 1 |
mov [eax+usb_device_data.ConfigDataSize], edi |
mov [eax+usb_device_data.Interfaces], edi |
movzx ecx, [esi+usb_controller.ResettingPort] |
; Note: the following write zeroes |
; usb_device_data.DeviceDescrSize, usb_device_data.NumInterfaces, |
; usb_device_data.Speed. |
mov dword [eax+usb_device_data.Port], ecx |
mov dl, [esi+usb_controller.ResettingSpeed] |
mov [eax+usb_device_data.Speed], dl |
mov edx, [esi+usb_controller.ResettingHub] |
mov [eax+usb_device_data.Hub], edx |
; 4. 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 |
; 5. Issue SET_ADDRESS control request, using buffer filled in step 1a. |
; Use the return value from usb_control_async as our return value; |
; if it is zero, then something has failed. |
lea eax, [esi+usb_controller.SetAddressBuffer] |
stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi |
.nothing: |
; 6. Return. |
pop edi ebx ; restore used registers to be stdcall |
ret |
; Handlers of failures in steps 1b, 1d, 1f. |
.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 |
; Load data to registers for further references. |
mov ebx, [pipe] |
mov ecx, dword [esi+usb_controller.SetAddressBuffer+2] |
mov eax, [esi+usb_controller.HardwareFunc] |
; 1. Check whether the device has accepted new address. If so, proceed to 2. |
; Otherwise, go to 3. |
cmp [status], 0 |
jnz .error |
; 2. Address accepted. |
; 2a. 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] |
; 2b. If the port is in non-root hub, clear 'reset in progress' flag. |
; In any case, proceed to 4. |
mov eax, [esi+usb_controller.ResettingHub] |
test eax, eax |
jz .return |
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS |
.return: |
; 4. Address configuration done, we can proceed with other ports. |
; Call the worker function for that. |
call usb_test_pending_port |
.nothing: |
pop ebx ; restore ebx to be stdcall |
ret |
.error: |
; 3. Device error: device not responding, disconnect etc. |
DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status] |
; 3a. The address has not been accepted. Mark it as free. |
bts dword [esi+usb_controller.ExistingAddresses], ecx |
; 3b. 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 |
; Note that malloc destroys ebx. |
push ebx |
call malloc |
pop ebx |
; 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 |
repeat sizeof.usb_device_data / 4 |
movsd |
end repeat |
pop edi esi |
call usb_reinit_pipe_list |
; 1d. Free the old memory. |
; Note that free destroys ebx. |
push ebx |
call free |
pop ebx |
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(DEVICE) 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], dl |
; 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 |
movzx 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 dl |
jz .invalid |
cmp dl, [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 |
/kernel/trunk/bus/usb/scheduler.inc |
---|
0,0 → 1,508 |
; Implementation of periodic transaction scheduler for USB. |
; Bandwidth dedicated to periodic transactions is limited, so |
; different pipes should be scheduled as uniformly as possible. |
; USB1 scheduler. |
; Algorithm is simple: |
; when adding a pipe, optimize the following quantity: |
; * for every millisecond, take all bandwidth scheduled to periodic transfers, |
; * calculate maximum over all milliseconds, |
; * select a variant which minimizes that maximum; |
; when removing a pipe, do nothing (except for bookkeeping). |
; sanity check: structures in UHCI and OHCI should be the same |
if (sizeof.ohci_static_ep=sizeof.uhci_static_ep)&(ohci_static_ep.SoftwarePart=uhci_static_ep.SoftwarePart)&(ohci_static_ep.NextList=uhci_static_ep.NextList) |
; Select a list for a new pipe. |
; in: esi -> usb_controller, maxpacket, type, interval can be found in the stack |
; in: ecx = 2 * maximal interval = total number of periodic lists + 1 |
; in: edx -> {u|o}hci_static_ep for the first list |
; in: eax -> byte past {u|o}hci_static_ep for the last list in the first group |
; out: edx -> usb_static_ep for the selected list or zero if failed |
proc usb1_select_interrupt_list |
; inherit some variables from usb_open_pipe |
virtual at ebp-8 |
.bandwidth dd ? |
.target dd ? |
dd ? |
dd ? |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
push ebx edi ; save used registers to be stdcall |
push eax ; save eax for checks in step 3 |
; 1. Only intervals 2^k ms can be supported. |
; The core specification says that the real interval should not be greater |
; than the interval given by the endpoint descriptor, but can be less. |
; Determine the actual interval as 2^k ms. |
mov eax, ecx |
; 1a. Set [.interval] to 1 if it was zero; leave it as is otherwise |
cmp [.interval], 1 |
adc [.interval], 0 |
; 1b. Divide ecx by two while it is strictly greater than [.interval]. |
@@: |
shr ecx, 1 |
cmp [.interval], ecx |
jb @b |
; ecx = the actual interval |
; |
; For example, let ecx = 8, eax = 64. |
; The scheduler space is 32 milliseconds, |
; we need to schedule something every 8 ms; |
; there are 8 variants: schedule at times 0,8,16,24, |
; schedule at times 1,9,17,25,..., schedule at times 7,15,23,31. |
; Now concentrate: there are three nested loops, |
; * the innermost loop calculates the total periodic bandwidth scheduled |
; in the given millisecond, |
; * the intermediate loop calculates the maximum over all milliseconds |
; in the given variant, that is the quantity we're trying to minimize, |
; * the outermost loop checks all variants. |
; 2. Calculate offset between the first list and the first list for the |
; selected interval, in bytes; save in the stack for step 4. |
sub eax, ecx |
sub eax, ecx |
imul eax, sizeof.ohci_static_ep |
push eax |
imul ebx, ecx, sizeof.ohci_static_ep |
; 3. Select the best variant. |
; 3a. The outermost loop. |
; Prepare for the loop: set the current optimal bandwidth to maximum |
; possible value (so that any variant will pass the first comparison), |
; calculate delta for the intermediate loop. |
or [.bandwidth], -1 |
.varloop: |
; 3b. The intermediate loop. |
; Prepare for the loop: set the maximum to be calculated to zero, |
; save counter of the outermost loop. |
xor edi, edi |
push edx |
virtual at esp |
.cur_variant dd ? ; step 3b |
.result_delta dd ? ; step 2 |
.group1_limit dd ? ; function prolog |
end virtual |
.calc_max_bandwidth: |
; 3c. The innermost loop. Sum over all lists. |
xor eax, eax |
push edx |
.calc_bandwidth: |
add eax, [edx+ohci_static_ep.SoftwarePart+usb_static_ep.Bandwidth] |
mov edx, [edx+ohci_static_ep.NextList] |
test edx, edx |
jnz .calc_bandwidth |
pop edx |
; 3d. The intermediate loop continued: update maximum. |
cmp eax, edi |
jb @f |
mov edi, eax |
@@: |
; 3e. The intermediate loop continued: advance counter. |
add edx, ebx |
cmp edx, [.group1_limit] |
jb .calc_max_bandwidth |
; 3e. The intermediate loop done: restore counter of the outermost loop. |
pop edx |
; 3f. The outermost loop continued: if the current variant is |
; better (maybe not strictly) then the previous optimum, update |
; the optimal bandwidth and resulting list. |
cmp edi, [.bandwidth] |
ja @f |
mov [.bandwidth], edi |
mov [.target], edx |
@@: |
; 3g. The outermost loop continued: advance counter. |
add edx, sizeof.ohci_static_ep |
dec ecx |
jnz .varloop |
; 4. Get the pointer to the best list. |
pop edx ; restore value from step 2 |
pop eax ; purge stack var from prolog |
add edx, [.target] |
; 5. Calculate bandwidth for the new pipe. |
mov eax, [.maxpacket] ; TODO: calculate real bandwidth |
and eax, (1 shl 11) - 1 |
; 6. TODO: check that bandwidth for the new pipe plus old bandwidth |
; still fits to maximum allowed by the core specification. |
; 7. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return. |
add edx, ohci_static_ep.SoftwarePart |
add [edx+usb_static_ep.Bandwidth], eax |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; sanity check, part 2 |
else |
.err select_interrupt_list must be different for UHCI and OHCI |
end if |
; Pipe is removing, update the corresponding lists. |
; We do not reorder anything, so just update book-keeping variable |
; in the list header. |
proc usb1_interrupt_list_unlink |
virtual at esp |
dd ? ; return address |
.maxpacket dd ? |
.lowspeed db ? |
.direction db ? |
rb 2 |
end virtual |
; find list header |
mov edx, ebx |
@@: |
mov edx, [edx+usb_pipe.NextVirt] |
cmp [edx+usb_pipe.Controller], esi |
jnz @b |
; subtract pipe bandwidth |
; TODO: calculate real bandwidth |
mov eax, [.maxpacket] |
and eax, (1 shl 11) - 1 |
sub [edx+usb_static_ep.Bandwidth], eax |
ret 8 |
endp |
; USB2 scheduler. |
; There are two parts: high-speed pipes and split-transaction pipes. |
; Split-transaction scheduler is currently a stub. |
; High-speed scheduler uses the same algorithm as USB1 scheduler: |
; when adding a pipe, optimize the following quantity: |
; * for every microframe, take all bandwidth scheduled to periodic transfers, |
; * calculate maximum over all microframe, |
; * select a variant which minimizes that maximum; |
; when removing a pipe, do nothing (except for bookkeeping). |
; in: esi -> usb_controller |
; out: edx -> usb_static_ep, eax = S-Mask |
proc ehci_select_hs_interrupt_list |
; inherit some variables from usb_open_pipe |
virtual at ebp-12 |
.targetsmask dd ? |
.bandwidth dd ? |
.target dd ? |
dd ? |
dd ? |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; prolog, initialize local vars |
or [.bandwidth], -1 |
or [.target], -1 |
or [.targetsmask], -1 |
push ebx edi ; save used registers to be stdcall |
; 1. In EHCI, every list describes one millisecond = 8 microframes. |
; Thus, there are two significantly different branches: |
; for pipes with interval >= 8 microframes, advance to 2, |
; for pipes which should be planned in every frame (one or more microframes), |
; go to 9. |
; Note: the actual interval for high-speed devices is 2^([.interval]-1), |
; (the core specification forbids [.interval] == 0) |
mov ecx, [.interval] |
dec ecx |
cmp ecx, 3 |
jb .every_frame |
; 2. Determine the actual interval in milliseconds. |
sub ecx, 3 |
cmp ecx, 5 ; maximum 32ms |
jbe @f |
push 5 |
pop ecx |
@@: |
; There are four nested loops, |
; * Loop #4 (the innermost one) calculates the total periodic bandwidth |
; scheduled in the given microframe of the given millisecond. |
; * Loop #3 calculates the maximum over all milliseconds |
; in the given variant, that is the quantity we're trying to minimize. |
; * Loops #1 and #2 check all variants; |
; loop #1 is responsible for the target millisecond, |
; loop #2 is responsible for the microframe within millisecond. |
; 3. Prepare for loops. |
; ebx = number of iterations of loop #1 |
; [esp] = delta of counter for loop #3, in bytes |
; [esp+4] = delta between the first group and the target group, in bytes |
push 1 |
pop ebx |
push sizeof.ehci_static_ep |
pop edx |
shl ebx, cl |
shl edx, cl |
mov eax, 64*sizeof.ehci_static_ep |
sub eax, edx |
sub eax, edx |
push eax |
push edx |
; 4. Select the best variant. |
; 4a. Loop #1: initialize counter = pointer to ehci_static_ep for |
; the target millisecond in the first group. |
lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] |
.varloop0: |
; 4b. Loop #2: initialize counter = microframe within the target millisecond. |
xor ecx, ecx |
.varloop: |
; 4c. Loop #3: save counter of loop #1, |
; initialize counter with the value of loop #1 counter, |
; initialize maximal bandwidth = zero. |
xor edi, edi |
push edx |
virtual at esp |
.saved_counter1 dd ? ; step 4c |
.loop3_delta dd ? ; step 3 |
.target_delta dd ? ; step 3 |
end virtual |
.calc_max_bandwidth: |
; 4d. Loop #4: initialize counter with the value of loop #3 counter, |
; initialize total bandwidth = zero. |
xor eax, eax |
push edx |
.calc_bandwidth: |
; 4e. Loop #4: add the bandwidth from the current list |
; and advance to the next list, while there is one. |
add ax, [edx+ehci_static_ep.Bandwidths+ecx*2] |
mov edx, [edx+ehci_static_ep.NextList] |
test edx, edx |
jnz .calc_bandwidth |
; 4f. Loop #4 end: restore counter of loop #3. |
pop edx |
; 4g. Loop #3: update maximal bandwidth. |
cmp eax, edi |
jb @f |
mov edi, eax |
@@: |
; 4h. Loop #3: advance the counter and repeat while within the first group. |
lea eax, [esi+ehci_controller.IntEDs+32*sizeof.ehci_static_ep-sizeof.ehci_controller] |
add edx, [.loop3_delta] |
cmp edx, eax |
jb .calc_max_bandwidth |
; 4i. Loop #3 end: restore counter of loop #1. |
pop edx |
; 4j. Loop #2: if the current variant is better (maybe not strictly) |
; then the previous optimum, update the optimal bandwidth and the target. |
cmp edi, [.bandwidth] |
ja @f |
mov [.bandwidth], edi |
mov [.target], edx |
push 1 |
pop eax |
shl eax, cl |
mov [.targetsmask], eax |
@@: |
; 4k. Loop #2: continue 8 times for every microframe. |
inc ecx |
cmp ecx, 8 |
jb .varloop |
; 4l. Loop #1: advance counter and repeat ebx times, |
; ebx was calculated in step 3. |
add edx, sizeof.ehci_static_ep |
dec ebx |
jnz .varloop0 |
; 5. Get the pointer to the best list. |
pop edx ; restore value from step 3 |
pop edx ; get delta calculated in step 3 |
add edx, [.target] |
; 6. Calculate bandwidth for the new pipe. |
; TODO1: calculate real bandwidth |
mov eax, [.maxpacket] |
mov ecx, eax |
and eax, (1 shl 11) - 1 |
shr ecx, 11 |
inc ecx |
and ecx, 3 |
imul eax, ecx |
; 7. TODO2: check that bandwidth for the new pipe plus old bandwidth |
; still fits to maximum allowed by the core specification |
; current [.bandwidth] + new bandwidth <= limit; |
; USB2 specification allows maximum 60000*80% bit times for periodic microframe |
; 8. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return. |
mov ecx, [.targetsmask] |
add [edx+ehci_static_ep.Bandwidths+ecx*2], ax |
add edx, ehci_static_ep.SoftwarePart |
push 1 |
pop eax |
shl eax, cl |
pop edi ebx ; restore used registers to be stdcall |
ret |
.every_frame: |
; The pipe should be scheduled every frame in two or more microframes. |
; 9. Calculate maximal bandwidth for every microframe: three nested loops. |
; 9a. The outermost loop: ebx = microframe to calculate. |
xor ebx, ebx |
.calc_all_bandwidths: |
; 9b. The intermediate loop: |
; edx = pointer to ehci_static_ep in the first group, [esp] = counter, |
; edi = maximal bandwidth |
lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller] |
xor edi, edi |
push 32 |
.calc_max_bandwidth2: |
; 9c. The innermost loop: calculate bandwidth for the given microframe |
; in the given frame. |
xor eax, eax |
push edx |
.calc_bandwidth2: |
add ax, [edx+ehci_static_ep.Bandwidths+ebx*2] |
mov edx, [edx+ehci_static_ep.NextList] |
test edx, edx |
jnz .calc_bandwidth2 |
pop edx |
; 9d. The intermediate loop continued: update maximal bandwidth. |
cmp eax, edi |
jb @f |
mov edi, eax |
@@: |
add edx, sizeof.ehci_static_ep |
dec dword [esp] |
jnz .calc_max_bandwidth2 |
pop eax |
; 9e. Push the calculated maximal bandwidth and continue the outermost loop. |
push edi |
inc ebx |
cmp ebx, 8 |
jb .calc_all_bandwidths |
virtual at esp |
.bandwidth7 dd ? |
.bandwidth6 dd ? |
.bandwidth5 dd ? |
.bandwidth4 dd ? |
.bandwidth3 dd ? |
.bandwidth2 dd ? |
.bandwidth1 dd ? |
.bandwidth0 dd ? |
end virtual |
; 10. Select the best variant. |
; edx = S-Mask = bitmask of scheduled microframes |
push 0x11 |
pop edx |
cmp ecx, 1 |
ja @f |
mov dl, 0x55 |
jz @f |
mov dl, 0xFF |
@@: |
; try all variants edx, edx shl 1, edx shl 2, ... |
; until they fit in the lower byte (8 microframes per frame) |
.select_best_mframe: |
xor edi, edi |
mov ecx, edx |
mov eax, esp |
.calc_mframe: |
add cl, cl |
jnc @f |
cmp edi, [eax] |
jae @f |
mov edi, [eax] |
@@: |
add eax, 4 |
test cl, cl |
jnz .calc_mframe |
cmp [.bandwidth], edi |
jb @f |
mov [.bandwidth], edi |
mov [.targetsmask], edx |
@@: |
add dl, dl |
jnc .select_best_mframe |
; 11. Restore stack after step 9. |
add esp, 8*4 |
; 12. Get the pointer to the target list (responsible for every microframe). |
lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+62*sizeof.ehci_static_ep-sizeof.ehci_controller] |
; 13. TODO1: calculate real bandwidth. |
mov eax, [.maxpacket] |
mov ecx, eax |
and eax, (1 shl 11) - 1 |
shr ecx, 11 |
inc ecx |
and ecx, 3 |
imul eax, ecx |
; 14. TODO2: check that current [.bandwidth] + new bandwidth <= limit; |
; USB2 specification allows maximum 60000*80% bit times for periodic microframe. |
; Update bandwidths including the new pipe. |
mov ecx, [.targetsmask] |
lea edi, [edx+ehci_static_ep.Bandwidths-ehci_static_ep.SoftwarePart] |
.update_bandwidths: |
shr ecx, 1 |
jnc @f |
add [edi], ax |
@@: |
add edi, 2 |
test ecx, ecx |
jnz .update_bandwidths |
; 15. Return target list and target S-Mask. |
mov eax, [.targetsmask] |
pop edi ebx ; restore used registers to be stdcall |
ret |
endp |
; Pipe is removing, update the corresponding lists. |
; We do not reorder anything, so just update book-keeping variable |
; in the list header. |
proc ehci_hs_interrupt_list_unlink |
; get target list |
mov edx, [ebx+ehci_pipe.BaseList-ehci_pipe.SoftwarePart] |
; TODO: calculate real bandwidth |
movzx eax, word [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart+2] |
mov ecx, [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart] |
and eax, (1 shl 11) - 1 |
shr ecx, 30 |
imul eax, ecx |
movzx ecx, byte [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart] |
add edx, ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart |
; update bandwidth |
.dec_bandwidth: |
shr ecx, 1 |
jnc @f |
sub [edx], ax |
@@: |
add edx, 2 |
test ecx, ecx |
jnz .dec_bandwidth |
; return |
ret |
endp |
uglobal |
ehci_last_fs_alloc dd ? |
endg |
; This needs to be rewritten. Seriously. |
; It schedules everything to the first microframe of some frame, |
; frame is spinned out of thin air. |
; This works while you have one keyboard and one mouse... |
; maybe even ten keyboards and ten mice... but give any serious stress, |
; and this would break. |
proc ehci_select_fs_interrupt_list |
virtual at ebp-12 |
.targetsmask dd ? |
.bandwidth dd ? |
.target dd ? |
dd ? |
dd ? |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
cmp [.interval], 1 |
adc [.interval], 0 |
mov ecx, 64 |
mov eax, ecx |
@@: |
shr ecx, 1 |
cmp [.interval], ecx |
jb @b |
sub eax, ecx |
sub eax, ecx |
dec ecx |
and ecx, [ehci_last_fs_alloc] |
inc [ehci_last_fs_alloc] |
add eax, ecx |
imul eax, sizeof.ehci_static_ep |
lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+eax-sizeof.ehci_controller] |
mov ax, 1C01h |
ret |
endp |
proc ehci_fs_interrupt_list_unlink |
ret |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/bus/usb/uhci.inc |
---|
0,0 → 1,1817 |
; Code for UHCI controllers. |
; Note: it should be moved to an external driver, |
; it was convenient to have this code compiled into the kernel during initial |
; development, but there are no reasons to keep it here. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; UHCI register declarations |
UhciCommandReg = 0 |
UhciStatusReg = 2 |
UhciInterruptReg = 4 |
UhciFrameNumberReg = 6 |
UhciBaseAddressReg = 8 |
UhciSOFModifyReg = 0Ch |
UhciPort1StatusReg = 10h |
; possible PIDs for USB data transfers |
USB_PID_SETUP = 2Dh |
USB_PID_IN = 69h |
USB_PID_OUT = 0E1h |
; UHCI does not support an interrupt on root hub status change. We must poll |
; the controller periodically. This is the period in timer ticks (10ms). |
; We use the value 100 ms: it is valid value for USB hub poll rate (1-255 ms), |
; small enough to be responsible to connect events and large enough to not |
; load CPU too often. |
UHCI_POLL_INTERVAL = 100 |
; the following constant is an invalid encoding for length fields in |
; uhci_gtd; it is used to check whether an inactive TD has been |
; completed (actual length of the transfer is valid) or not processed at all |
; (actual length of the transfer is UHCI_INVALID_LENGTH). |
; Valid values are 0-4FFh and 7FFh. We use 700h as an invalid value. |
UHCI_INVALID_LENGTH = 700h |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; UHCI-specific part of a pipe descriptor. |
; * The structure corresponds to the Queue Head aka QH from the UHCI |
; specification with some additional fields. |
; * The hardware uses first two fields (8 bytes). Next two fields are used for |
; software book-keeping. |
; * The hardware requires 16-bytes alignment of the hardware part. |
; Since the allocator (usb_allocate_common) allocates memory sequentially |
; from page start (aligned on 0x1000 bytes), size of the structure must be |
; divisible by 16. |
struct uhci_pipe |
NextQH dd ? |
; 1. First bit (bit 0) is Terminate bit. 1 = there is no next QH. |
; 2. Next bit (bit 1) is QH/TD select bit. 1 = NextQH points to QH. |
; 3. Next two bits (bits 2-3) are reserved. |
; 4. With masked 4 lower bits, this is the physical address of the next QH in |
; the QH list. |
; See also the description before NextVirt field of the usb_pipe |
; structure. Additionally to that description, the following is specific for |
; the UHCI controller: |
; * n=10, N=1024. However, this number is quite large. |
; * 1024 lists are used only for individual transfer descriptors for |
; Isochronous endpoints. This means that the software can sleep up to 1024 ms |
; before initiating the next portion of a large isochronous transfer, which |
; is a sufficiently large value. |
; * We use the 32ms upper limit for interrupt endpoint polling interval. |
; This seems to be a reasonable value. |
; * The "next" list for last Periodic list is the Control list. |
; * The "next" list for Control list is Bulk list and the "next" |
; list for Bulk list is Control list. This loop is used for bandwidth |
; reclamation: the hardware traverses lists until end-of-frame. |
HeadTD dd ? |
; 1. First bit (bit 0) is Terminate bit. 1 = there is no TDs in this QH. |
; 2. Next bit (bit 1) is QH/TD select bit. 1 = HeadTD points to QH. |
; 3. Next two bits (bits 2-3) are reserved. |
; 4. With masked 4 lower bits, this is the physical address of the first TD in |
; the TD queue for this QH. |
Token dd ? |
; This field is a template for uhci_gtd.Token field in transfer |
; descriptors. The meaning of individual bits is the same as for |
; uhci_gtd.Token, except that PID bitfield is always |
; USB_PID_SETUP/IN/OUT for control/in/out pipes, |
; the MaximumLength bitfield encodes maximum packet size, |
; the Reserved bit 20 is LowSpeedDevice bit. |
ErrorTD dd ? |
; Usually NULL. If nonzero, it is a pointer to descriptor which was error'd |
; and should be freed sometime in the future (the hardware could still use it). |
SoftwarePart rd sizeof.usb_pipe/4 |
; Common part for all controllers, described by usb_pipe structure. |
ends |
if sizeof.uhci_pipe mod 16 |
.err uhci_pipe must be 16-bytes aligned |
end if |
; This structure describes the static head of every list of pipes. |
; The hardware requires 16-bytes alignment of this structure. |
; All instances of this structure are located sequentially in uhci_controller, |
; uhci_controller is page-aligned, so it is sufficient to make this structure |
; 16-bytes aligned and verify that the first instance is 16-bytes aligned |
; inside uhci_controller. |
struct uhci_static_ep |
NextQH dd ? |
; Same as uhci_pipe.NextQH. |
HeadTD dd ? |
; Same as uhci_pipe.HeadTD. |
NextList dd ? |
; Virtual address of the next list. |
dd ? |
; Not used. |
SoftwarePart rd sizeof.usb_static_ep/4 |
; Common part for all controllers, described by usb_static_ep structure. |
dd ? |
; Padding for 16-byte alignment. |
ends |
if sizeof.uhci_static_ep mod 16 |
.err uhci_static_ep must be 16-bytes aligned |
end if |
; UHCI-specific part of controller data. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 4096 bytes and corresponds to |
; the Frame List from UHCI specification. |
; * The hardware requires page-alignment of the hardware part, so |
; the entire descriptor must be page-aligned. |
; This structure is allocated with kernel_alloc (see usb_init_controller), |
; this gives page-aligned data. |
struct uhci_controller |
; ------------------------------ hardware fields ------------------------------ |
FrameList rd 1024 |
; Entry n corresponds to the head of the frame list to be executed in |
; the frames n,n+1024,n+2048,n+3096,... |
; The first bit of each entry is Terminate bit, 1 = the frame is empty. |
; The second bit of each entry is QH/TD select bit, 1 = the entry points to |
; QH, 0 = to TD. |
; With masked 2 lower bits, the entry is a physical address of the first QH/TD |
; to be executed. |
; ------------------------------ software fields ------------------------------ |
; Every list has the static head, which is an always empty QH. |
; The following fields are static heads, one per list: |
; 32+16+8+4+2+1 = 63 for Periodic lists, 1 for Control list and 1 for Bulk list. |
IntEDs uhci_static_ep |
rb 62 * sizeof.uhci_static_ep |
ControlED uhci_static_ep |
BulkED uhci_static_ep |
IOBase dd ? |
; Base port in I/O space for UHCI controller. |
; UHCI register UhciXxx is addressed as in/out to IOBase + UhciXxx, |
; see declarations in the beginning of this source. |
DeferredActions dd ? |
; Bitmask of bits from UhciStatusReg which need to be processed |
; by uhci_process_deferred. Bit 0 = a transaction with IOC bit |
; has completed. Bit 1 = a transaction has failed. Set by uhci_irq, |
; cleared by uhci_process_deferred. |
LastPollTime dd ? |
; See the comment before UHCI_POLL_INTERVAL. This variable keeps the |
; last time, in timer ticks, when the polling was done. |
ends |
if uhci_controller.IntEDs mod 16 |
.err Static endpoint descriptors must be 16-bytes aligned inside uhci_controller |
end if |
; UHCI general transfer descriptor. |
; * The structure describes non-Isochronous data transfers |
; for the UHCI controller. |
; * The structure includes two parts, the hardware part and the software part. |
; * The hardware part consists of first 16 bytes and corresponds to the |
; Transfer Descriptor aka TD from UHCI specification. |
; * The hardware requires 16-bytes alignment of the hardware part, so |
; the entire descriptor must be 16-bytes aligned. Since the allocator |
; (uhci_allocate_common) allocates memory sequentially from page start |
; (aligned on 0x1000 bytes), size of the structure must be divisible by 16. |
struct uhci_gtd |
NextTD dd ? |
; 1. First bit (bit 0) is Terminate bit. 1 = there is no next TD. |
; 2. Next bit (bit 1) is QH/TD select bit. 1 = NextTD points to QH. |
; This bit is always set to 0 in the implementation. |
; 3. Next bit (bit 2) is Depth/Breadth select bit. 1 = the controller should |
; proceed to the NextTD after this TD is complete. 0 = the controller |
; should proceed to the next endpoint after this TD is complete. |
; The implementation sets this bit to 0 for final stages of all transactions |
; and to 1 for other stages. |
; 4. Next bit (bit 3) is reserved and must be zero. |
; 5. With masked 4 lower bits, this is the physical address of the next TD |
; in the TD list. |
ControlStatus dd ? |
; 1. Lower 11 bits (bits 0-10) are ActLen. This is written by the controller |
; at the conclusion of a USB transaction to indicate the actual number of |
; bytes that were transferred minus 1. |
; 2. Next 6 bits (bits 11-16) are reserved. |
; 3. Next bit (bit 17) signals Bitstuff error. |
; 4. Next bit (bit 18) signals CRC/Timeout error. |
; 5. Next bit (bit 19) signals NAK receive. |
; 6. Next bit (bit 20) signals Babble error. |
; 7. Next bit (bit 21) signals Data Buffer error. |
; 8. Next bit (bit 22) signals Stall error. |
; 9. Next bit (bit 23) is Active field. 1 = this TD should be processed. |
; 10. Next bit (bit 24) is InterruptOnComplete bit. 1 = the controller should |
; issue an interrupt on completion of the frame in which this TD is |
; executed. |
; 11. Next bit (bit 25) is IsochronousSelect bit. 1 = this TD is isochronous. |
; 12. Next bit (bit 26) is LowSpeedDevice bit. 1 = this TD is for low-speed. |
; 13. Next two bits (bits 27-28) are ErrorCounter field. This field is |
; decremented by the controller on every non-fatal error with this TD. |
; Babble and Stall are considered fatal errors and immediately deactivate |
; the TD without decrementing this field. 0 = no error limit, |
; n = deactivate the TD after n errors. |
; 14. Next bit (bit 29) is ShortPacketDetect bit. 1 = short packet is an error. |
; Note: the specification defines this bit as input for the controller, |
; but does not specify the value written by controller. |
; Some controllers (e.g. Intel) keep the value, some controllers (e.g. VIA) |
; set the value to whether a short packet was actually detected |
; (or something like that). |
; Thus, we duplicate this bit as bit 0 of OrigBufferInfo. |
; 15. Upper two bits (bits 30-31) are reserved. |
Token dd ? |
; 1. Lower 8 bits (bits 0-7) are PID, one of USB_PID_*. |
; 2. Next 7 bits (bits 8-14) are DeviceAddress field. This is the address of |
; the target device on the USB bus. |
; 3. Next 4 bits (bits 15-18) are Endpoint field. This is the target endpoint |
; number. |
; 4. Next bit (bit 19) is DataToggle bit. n = issue/expect DATAn token. |
; 5. Next bit (bit 20) is reserved. |
; 6. Upper 11 bits (bits 21-31) are MaximumLength field. This field specifies |
; the maximum number of data bytes for the transfer minus 1 byte. Null data |
; packet is encoded as 0x7FF, maximum possible non-null data packet is 1280 |
; bytes, encoded as 0x4FF. |
Buffer dd ? |
; Physical address of the data buffer for this TD. |
OrigBufferInfo dd ? |
; Usually NULL. If the original buffer crosses a page boundary, this is a |
; pointer to the structure uhci_original_buffer for this request. |
; bit 0: 1 = short packet is NOT allowed |
; (before the TD is processed, it is the copy of bit 29 of ControlStatus; |
; some controllers modify that bit, so we need a copy in a safe place) |
SoftwarePart rd sizeof.usb_gtd/4 |
; Software part, common for all controllers. |
ends |
if sizeof.uhci_gtd mod 16 |
.err uhci_gtd must be 16-bytes aligned |
end if |
; UHCI requires that the entire transfer buffer should be on one page. |
; If the actual buffer crosses page boundary, uhci_alloc_packet |
; allocates additional memory for buffer for hardware. |
; This structure describes correspondence between two buffers. |
struct uhci_original_buffer |
OrigBuffer dd ? |
UsedBuffer dd ? |
ends |
; Description of UHCI-specific data and functions for |
; controller-independent code. |
; Implements the structure usb_hardware_func from hccommon.inc for UHCI. |
iglobal |
align 4 |
uhci_hardware_func: |
dd 'UHCI' |
dd sizeof.uhci_controller |
dd uhci_init |
dd uhci_process_deferred |
dd uhci_set_device_address |
dd uhci_get_device_address |
dd uhci_port_disable |
dd uhci_new_port.reset |
dd uhci_set_endpoint_packet_size |
dd usb1_allocate_endpoint |
dd uhci_free_pipe |
dd uhci_init_pipe |
dd uhci_unlink_pipe |
dd usb1_allocate_general_td |
dd uhci_free_td |
dd uhci_alloc_transfer |
dd uhci_insert_transfer |
dd uhci_new_device |
endg |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
; Controller-specific initialization function. |
; Called from usb_init_controller. Initializes the hardware and |
; UHCI-specific parts of software structures. |
; eax = pointer to uhci_controller to be initialized |
; [ebp-4] = pcidevice |
proc uhci_init |
; inherit some variables from the parent (usb_init_controller) |
.devfn equ ebp - 4 |
.bus equ ebp - 3 |
; 1. Store pointer to uhci_controller for further use. |
push eax |
mov edi, eax |
mov esi, eax |
; 2. Initialize uhci_controller.FrameList. |
; Note that FrameList is located in the beginning of uhci_controller, |
; so esi and edi now point to uhci_controller.FrameList. |
; First 32 entries of FrameList contain physical addresses |
; of first 32 Periodic static heads, further entries duplicate these. |
; See the description of structures for full info. |
; Note that all static heads fit in one page, so one call to |
; get_phys_addr is sufficient. |
if (uhci_controller.IntEDs / 0x1000) <> (uhci_controller.BulkED / 0x1000) |
.err assertion failed |
end if |
; 2a. Get physical address of first static head. |
; Note that 1) it is located in the beginning of a page |
; and 2) all other static heads fit in the same page, |
; so one call to get_phys_addr without correction of lower 12 bits |
; is sufficient. |
if (uhci_controller.IntEDs mod 0x1000) <> 0 |
.err assertion failed |
end if |
add eax, uhci_controller.IntEDs |
call get_phys_addr |
; 2b. Fill first 32 entries. |
inc eax |
inc eax ; set QH bit for uhci_pipe.NextQH |
push 32 |
pop ecx |
mov edx, ecx |
@@: |
stosd |
add eax, sizeof.uhci_static_ep |
loop @b |
; 2c. Fill the rest entries. |
mov ecx, 1024 - 32 |
rep movsd |
; 3. Initialize static heads uhci_controller.*ED. |
; Use the loop over groups: first group consists of first 32 Periodic |
; descriptors, next group consists of next 16 Periodic descriptors, |
; ..., last group consists of the last Periodic descriptor. |
; 3a. Prepare for the loop. |
; make esi point to the second group, other registers are already set. |
add esi, 32*4 + 32*sizeof.uhci_static_ep |
; 3b. Loop over groups. On every iteration: |
; edx = size of group, edi = pointer to the current group, |
; esi = pointer to the next group, eax = physical address of the next group. |
.init_static_eds: |
; 3c. Get the size of next group. |
shr edx, 1 |
; 3d. Exit the loop if there is no next group. |
jz .init_static_eds_done |
; 3e. Initialize the first half of the current group. |
; Advance edi to the second half. |
push eax esi |
call uhci_init_static_ep_group |
pop esi eax |
; 3f. Initialize the second half of the current group |
; with the same values. |
; Advance edi to the next group, esi/eax to the next of the next group. |
call uhci_init_static_ep_group |
jmp .init_static_eds |
.init_static_eds_done: |
; 3g. Initialize the last static head. |
xor esi, esi |
call uhci_init_static_endpoint |
; 3i. Initialize the head of Control list. |
add eax, sizeof.uhci_static_ep |
call uhci_init_static_endpoint |
; 3j. Initialize the head of Bulk list. |
sub eax, sizeof.uhci_static_ep |
call uhci_init_static_endpoint |
; 4. Get I/O base address and size from PCI bus. |
; 4a. Read&save PCI command state. |
mov bh, [.devfn] |
mov ch, [.bus] |
mov cl, 1 |
mov eax, ecx |
mov bl, 4 |
call pci_read_reg |
push eax |
; 4b. Disable IO access. |
and al, not 1 |
push ecx |
xchg eax, ecx |
call pci_write_reg |
pop ecx |
; 4c. Read&save IO base address. |
mov eax, ecx |
mov bl, 20h |
call pci_read_reg |
and al, not 3 |
xchg eax, edi |
; now edi = IO base |
; 4d. Write 0xffff to IO base address. |
push ecx |
xchg eax, ecx |
or ecx, -1 |
call pci_write_reg |
pop ecx |
; 4e. Read IO base address. |
mov eax, ecx |
call pci_read_reg |
and al, not 3 |
cwde |
not eax |
inc eax |
xchg eax, esi |
; now esi = IO size |
; 4f. Restore IO base address. |
xchg eax, ecx |
mov ecx, edi |
push eax |
call pci_write_reg |
pop eax |
; 4g. Restore PCI command state and enable io & bus master access. |
pop ecx |
or ecx, 5 |
mov bl, 4 |
push eax |
call pci_write_reg |
pop eax |
; 5. Reset the controller. |
; 5e. Host reset. |
mov edx, edi |
mov ax, 2 |
out dx, ax |
; 5f. Wait up to 10ms. |
push 10 |
pop ecx |
@@: |
push esi |
push 1 |
pop esi |
call delay_ms |
pop esi |
in ax, dx |
test al, 2 |
loopnz @b |
jz @f |
dbgstr 'UHCI controller reset timeout' |
jmp .fail |
@@: |
if 0 |
; emergency variant for tests - always wait 10 ms |
; wait 10 ms |
push esi |
push 10 |
pop esi |
call delay_ms |
pop esi |
; clear reset signal |
xor eax, eax |
out dx, ax |
end if |
.resetok: |
; 6. Get number of ports & disable all ports. |
add esi, edi |
lea edx, [edi+UhciPort1StatusReg] |
.scanports: |
cmp edx, esi |
jae .doneports |
in ax, dx |
cmp ax, 0xFFFF |
jz .doneports |
test al, al |
jns .doneports |
xor eax, eax |
out dx, ax |
inc edx |
inc edx |
jmp .scanports |
.doneports: |
lea esi, [edx-UhciPort1StatusReg] |
sub esi, edi |
shr esi, 1 ; esi = number of ports |
jnz @f |
dbgstr 'error: no ports on UHCI controller' |
jmp .fail |
@@: |
; 7. Setup the rest of uhci_controller. |
xchg esi, [esp] ; restore the pointer to uhci_controller from the step 1 |
add esi, sizeof.uhci_controller |
pop [esi+usb_controller.NumPorts] |
DEBUGF 1,'K : UHCI controller at %x:%x with %d ports initialized\n',[.bus]:2,[.devfn]:2,[esi+usb_controller.NumPorts] |
mov [esi+uhci_controller.IOBase-sizeof.uhci_controller], edi |
mov eax, [timer_ticks] |
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax |
; 8. Hook interrupt. |
mov ah, [.bus] |
mov al, 0 |
mov bh, [.devfn] |
mov bl, 3Ch |
call pci_read_reg |
; al = IRQ |
; DEBUGF 1,'K : UHCI %x: io=%x, irq=%x\n',esi,edi,al |
movzx eax, al |
stdcall attach_int_handler, eax, uhci_irq, esi |
; 9. Setup controller registers. |
xor eax, eax |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
; 9a. UhciStatusReg := 3Fh: clear all status bits |
; (for this register 1 clears the corresponding bit, 0 does not change it). |
inc edx |
inc edx ; UhciStatusReg == 2 |
mov al, 3Fh |
out dx, ax |
; 9b. UhciInterruptReg := 0Dh. |
inc edx |
inc edx ; UhciInterruptReg == 4 |
mov al, 0Dh |
out dx, ax |
; 9c. UhciFrameNumberReg := 0. |
inc edx |
inc edx ; UhciFrameNumberReg == 6 |
mov al, 0 |
out dx, ax |
; 9d. UhciBaseAddressReg := physical address of uhci_controller. |
inc edx |
inc edx ; UhciBaseAddressReg == 8 |
lea eax, [esi-sizeof.uhci_controller] |
call get_phys_addr |
out dx, eax |
; 9e. UhciCommandReg := Run + Configured + (MaxPacket is 64 bytes) |
sub edx, UhciBaseAddressReg ; UhciCommandReg == 0 |
mov ax, 0C1h ; Run, Configured, MaxPacket = 64b |
out dx, ax |
; 10. Do initial scan of existing devices. |
call uhci_poll_roothub |
; 11. Return pointer to usb_controller. |
xchg eax, esi |
ret |
.fail: |
; On error, pop the pointer saved at step 1 and return zero. |
; Note that the main code branch restores the stack at step 7 and never fails |
; after step 7. |
pop ecx |
xor eax, eax |
ret |
endp |
; Controller-specific pre-initialization function: take ownership from BIOS. |
; UHCI has no mechanism to ask the owner politely to release ownership, |
; so do it in inpolite way, preventing controller from any SMI activity. |
proc uhci_kickoff_bios |
; 1. Get the I/O address. |
mov ah, [esi+PCIDEV.bus] |
mov al, 1 |
mov bh, [esi+PCIDEV.devfn] |
mov bl, 20h |
call pci_read_reg |
and eax, 0xFFFC |
xchg eax, edx |
; 2. Stop the controller and disable all interrupts. |
in ax, dx |
and al, not 1 |
out dx, ax |
add edx, UhciInterruptReg |
xor eax, eax |
out dx, ax |
; 3. Disable all bits for SMI routing, clear SMI routing status, |
; enable master interrupt bit. |
mov ah, [esi+PCIDEV.bus] |
mov al, 1 |
mov bl, 0xC0 |
mov ecx, 0AF00h |
call pci_write_reg |
ret |
endp |
; Helper procedure for step 3 of uhci_init. |
; Initializes the static head of one list. |
; eax = physical address of the "next" list, esi = pointer to the "next" list, |
; edi = pointer to head to initialize. |
; Advances edi to the next head, keeps eax/esi. |
proc uhci_init_static_endpoint |
mov [edi+uhci_static_ep.NextQH], eax |
mov byte [edi+uhci_static_ep.HeadTD], 1 |
mov [edi+uhci_static_ep.NextList], esi |
add edi, uhci_static_ep.SoftwarePart |
call usb_init_static_endpoint |
add edi, sizeof.uhci_static_ep - uhci_static_ep.SoftwarePart |
ret |
endp |
; Helper procedure for step 3 of uhci_init, see comments there. |
; Initializes one half of group of static heads. |
; edx = size of the next group = half of size of the group, |
; edi = pointer to the group, eax = physical address of the next group, |
; esi = pointer to the next group. |
; Advances eax, esi, edi to next group, keeps edx. |
proc uhci_init_static_ep_group |
push edx |
@@: |
call uhci_init_static_endpoint |
add eax, sizeof.uhci_static_ep |
add esi, sizeof.uhci_static_ep |
dec edx |
jnz @b |
pop edx |
ret |
endp |
; IRQ handler for UHCI controllers. |
uhci_irq.noint: |
; Not our interrupt: restore esi and return zero. |
pop esi |
xor eax, eax |
ret |
proc uhci_irq |
push esi ; save used register to be cdecl |
virtual at esp |
dd ? ; saved esi |
dd ? ; return address |
.controller dd ? |
end virtual |
mov esi, [.controller] |
; 1. Read UhciStatusReg. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
inc edx |
inc edx ; UhciStatusReg == 2 |
in ax, dx |
; 2. Test whether it is our interrupt; if so, at least one status bit is set. |
test al, 0x1F |
jz .noint |
; 3. Clear all status bits. |
out dx, ax |
; 4. Sanity check. |
test al, 0x3C |
jz @f |
DEBUGF 1,'K : something terrible happened with UHCI (%x)\n',al |
@@: |
; 5. We can't do too much from an interrupt handler, e.g. we can't take |
; any mutex locks since our code could be called when another code holds the |
; lock and has no chance to release it. Thus, only inform the processing thread |
; that it should scan the queue and wake it if needed. |
lock or byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], al |
push ebx |
xor ebx, ebx |
inc ebx |
call usb_wakeup_if_needed |
pop ebx |
; 6. This is our interrupt; return 1. |
mov al, 1 |
pop esi ; restore used register to be stdcall |
ret |
endp |
; This procedure is called in the USB thread from usb_thread_proc, |
; processes regular actions and those actions which can't be safely done |
; from interrupt handler. |
; Returns maximal time delta before the next call. |
proc uhci_process_deferred |
push ebx edi ; save used registers to be stdcall |
; 1. Initialize the return value. |
push -1 |
; 2. Poll the root hub every UHCI_POLL_INTERVAL ticks. |
; Also force polling if some transaction has completed with errors; |
; the error can be caused by disconnect, try to detect it. |
test byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], 2 |
jnz .force_poll |
mov eax, [timer_ticks] |
sub eax, [esi+uhci_controller.LastPollTime-sizeof.uhci_controller] |
sub eax, UHCI_POLL_INTERVAL |
jl .nopoll |
.force_poll: |
mov eax, [timer_ticks] |
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax |
call uhci_poll_roothub |
mov eax, -UHCI_POLL_INTERVAL |
.nopoll: |
neg eax |
cmp [esp], eax |
jb @f |
mov [esp], eax |
@@: |
; 3. Process wait lists. |
; 3a. Test whether there is a wait request. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
cmp eax, [esi+usb_controller.ReadyPipeHeadAsync] |
jnz .check_removed |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
cmp eax, [esi+usb_controller.ReadyPipeHeadPeriodic] |
jz @f |
.check_removed: |
; 3b. Yep. Find frame and compare it with the saved one. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
add edx, UhciFrameNumberReg |
in ax, dx |
cmp word [esi+usb_controller.StartWaitFrame], ax |
jnz .removed |
; 3c. The same frame; wake up in 0.01 sec. |
mov dword [esp], 1 |
jmp @f |
.removed: |
; 3d. The frame is changed, old contents is guaranteed to be forgotten. |
mov eax, [esi+usb_controller.WaitPipeRequestAsync] |
mov [esi+usb_controller.ReadyPipeHeadAsync], eax |
mov eax, [esi+usb_controller.WaitPipeRequestPeriodic] |
mov [esi+usb_controller.ReadyPipeHeadPeriodic], eax |
@@: |
; 4. Process disconnect events. This should be done after step 2 |
; (which includes the first stage of disconnect processing). |
call usb_disconnect_stage2 |
; 5. Test whether USB_CONNECT_DELAY for a connected device is over. |
; Call uhci_new_port for all such devices. |
xor ecx, ecx |
cmp [esi+usb_controller.NewConnected], ecx |
jz .skip_newconnected |
.portloop: |
bt [esi+usb_controller.NewConnected], ecx |
jnc .noconnect |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ConnectedTime+ecx*4] |
sub eax, USB_CONNECT_DELAY |
jge .connected |
neg eax |
cmp [esp], eax |
jb .nextport |
mov [esp], eax |
jmp .nextport |
.connected: |
btr [esi+usb_controller.NewConnected], ecx |
call uhci_new_port |
.noconnect: |
.nextport: |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
.skip_newconnected: |
; 6. Test for processed packets. |
; This should be done after step 4, so transfers which were failed due |
; to disconnect are marked with the exact reason, not just |
; 'device not responding'. |
xor eax, eax |
xchg byte [esi+uhci_controller.DeferredActions-sizeof.uhci_controller], al |
test al, 3 |
jz .noioc |
call uhci_process_updated_schedule |
.noioc: |
; 7. Test whether reset signalling has been started. If so, |
; either should be stopped now (if time is over) or schedule wakeup (otherwise). |
; This should be done after step 6, because a completed SET_ADDRESS command |
; could result in reset of a new port. |
.test_reset: |
; 7a. Test whether reset signalling is active. |
cmp [esi+usb_controller.ResettingStatus], 1 |
jnz .no_reset_in_progress |
; 7b. Yep. Test whether it should be stopped. |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_TIME |
jge .reset_done |
; 7c. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_done: |
; 7d. Yep, call the worker function and proceed to 7e. |
call uhci_port_reset_done |
.no_reset_in_progress: |
; 7e. Test whether reset process is done, either successful or failed. |
cmp [esi+usb_controller.ResettingStatus], 0 |
jz .skip_reset |
; 7f. Yep. Test whether it should be stopped. |
mov eax, [timer_ticks] |
sub eax, [esi+usb_controller.ResetTime] |
sub eax, USB_RESET_RECOVERY_TIME |
jge .reset_recovery_done |
; 7g. Not yet, but initiate wakeup in -eax ticks and exit this step. |
neg eax |
cmp [esp], eax |
jb .skip_reset |
mov [esp], eax |
jmp .skip_reset |
.reset_recovery_done: |
; 7h. Yep, call the worker function. This could initiate another reset, |
; so return to the beginning of this step. |
call uhci_port_init |
jmp .test_reset |
.skip_reset: |
; 8. Process wait-done notifications, test for new wait requests. |
; Note: that must be done after steps 4 and 6 which could create new requests. |
; 8a. Call the worker function. |
call usb_process_wait_lists |
; 8b. If no new requests, skip the rest of this step. |
test eax, eax |
jz @f |
; 8c. UHCI is not allowed to cache anything; we don't know what is |
; processed right now, but we can be sure that the controller will not |
; use any removed structure starting from the next frame. |
; Request removal of everything disconnected until now, |
; schedule wakeup in 0.01 sec. |
mov eax, [esi+usb_controller.WaitPipeListAsync] |
mov [esi+usb_controller.WaitPipeRequestAsync], eax |
mov eax, [esi+usb_controller.WaitPipeListPeriodic] |
mov [esi+usb_controller.WaitPipeRequestPeriodic], eax |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
add edx, UhciFrameNumberReg |
in ax, dx |
mov word [esi+usb_controller.StartWaitFrame], ax |
mov dword [esp], 1 |
@@: |
; 9. Return the value from the top of stack. |
pop eax |
pop edi ebx ; restore used registers to be stdcall. |
ret |
endp |
; This procedure is called in the USB thread from uhci_process_deferred |
; when UHCI IRQ handler has signalled that new IOC-packet was processed. |
; It scans all lists for completed packets and calls uhci_process_finalized_td |
; for those packets. |
; in: esi -> usb_controller |
proc uhci_process_updated_schedule |
; Important note: we cannot hold the list lock during callbacks, |
; because callbacks sometimes open and/or close pipes and thus acquire/release |
; the corresponding lock itself. |
; Fortunately, pipes can be finally freed only by another step of |
; uhci_process_deferred, so all pipes existing at the start of this function |
; will be valid while this function is running. Some pipes can be removed |
; from the corresponding list, some pipes can be inserted; insert/remove |
; functions guarantee that traversing one list yields all pipes that were in |
; that list at the beginning of the traversing (possibly with some new pipes, |
; possibly without some new pipes, that doesn't matter). |
; 1. Process all Periodic lists. |
lea edi, [esi+uhci_controller.IntEDs.SoftwarePart-sizeof.uhci_controller] |
lea ebx, [esi+uhci_controller.IntEDs.SoftwarePart+63*sizeof.uhci_static_ep-sizeof.uhci_controller] |
@@: |
call uhci_process_updated_list |
cmp edi, ebx |
jnz @b |
; 2. Process the Control list. |
call uhci_process_updated_list |
; 3. Process the Bulk list. |
call uhci_process_updated_list |
; 4. Return. |
ret |
endp |
; This procedure is called from uhci_process_updated_schedule, |
; see comments there. |
; It processes one list, esi -> usb_controller, edi -> usb_static_ep, |
; and advances edi to the next head. |
proc uhci_process_updated_list |
push ebx ; save used register to be stdcall |
; 1. Perform the external loop over all pipes. |
mov ebx, [edi+usb_static_ep.NextVirt] |
.loop: |
cmp ebx, edi |
jz .done |
; store pointer to the next pipe in the stack |
push [ebx+usb_static_ep.NextVirt] |
; 2. For every pipe, perform the internal loop over all descriptors. |
; All descriptors are organized in the queue; we process items from the start |
; of the queue until a) the last descriptor (not the part of the queue itself) |
; or b) an active (not yet processed by the hardware) descriptor is reached. |
lea ecx, [ebx+usb_pipe.Lock] |
call mutex_lock |
mov ebx, [ebx+usb_pipe.LastTD] |
push ebx |
mov ebx, [ebx+usb_gtd.NextVirt] |
.tdloop: |
; 3. For every descriptor, test active flag and check for end-of-queue; |
; if either of conditions holds, exit from the internal loop. |
cmp ebx, [esp] |
jz .tddone |
mov eax, [ebx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart] |
test eax, 1 shl 23 ; active? |
jnz .tddone |
; Release the queue lock while processing one descriptor: |
; callback function could (and often would) schedule another transfer. |
push ecx |
call mutex_unlock |
call uhci_process_finalized_td |
pop ecx |
call mutex_lock |
jmp .tdloop |
.tddone: |
call mutex_unlock |
pop ebx |
; End of internal loop, restore pointer to the next pipe |
; and continue the external loop. |
pop ebx |
jmp .loop |
.done: |
pop ebx ; restore used register to be stdcall |
add edi, sizeof.uhci_static_ep |
ret |
endp |
; This procedure is called from uhci_process_updated_list, which is itself |
; called from uhci_process_updated_schedule, see comments there. |
; It processes one completed descriptor. |
; in: esi -> usb_controller, ebx -> usb_gtd, out: ebx -> next usb_gtd. |
proc uhci_process_finalized_td |
; 1. Remove this descriptor from the list of descriptors for this pipe. |
call usb_unlink_td |
; DEBUGF 1,'K : finalized TD:\n' |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8] |
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8] |
; 2. If this is IN transfer into special buffer, copy the data |
; to target location. |
mov edx, [ebx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] |
and edx, not 1 ; clear lsb (used for another goal) |
jz .nocopy |
cmp byte [ebx+uhci_gtd.Token-uhci_gtd.SoftwarePart], USB_PID_IN |
jnz .nocopy |
; Note: we assume that pointer to buffer is valid in the memory space of |
; the USB thread. This means that buffer must reside in kernel memory |
; (shared by all processes). |
push esi edi |
mov esi, [edx+uhci_original_buffer.UsedBuffer] |
mov edi, [edx+uhci_original_buffer.OrigBuffer] |
mov ecx, [ebx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart] |
inc ecx |
and ecx, 7FFh |
mov edx, ecx |
shr ecx, 2 |
and edx, 3 |
rep movsd |
mov ecx, edx |
rep movsb |
pop edi esi |
.nocopy: |
; 3. Calculate actual number of bytes transferred. |
; 3a. Read the state. |
mov eax, [ebx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart] |
mov ecx, [ebx+uhci_gtd.Token-uhci_gtd.SoftwarePart] |
; 3b. Get number of bytes processed. |
lea edx, [eax+1] |
and edx, 7FFh |
; 3c. Subtract number of bytes in this packet. |
add ecx, 1 shl 21 |
shr ecx, 21 |
sub edx, ecx |
; 3d. Add total length transferred so far. |
add edx, [ebx+usb_gtd.Length] |
; Actions on error and on success are slightly different. |
; 4. Test for error. On error, proceed to step 5, otherwise go to step 6 |
; with ecx = 0 (no error). |
; USB transaction error is always considered as such. |
; If short packets are not allowed, UHCI controllers do not set an error bit, |
; but stop (clear Active bit and do not advance) the queue. |
; Short packet is considered as an error if the packet is actually short |
; (actual length is less than maximal one) and the code creating the packet |
; requested that behaviour (so bit 0 of OrigBufferInfo is set; this could be |
; because the caller disallowed short packets or because the packet is not |
; the last one in the corresponding transfer). |
xor ecx, ecx |
test eax, 1 shl 22 |
jnz .error |
test byte [ebx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], 1 |
jz .notify |
cmp edx, [ebx+usb_gtd.Length] |
jz .notify |
.error: |
; 5. There was an error while processing this packet. |
; The hardware has stopped processing the queue. |
DEBUGF 1,'K : TD failed:\n' |
if uhci_gtd.SoftwarePart <> 20 |
.err modify offsets for debug output |
end if |
DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8] |
DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8] |
; 5a. Save the status and length. |
push edx |
push eax |
mov eax, [ebx+usb_gtd.Pipe] |
DEBUGF 1,'K : pipe: %x %x\n',[eax+0-uhci_pipe.SoftwarePart],[eax+4-uhci_pipe.SoftwarePart] |
; 5b. Store the current TD as an error packet. |
; If an error packet is already stored for this pipe, |
; it is definitely not used already, so free the old packet. |
mov eax, [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] |
test eax, eax |
jz @f |
stdcall uhci_free_td, eax |
@@: |
mov eax, [ebx+usb_gtd.Pipe] |
mov [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart], ebx |
; 5c. Traverse the list of descriptors looking for the final packet |
; for this transfer. |
; Free and unlink non-final descriptors, except the current one. |
; Final descriptor will be freed in step 7. |
call usb_is_final_packet |
jnc .found_final |
mov ebx, [ebx+usb_gtd.NextVirt] |
.look_final: |
call usb_unlink_td |
call usb_is_final_packet |
jnc .found_final |
push [ebx+usb_gtd.NextVirt] |
stdcall uhci_free_td, ebx |
pop ebx |
jmp .look_final |
.found_final: |
; 5d. Restore the status saved in 5a and transform it to the error code. |
pop eax ; error code |
shr eax, 16 |
; Notes: |
; * any USB transaction error results in Stalled bit; if it is not set, |
; but we are here, it must be due to short packet; |
; * babble is considered a fatal USB transaction error, |
; other errors just lead to retrying the transaction; |
; if babble is detected, return the corresponding error; |
; * if several non-fatal errors have occured during transaction retries, |
; all corresponding bits are set. In this case, return some error code, |
; the order is quite arbitrary. |
push USB_STATUS_UNDERRUN |
pop ecx |
test al, 1 shl (22-16) ; not Stalled? |
jz .know_error |
mov cl, USB_STATUS_OVERRUN |
test al, 1 shl (20-16) ; Babble detected? |
jnz .know_error |
mov cl, USB_STATUS_BITSTUFF |
test al, 1 shl (17-16) ; Bitstuff error? |
jnz .know_error |
mov cl, USB_STATUS_NORESPONSE |
test al, 1 shl (18-16) ; CRC/TimeOut error? |
jnz .know_error |
mov cl, USB_STATUS_BUFOVERRUN |
test al, 1 shl (21-16) ; Data Buffer error? |
jnz .know_error |
mov cl, USB_STATUS_STALL |
.know_error: |
; 5e. If error code is USB_STATUS_UNDERRUN |
; and the last TD allows short packets, it is not an error. |
; Note: all TDs except the last one in any transfer stage are marked |
; as short-packet-is-error to stop controller from further processing |
; of that stage; we need to restart processing from a TD following the last. |
; After that, go to step 6 with ecx = 0 (no error). |
cmp ecx, USB_STATUS_UNDERRUN |
jnz @f |
test byte [ebx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], 1 |
jnz @f |
; The controller has stopped this queue on the error packet. |
; Update uhci_pipe.HeadTD to point to the next packet in the queue. |
call uhci_fix_toggle |
xor ecx, ecx |
.control: |
mov eax, [ebx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart] |
and al, not 0xF |
mov edx, [ebx+usb_gtd.Pipe] |
mov [edx+uhci_pipe.HeadTD-uhci_pipe.SoftwarePart], eax |
pop edx ; length |
jmp .notify |
@@: |
; 5f. Abort the entire transfer. |
; There are two cases: either there is only one transfer stage |
; (everything except control transfers), then ebx points to the last TD and |
; all previous TD were unlinked and dismissed (if possible), |
; or there are several stages (a control transfer) and ebx points to the last |
; TD of Data or Status stage (usb_is_final_packet does not stop in Setup stage, |
; because Setup stage can not produce short packets); for Data stage, we need |
; to unlink and free (if possible) one more TD and advance ebx to the next one. |
cmp [ebx+usb_gtd.Callback], 0 |
jnz .normal |
; We cannot free ErrorTD yet, it could still be used by the hardware. |
push ecx |
mov eax, [ebx+usb_gtd.Pipe] |
push [ebx+usb_gtd.NextVirt] |
cmp ebx, [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] |
jz @f |
stdcall uhci_free_td, ebx |
@@: |
pop ebx |
call usb_unlink_td |
pop ecx |
.normal: |
; 5g. For bulk/interrupt transfers we have no choice but halt the queue, |
; the driver should intercede (through some API which is not written yet). |
; Control pipes normally recover at the next SETUP transaction (first stage |
; of any control transfer), so we hope on the best and just advance the queue |
; to the next transfer. (According to the standard, "A control pipe may also |
; support functional stall as well, but this is not recommended."). |
mov edx, [ebx+usb_gtd.Pipe] |
cmp [edx+usb_pipe.Type], CONTROL_PIPE |
jz .control |
; Bulk/interrupt transfer; halt the queue. |
mov eax, [ebx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart] |
and al, not 0xF |
inc eax ; set Halted bit |
mov [edx+uhci_pipe.HeadTD-uhci_pipe.SoftwarePart], eax |
pop edx ; restore length saved in step 5a |
.notify: |
; 6. Either the descriptor in ebx was processed without errors, |
; or all necessary error actions were taken and ebx points to the last |
; related descriptor. |
; 6a. Test whether it is the last packet in the transfer |
; <=> it has an associated callback. |
mov eax, [ebx+usb_gtd.Callback] |
test eax, eax |
jz .nocallback |
; 6b. It has an associated callback; call it with corresponding parameters. |
stdcall_verify eax, [ebx+usb_gtd.Pipe], ecx, \ |
[ebx+usb_gtd.Buffer], edx, [ebx+usb_gtd.UserData] |
jmp .callback |
.nocallback: |
; 6c. It is an intermediate packet. Add its length to the length |
; in the following packet. |
mov eax, [ebx+usb_gtd.NextVirt] |
add [eax+usb_gtd.Length], edx |
.callback: |
; 7. Free the current descriptor (if allowed) and return the next one. |
; 7a. Save pointer to the next descriptor. |
push [ebx+usb_gtd.NextVirt] |
; 7b. Free the descriptor, unless it is saved as ErrorTD. |
mov eax, [ebx+usb_gtd.Pipe] |
cmp [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart], ebx |
jz @f |
stdcall uhci_free_td, ebx |
@@: |
; 7c. Restore pointer to the next descriptor and return. |
pop ebx |
ret |
endp |
; Helper procedure for restarting transfer queue. |
; When transfers are queued, their toggle bit is filled assuming that |
; everything will go without errors. On error, some packets needs to be |
; skipped, so toggle bits may become incorrect. |
; This procedure fixes toggle bits. |
; in: ebx -> last packet to be skipped, ErrorTD -> last processed packet |
proc uhci_fix_toggle |
; 1. Nothing to do for control pipes: in that case, |
; toggle bits for different transfer stages are independent. |
mov ecx, [ebx+usb_gtd.Pipe] |
cmp [ecx+usb_pipe.Type], CONTROL_PIPE |
jz .nothing |
; 2. The hardware expects next packet with toggle = (ErrorTD.toggle xor 1), |
; the current value in next packet is (ebx.toggle xor 1). |
; Nothing to do if ErrorTD.toggle == ebx.toggle. |
mov eax, [ecx+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] |
mov eax, [eax+uhci_gtd.Token-uhci_gtd.SoftwarePart] |
xor eax, [ebx+uhci_gtd.Token-uhci_gtd.SoftwarePart] |
test eax, 1 shl 19 |
jz .nothing |
; 3. Lock the transfer queue. |
add ecx, usb_pipe.Lock |
call mutex_lock |
; 4. Flip the toggle bit in all packets from ebx.NextVirt to ecx.LastTD |
; (inclusive). |
mov eax, [ebx+usb_gtd.NextVirt] |
.loop: |
xor byte [eax+uhci_gtd.Token-uhci_gtd.SoftwarePart+2], 1 shl (19-16) |
cmp eax, [ecx+usb_pipe.LastTD-usb_pipe.Lock] |
mov eax, [eax+usb_gtd.NextVirt] |
jnz .loop |
; 5. Flip the toggle bit in uhci_pipe structure. |
xor byte [ecx+uhci_pipe.Token-uhci_pipe.SoftwarePart-usb_pipe.Lock+2], 1 shl (19-16) |
or dword [ecx+uhci_pipe.Token-uhci_pipe.SoftwarePart-usb_pipe.Lock], eax |
; 6. Unlock the transfer queue. |
call mutex_unlock |
.nothing: |
ret |
endp |
; This procedure is called in the USB thread from uhci_process_deferred |
; every UHCI_POLL_INTERVAL ticks. It polls the controller for |
; connect/disconnect events. |
; in: esi -> usb_controller |
proc uhci_poll_roothub |
push ebx ; save used register to be stdcall |
; 1. Prepare for the loop for every port. |
xor ecx, ecx |
.portloop: |
; 2. Some implementations of UHCI set ConnectStatusChange bit in a response to |
; PortReset. Thus, we must ignore this change for port which is resetting. |
cmp cl, [esi+usb_controller.ResettingPort] |
jz .nextport |
; 3. Read port status. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
; 4. If no change bits are set, continue to the next port. |
test al, 0Ah |
jz .nextport |
; 5. Clear change bits and read the status again. |
; (It is possible, although quite unlikely, that some event occurs between |
; the first read and the clearing, invalidating the old status. If an event |
; occurs after the clearing, we will not miss it, looking in the next scan. |
out dx, ax |
mov ebx, eax |
in ax, dx |
; 6. Process connect change notifications. |
; Note: if connect status has changed, ignore enable status change; |
; it is normal to disable a port at disconnect event. |
; Some controllers set enable status change bit, some don't. |
test bl, 2 |
jz .noconnectchange |
DEBUGF 1,'K : [%d] UHCI %x connect status changed, %x/%x\n',[timer_ticks],esi,bx,ax |
; yep. Regardless of the current status, note disconnect event; |
; if there is something connected, store the connect time and note connect event. |
; In any way, do not process |
bts [esi+usb_controller.NewDisconnected], ecx |
test al, 1 |
jz .disconnect |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ConnectedTime+ecx*4], eax |
bts [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.disconnect: |
btr [esi+usb_controller.NewConnected], ecx |
jmp .nextport |
.noconnectchange: |
; 7. Process enable change notifications. |
; Note: that needs work. |
test bl, 8 |
jz .nextport |
test al, 4 |
jnz .nextport |
dbgstr 'Port disabled' |
.nextport: |
; 8. Continue the loop for every port. |
inc ecx |
cmp ecx, [esi+usb_controller.NumPorts] |
jb .portloop |
pop ebx ; restore used register to be stdcall |
ret |
endp |
; This procedure is called from uhci_process_deferred when |
; a new device was connected at least USB_CONNECT_DELAY ticks |
; and therefore is ready to be configured. |
; in: esi -> usb_controller, ecx = port (zero-based) |
proc uhci_new_port |
; test whether we are configuring another port |
; if so, postpone configuring and return |
bts [esi+usb_controller.PendingPorts], ecx |
cmp [esi+usb_controller.ResettingPort], -1 |
jnz .nothing |
btr [esi+usb_controller.PendingPorts], ecx |
; fall through to uhci_new_port.reset |
; This function is called from uhci_new_port and uhci_test_pending_port. |
; It starts reset signalling for the port. Note that in USB first stages |
; of configuration can not be done for several ports in parallel. |
.reset: |
; 1. Store information about resetting hub (roothub) and port. |
and [esi+usb_controller.ResettingHub], 0 |
mov [esi+usb_controller.ResettingPort], cl |
; 2. Initiate reset signalling. |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
or ah, 2 |
out dx, ax |
; 3. Store the current time and set status to 1 = reset signalling active. |
mov eax, [timer_ticks] |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 1 |
.nothing: |
ret |
endp |
; This procedure is called from uhci_process_deferred when |
; reset signalling for a port needs to be finished. |
proc uhci_port_reset_done |
; 1. Stop reset signalling. |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
DEBUGF 1,'K : [%d] UHCI %x status %x/',[timer_ticks],esi,ax |
and ah, not 2 |
out dx, ax |
; 2. Status bits in UHCI are invalid during reset signalling. |
; Wait a millisecond while status bits become valid again. |
push esi |
push 1 |
pop esi |
call delay_ms |
pop esi |
; 3. ConnectStatus bit is zero during reset and becomes 1 during step 2; |
; some controllers interpret this as a (fake) connect event. |
; Enable port and clear status change notification. |
in ax, dx |
DEBUGF 1,'%x\n',ax |
or al, 6 ; enable port, clear status change |
out dx, ax |
; 4. Store the current time and set status to 2 = reset recovery active. |
mov eax, [timer_ticks] |
DEBUGF 1,'K : reset done at %d\n',[timer_ticks] |
mov [esi+usb_controller.ResetTime], eax |
mov [esi+usb_controller.ResettingStatus], 2 |
ret |
endp |
; This procedure is called from uhci_process_deferred when |
; a new device has been reset, recovered after reset and |
; needs to be configured. |
; in: esi -> usb_controller |
proc uhci_port_init |
; 1. Read port status. |
mov [esi+usb_controller.ResettingStatus], 0 |
movzx ecx, [esi+usb_controller.ResettingPort] |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+ecx*2+UhciPort1StatusReg] |
in ax, dx |
DEBUGF 1,'K : [%d] UHCI %x status %x\n',[timer_ticks],esi,ax |
; 2. If the device has been disconnected, stop the initialization. |
test al, 1 |
jnz @f |
dbgstr 'USB port disabled after reset' |
jmp usb_test_pending_port |
@@: |
; 3. Copy LowSpeed bit to bit 0 of eax and call the worker procedure |
; to notify the protocol layer about new UHCI device. |
push edx |
mov al, ah |
call uhci_new_device |
pop edx |
test eax, eax |
jnz .nothing |
; 4. If something at the protocol layer has failed |
; (no memory, no bus address), disable the port and stop the initialization. |
.disable_exit: |
in ax, dx |
and al, not 4 |
out dx, ax ; disable the port |
jmp usb_test_pending_port |
.nothing: |
ret |
endp |
; This procedure is called from uhci_port_init and from hub support code |
; when a new device is connected and has been reset. |
; It calls usb_new_device at the protocol layer with correct parameters. |
; in: esi -> usb_controller, eax = speed; |
; UHCI is USB1 device, so only low bit of eax (LowSpeed) is used. |
proc uhci_new_device |
; 1. Clear all bits of speed except bit 0. |
and eax, 1 |
; 2. Store the speed for the protocol layer. |
mov [esi+usb_controller.ResettingSpeed], al |
; 3. Create pseudo-pipe in the stack. |
; See uhci_init_pipe: only .Controller and .Token fields are used. |
push esi ; fill .Controller field |
mov ecx, esp |
shl eax, 20 ; bit 20 = LowSpeedDevice |
push eax ; ignored (ErrorTD) |
push eax ; .Token field: DeviceAddress is zero, bit 20 = LowSpeedDevice |
; 4. Notify the protocol layer. |
call usb_new_device |
; 5. Cleanup the stack after step 3 and return. |
add esp, 12 |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; and stores USB device address in the uhci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address |
proc uhci_set_device_address |
mov byte [ebx+uhci_pipe.Token+1-uhci_pipe.SoftwarePart], cl |
call usb_subscription_done |
ret |
endp |
; This procedure returns USB device address from the uhci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe |
; out: eax = endpoint address |
proc uhci_get_device_address |
mov al, byte [ebx+uhci_pipe.Token+1-uhci_pipe.SoftwarePart] |
and eax, 7Fh |
ret |
endp |
; This procedure is called from usb_set_address_callback |
; if the device does not accept SET_ADDRESS command and needs |
; to be disabled at the port level. |
; in: esi -> usb_controller, ecx = port (zero-based) |
proc uhci_port_disable |
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller] |
lea edx, [edx+UhciPort1StatusReg+ecx*2] |
in ax, dx |
and al, not 4 |
out dx, ax |
ret |
endp |
; This procedure is called from usb_get_descr8_callback when |
; the packet size for zero endpoint becomes known and |
; stores the packet size in uhci_pipe structure. |
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size |
proc uhci_set_endpoint_packet_size |
dec ecx |
shl ecx, 21 |
and [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart], (1 shl 21) - 1 |
or [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart], ecx |
; uhci_pipe.Token field is purely for software bookkeeping and does not affect |
; the hardware; thus, we can continue initialization immediately. |
call usb_subscription_done |
ret |
endp |
; This procedure is called from API usb_open_pipe and processes |
; the controller-specific part of this API. See docs. |
; in: edi -> usb_pipe for target, ecx -> usb_pipe for config pipe, |
; esi -> usb_controller, eax -> usb_gtd for the first TD, |
; [ebp+12] = endpoint, [ebp+16] = maxpacket, [ebp+20] = type |
proc uhci_init_pipe |
; inherit some variables from the parent usb_open_pipe |
virtual at ebp+8 |
.config_pipe dd ? |
.endpoint dd ? |
.maxpacket dd ? |
.type dd ? |
.interval dd ? |
end virtual |
; 1. Initialize ErrorTD to zero. |
and [edi+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart], 0 |
; 2. Initialize HeadTD to the physical address of the first TD. |
push eax ; store pointer to the first TD for step ? |
sub eax, uhci_gtd.SoftwarePart |
call get_phys_addr |
mov [edi+uhci_pipe.HeadTD-uhci_pipe.SoftwarePart], eax |
; 3. Initialize Token field: |
; take DeviceAddress and LowSpeedDevice from the parent pipe, |
; take Endpoint and MaximumLength fields from API arguments, |
; set PID depending on pipe type and provided pipe direction, |
; set DataToggle to zero. |
mov eax, [ecx+uhci_pipe.Token-uhci_pipe.SoftwarePart] |
and eax, 0x107F00 ; keep DeviceAddress and LowSpeedDevice |
mov edx, [.endpoint] |
and edx, 15 |
shl edx, 15 |
or eax, edx |
mov edx, [.maxpacket] |
dec edx |
shl edx, 21 |
or eax, edx |
mov al, USB_PID_SETUP |
cmp [.type], CONTROL_PIPE |
jz @f |
mov al, USB_PID_OUT |
test byte [.endpoint], 80h |
jz @f |
mov al, USB_PID_IN |
@@: |
mov [edi+uhci_pipe.Token-uhci_pipe.SoftwarePart], eax |
; 4. Initialize the first TD: |
; copy Token from uhci_pipe.Token zeroing reserved bit 20, |
; set ControlStatus for future transfers, bit make it inactive, |
; set bit 0 in NextTD = "no next TD". |
pop edx ; restore pointer saved in step 2 |
mov [edx+uhci_gtd.Token-uhci_gtd.SoftwarePart], eax |
and byte [edx+uhci_gtd.Token+2-uhci_gtd.SoftwarePart], not (1 shl (20-16)) |
and eax, 1 shl 20 |
shl eax, 6 |
or eax, UHCI_INVALID_LENGTH + (3 shl 27) |
; not processed, inactive, allow 3 errors |
mov [edx+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart], eax |
mov [edx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], 1 |
; 5. Select the corresponding list and insert to the list. |
; 5a. Use Control list for control pipes, Bulk list for bulk pipes. |
lea edx, [esi+uhci_controller.ControlED.SoftwarePart-sizeof.uhci_controller] |
cmp [.type], BULK_PIPE |
jb .insert ; control pipe |
lea edx, [esi+uhci_controller.BulkED.SoftwarePart-sizeof.uhci_controller] |
jz .insert ; bulk pipe |
.interrupt_pipe: |
; 5b. For interrupt pipes, let the scheduler select the appropriate list |
; based on the current bandwidth distribution and the requested bandwidth. |
; This could fail if the requested bandwidth is not available; |
; if so, return an error. |
lea edx, [esi + uhci_controller.IntEDs - sizeof.uhci_controller] |
lea eax, [esi + uhci_controller.IntEDs + 32*sizeof.uhci_static_ep - sizeof.uhci_controller] |
push 64 |
pop ecx |
call usb1_select_interrupt_list |
test edx, edx |
jz .return0 |
.insert: |
; Insert to the head of the corresponding list. |
; Note: inserting to the head guarantees that the list traverse in |
; uhci_process_updated_schedule, once started, will not interact with new pipes. |
; However, we still need to ensure that links in the new pipe (edi.NextVirt) |
; are initialized before links to the new pipe (edx.NextVirt). |
; 5c. Insert in the list of virtual addresses. |
mov ecx, [edx+usb_pipe.NextVirt] |
mov [edi+usb_pipe.NextVirt], ecx |
mov [edi+usb_pipe.PrevVirt], edx |
mov [ecx+usb_pipe.PrevVirt], edi |
mov [edx+usb_pipe.NextVirt], edi |
; 5d. Insert in the hardware list: copy previous NextQH to the new pipe, |
; store the physical address of the new pipe to previous NextQH. |
mov ecx, [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart] |
mov [edi+uhci_pipe.NextQH-uhci_pipe.SoftwarePart], ecx |
lea eax, [edi-uhci_pipe.SoftwarePart] |
call get_phys_addr |
inc eax |
inc eax |
mov [edx+uhci_static_ep.NextQH-uhci_static_ep.SoftwarePart], eax |
; 6. Return with nonzero eax. |
ret |
.return0: |
xor eax, eax |
ret |
endp |
; This procedure is called when a pipe is closing (either due to API call |
; or due to disconnect); it unlinks a pipe from the corresponding list. |
if uhci_static_ep.SoftwarePart <> uhci_pipe.SoftwarePart |
.err uhci_unlink_pipe assumes that uhci_static_ep.SoftwarePart == uhci_pipe.SoftwarePart |
end if |
proc uhci_unlink_pipe |
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE |
jnz @f |
mov eax, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] |
cmp al, USB_PID_IN |
setz ch |
bt eax, 20 |
setc cl |
add eax, 1 shl 21 |
shr eax, 21 |
stdcall usb1_interrupt_list_unlink, eax, ecx |
@@: |
; Note: we need to ensure that NextVirt field of the pipe is not modified; |
; this procedure can be called while uhci_process_updated_schedule processes |
; the same pipe, and it needs a correct NextVirt field to continue. |
mov edx, [ebx+usb_pipe.NextVirt] |
mov eax, [ebx+usb_pipe.PrevVirt] |
mov [edx+usb_pipe.PrevVirt], eax |
mov [eax+usb_pipe.NextVirt], edx |
; Note: eax could be either usb_pipe or usb_static_ep; |
; fortunately, NextQH and SoftwarePart have same offsets in both. |
mov edx, [ebx+uhci_pipe.NextQH-uhci_pipe.SoftwarePart] |
mov [eax+uhci_pipe.NextQH-uhci_pipe.SoftwarePart], edx |
ret |
endp |
; Free memory associated with pipe. |
; For UHCI, this includes usb_pipe structure and ErrorTD, if present. |
proc uhci_free_pipe |
mov eax, [esp+4] |
mov eax, [eax+uhci_pipe.ErrorTD-uhci_pipe.SoftwarePart] |
test eax, eax |
jz @f |
stdcall uhci_free_td, eax |
@@: |
jmp usb1_free_endpoint |
endp |
; This procedure is called from the several places in main USB code |
; and allocates required packets for the given transfer stage. |
; ebx = pipe, other parameters are passed through the stack |
proc uhci_alloc_transfer stdcall uses edi, buffer:dword, size:dword, flags:dword, td:dword, direction:dword |
locals |
token dd ? |
origTD dd ? |
packetSize dd ? ; must be the last variable, see usb_init_transfer |
endl |
; 1. [td] will be the first packet in the transfer. |
; Save it to allow unrolling if something will fail. |
mov eax, [td] |
mov [origTD], eax |
; In UHCI one TD describes one packet, transfers should be split into parts |
; with size <= endpoint max packet size. |
; 2. Get the maximum packet size for endpoint from uhci_pipe.Token |
; and generate Token field for TDs. |
mov edi, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] |
mov eax, edi |
shr edi, 21 |
inc edi |
; zero packet size (it will be set for every packet individually), |
; zero reserved bit 20, |
and eax, (1 shl 20) - 1 |
mov [packetSize], edi |
; set the correct PID if it is different from the pipe-wide PID |
; (Data and Status stages of control transfers), |
mov ecx, [direction] |
and ecx, 3 |
jz @f |
mov al, USB_PID_OUT |
dec ecx |
jz @f |
mov al, USB_PID_IN |
@@: |
; set the toggle bit for control transfers, |
mov ecx, [direction] |
test cl, 1 shl 3 |
jz @f |
and ecx, 1 shl 2 |
and eax, not (1 shl 19) |
shl ecx, 19-2 |
or eax, ecx |
@@: |
; store the resulting Token in the stack variable. |
mov [token], eax |
; 3. While the remaining data cannot fit in one packet, |
; allocate full packets (of maximal possible size). |
.fullpackets: |
cmp [size], edi |
jbe .lastpacket |
call uhci_alloc_packet |
test eax, eax |
jz .fail |
mov [td], eax |
add [buffer], edi |
sub [size], edi |
jmp .fullpackets |
.lastpacket: |
; 4. The remaining data can fit in one packet; |
; allocate the last packet with size = size of remaining data. |
mov eax, [size] |
mov [packetSize], eax |
call uhci_alloc_packet |
test eax, eax |
jz .fail |
; 5. Clear 'short packets are not allowed' bit for the last packet, |
; if the caller requested this. |
; Note: even if the caller says that short transfers are ok, |
; all packets except the last one are marked as 'must be complete': |
; if one of them will be short, the software intervention is needed |
; to skip remaining packets; uhci_process_finalized_td will handle this |
; transparently to the caller. |
test [flags], 1 |
jz @f |
and byte [ecx+uhci_gtd.ControlStatus+3-uhci_gtd.SoftwarePart], not (1 shl (29-24)) |
and byte [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], not 1 |
@@: |
; 6. Update toggle bit in uhci_pipe structure from current value of [token]. |
mov edx, [token] |
xor edx, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] |
and edx, 1 shl 19 |
xor [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart], edx |
.nothing: |
ret |
.fail: |
mov edi, uhci_hardware_func |
mov eax, [td] |
stdcall usb_undo_tds, [origTD] |
xor eax, eax |
jmp .nothing |
endp |
; Helper procedure for uhci_alloc_transfer. Allocates one packet. |
proc uhci_alloc_packet |
; inherit some variables from the parent uhci_alloc_transfer |
virtual at ebp-12 |
.token dd ? |
.origTD dd ? |
.packetSize dd ? |
rd 2 |
.buffer dd ? |
.transferSize dd ? |
.Flags dd ? |
.td dd ? |
.direction dd ? |
end virtual |
; 1. In UHCI all data for one packet must be on the same page. |
; Thus, if the given buffer splits page boundary, we need a temporary buffer |
; and code that transfers data between the given buffer and the temporary one. |
; 1a. There is no buffer for zero-length packets. |
xor eax, eax |
cmp [.packetSize], eax |
jz .notempbuf |
; 1b. A temporary buffer is not required if the first and the last bytes |
; of the given buffer are the same except lower 12 bits. |
mov edx, [.buffer] |
add edx, [.packetSize] |
dec edx |
xor edx, [.buffer] |
test edx, -0x1000 |
jz .notempbuf |
; 1c. We need a temporary buffer. Allocate [packetSize]*2 bytes, so that |
; there must be [packetSize] bytes on one page, |
; plus space for a header uhci_original_buffer. |
push ebx |
mov eax, [.packetSize] |
add eax, eax |
add eax, sizeof.uhci_original_buffer |
call malloc |
pop ebx |
; 1d. If failed, return zero. |
test eax, eax |
jz .nothing |
; 1e. Test whether [.packetSize] bytes starting from |
; eax + sizeof.uhci_original_buffer are in the same page. |
; If so, use eax + sizeof.uhci_original_buffer as a temporary buffer. |
; Otherwise, use the beginning of the next page as a temporary buffer |
; (since we have overallocated, sufficient space must remain). |
lea ecx, [eax+sizeof.uhci_original_buffer] |
mov edx, ecx |
add edx, [.packetSize] |
dec edx |
xor edx, ecx |
test edx, -0x1000 |
jz @f |
mov ecx, eax |
or ecx, 0xFFF |
inc ecx |
@@: |
mov [eax+uhci_original_buffer.UsedBuffer], ecx |
mov ecx, [.buffer] |
mov [eax+uhci_original_buffer.OrigBuffer], ecx |
; 1f. For SETUP and OUT packets, copy data from the given buffer |
; to the temporary buffer now. For IN packets, data go in other direction |
; when the transaction completes. |
cmp byte [.token], USB_PID_IN |
jz .nocopy |
push esi edi |
mov esi, ecx |
mov edi, [eax+uhci_original_buffer.UsedBuffer] |
mov ecx, [.packetSize] |
mov edx, ecx |
shr ecx, 2 |
and edx, 3 |
rep movsd |
mov ecx, edx |
rep movsb |
pop edi esi |
.nocopy: |
.notempbuf: |
; 2. Allocate the next TD. |
push eax |
call usb1_allocate_general_td |
pop edx |
; If failed, free the temporary buffer (if it was allocated) and return zero. |
test eax, eax |
jz .fail |
; 3. Initialize controller-independent parts of both TDs. |
push edx |
call usb_init_transfer |
; 4. Initialize the next TD: |
; mark it as last one (this will be changed when further packets will be |
; allocated), copy Token field from uhci_pipe.Token zeroing bit 20, |
; generate ControlStatus field, mark as Active |
; (for last descriptor, this will be changed by uhci_insert_transfer). |
mov [eax+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], 1 ; no next TD |
mov edx, [ebx+uhci_pipe.Token-uhci_pipe.SoftwarePart] |
mov [eax+uhci_gtd.Token-uhci_gtd.SoftwarePart], edx |
and byte [eax+uhci_gtd.Token+2-uhci_gtd.SoftwarePart], not (1 shl (20-16)) |
and edx, 1 shl 20 |
shl edx, 6 |
or edx, UHCI_INVALID_LENGTH + (1 shl 23) + (3 shl 27) |
; not processed, active, allow 3 errors |
mov [eax+uhci_gtd.ControlStatus-uhci_gtd.SoftwarePart], edx |
; 5. Initialize remaining fields of the current TD. |
; 5a. Store pointer to the buffer allocated in step 1 (or zero). |
pop [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] |
; 5b. Store physical address of the next TD. |
push eax |
sub eax, uhci_gtd.SoftwarePart |
call get_phys_addr |
; use Depth traversal unless this is the first TD in the transfer stage; |
; uhci_insert_transfer will set Depth traversal for the first TD and clear |
; it in the last TD |
cmp ecx, [ebx+usb_pipe.LastTD] |
jz @f |
or eax, 4 |
@@: |
mov [ecx+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], eax |
; 5c. Store physical address of the buffer: zero if no data present, |
; the temporary buffer if it was allocated, the given buffer otherwise. |
xor eax, eax |
cmp [.packetSize], eax |
jz .hasphysbuf |
mov eax, [.buffer] |
mov edx, [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] |
test edx, edx |
jz @f |
mov eax, [edx+uhci_original_buffer.UsedBuffer] |
@@: |
call get_phys_addr |
.hasphysbuf: |
mov [ecx+uhci_gtd.Buffer-uhci_gtd.SoftwarePart], eax |
; 5d. For IN transfers, disallow short packets. |
; This will be overridden, if needed, by uhci_alloc_transfer. |
mov eax, [.token] |
mov edx, [.packetSize] |
dec edx |
cmp al, USB_PID_IN |
jnz @f |
or byte [ecx+uhci_gtd.ControlStatus+3-uhci_gtd.SoftwarePart], 1 shl (29-24) ; disallow short packets |
or byte [ecx+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart], 1 |
@@: |
; 5e. Get Token field: combine [.token] with [.packetSize]. |
shl edx, 21 |
or edx, eax |
mov [ecx+uhci_gtd.Token-uhci_gtd.SoftwarePart], edx |
; 6. Flip toggle bit in [.token]. |
xor eax, 1 shl 19 |
mov [.token], eax |
; 7. Return pointer to the next TD. |
pop eax |
.nothing: |
ret |
.fail: |
xchg eax, edx |
call free |
xor eax, eax |
ret |
endp |
; This procedure is called from the several places in main USB code |
; and activates the transfer which was previously allocated by |
; uhci_alloc_transfer. |
; ecx -> last descriptor for the transfer, ebx -> usb_pipe |
proc uhci_insert_transfer |
; DEBUGF 1,'K : uhci_insert_transfer: eax=%x, ecx=%x, [esp+4]=%x\n',eax,ecx,[esp+4] |
and byte [eax+uhci_gtd.ControlStatus+2-uhci_gtd.SoftwarePart], not (1 shl (23-16)) ; clear Active bit |
or byte [ecx+uhci_gtd.ControlStatus+3-uhci_gtd.SoftwarePart], 1 shl (24-24) ; set InterruptOnComplete bit |
mov eax, [esp+4] |
or byte [eax+uhci_gtd.ControlStatus+2-uhci_gtd.SoftwarePart], 1 shl (23-16) ; set Active bit |
or byte [eax+uhci_gtd.NextTD-uhci_gtd.SoftwarePart], 4 ; set Depth bit |
ret |
endp |
; Free all memory associated with one TD. |
; For UHCI, this includes memory for uhci_gtd itself |
; and the temporary buffer, if present. |
proc uhci_free_td |
mov eax, [esp+4] |
mov eax, [eax+uhci_gtd.OrigBufferInfo-uhci_gtd.SoftwarePart] |
and eax, not 1 |
jz .nobuf |
push ebx |
call free |
pop ebx |
.nobuf: |
sub dword [esp+4], uhci_gtd.SoftwarePart |
jmp usb_free_common |
endp |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/const.inc |
---|
634,6 → 634,17 |
srv_proc_ex dd ? ;+0x2C ;kernel mode service handler |
ends |
struct USBSRV |
srv SRV |
usb_func dd ? |
ends |
struct USBFUNC |
strucsize dd ? |
add_device dd ? |
device_disconnect dd ? |
ends |
DRV_ENTRY equ 1 |
DRV_EXIT equ -1 |
/kernel/trunk/core/dll.inc |
---|
142,7 → 142,11 |
cmp [edi+SRV.size], sizeof.SRV |
jne .fail |
stdcall [edi+SRV.srv_proc], esi |
; stdcall [edi+SRV.srv_proc], esi |
mov eax, [edi+SRV.srv_proc] |
test eax, eax |
jz .fail |
stdcall eax, esi |
ret |
.fail: |
xor eax, eax |
174,7 → 178,11 |
cmp [eax+SRV.size], sizeof.SRV |
jne .fail |
stdcall [eax+SRV.srv_proc], ecx |
; stdcall [eax+SRV.srv_proc], ecx |
mov eax, [eax+SRV.srv_proc] |
test eax, eax |
jz .fail |
stdcall eax, ecx |
ret |
.fail: |
or eax, -1 |
213,9 → 221,31 |
ret |
endp |
align 4 |
proc reg_service stdcall, name:dword, handler:dword |
reg_service: |
xor eax, eax |
mov ecx, [esp+8] |
jecxz .nothing |
push sizeof.SRV |
push ecx |
pushd [esp+12] |
call reg_service_ex |
.nothing: |
ret 8 |
reg_usb_driver: |
push sizeof.USBSRV |
pushd [esp+12] |
pushd [esp+12] |
call reg_service_ex |
test eax, eax |
jz .nothing |
mov ecx, [esp+12] |
mov [eax+USBSRV.usb_func], ecx |
.nothing: |
ret 12 |
proc reg_service_ex stdcall, name:dword, handler:dword, srvsize:dword |
push ebx |
xor eax, eax |
223,10 → 253,10 |
cmp [name], eax |
je .fail |
cmp [handler], eax |
je .fail |
; cmp [handler], eax |
; je .fail |
mov eax, sizeof.SRV |
mov eax, [srvsize] |
call malloc |
test eax, eax |
jz .fail |
/kernel/trunk/core/taskman.inc |
---|
550,7 → 550,7 |
xor edx, edx |
push edx |
mov eax, 0x2 |
mov eax, 0x1 |
mov ebx, [pg_dir] |
.loop: |
;eax = current slot of process |
/kernel/trunk/detect/biosdisk.inc |
---|
7,6 → 7,7 |
; Detect all BIOS hard drives. |
; diamond, 2008 |
; Do not include USB mass storages. CleverMouse, 2013 |
xor cx, cx |
mov es, cx |
24,21 → 25,40 |
test ah, ah |
jz bddc |
inc cx |
; We are going to call int 13h/func 48h, Extended get drive parameters. |
; The latest version of the EDD specification is 3.0. |
; There are two slightly incompatible variants for version 3.0; |
; original one from Phoenix in 1998, see e.g. |
; http://www.t10.org/t13/technical/d98120r0.pdf, and T13 draft, |
; http://www.t13.org/documents/UploadedDocuments/docs2004/d1572r3-EDD3.pdf |
; T13 draft addresses more possible buses, so it gives additional 8 bytes |
; for device path. |
; Most BIOSes follow Phoenix, but T13 version is also known to be used |
; (e.g. systems based on AMD Geode). |
; Fortunately, there is an in/out length field, so |
; it is easy to tell what variant was selected by the BIOS: |
; Phoenix-3.0 has 42h bytes, T13-3.0 has 4Ah bytes. |
; Note that 2.0 has 1Eh bytes, 1.1 has 1Ah bytes; both variants of 3.0 have |
; the same structure for first 1Eh bytes, compatible with previous versions. |
; Note also that difference between Phoenix-3.0 and T13-3.0 starts near the |
; end of the structure, so the current code doesn't even need to distinguish. |
; It needs, however, give at least 4Ah bytes as input and expect that BIOS |
; could return 42h bytes as output while still giving all the information. |
mov ah, 48h |
push ds |
push es |
pop ds |
mov si, 0xA000 |
mov word [si], 1Eh |
mov word [si], 4Ah |
mov ah, 48h |
int 13h |
pop ds |
jc bddc2 |
inc byte [es:0x907F] |
cmp word [es:si], 1Eh |
jb bddl |
jb .noide |
cmp word [es:si+1Ah], 0xFFFF |
jz bddl |
jz .noide |
inc byte [es:0x907F] |
mov al, dl |
stosb |
push ds |
61,7 → 81,15 |
stosw |
pop ds |
jmp bddc2 |
bddl: |
.noide: |
cmp word [es:si], 42h |
jb .nousb |
cmp word [es:si+28h], 'US' |
jnz .nousb |
cmp byte [es:si+2Ah], 'B' |
jz bddc2 |
.nousb: |
inc byte [es:0x907F] |
mov al, dl |
stosb |
xor ax, ax |
/kernel/trunk/docs/usbapi.txt |
---|
0,0 → 1,183 |
When the kernel detects a connected USB device, it configures the device in |
terms of USB protocol - SET_ADDRESS + SET_CONFIGURATION, the first |
configuration is always selected. The kernel also reads device descriptor to |
print some information, reads and parses configuration descriptor. For every |
interface the kernel looks for class code of this interface and loads the |
corresponding COFF driver. Currently the correspondence is hardcoded into |
the kernel code and looks as follows: 3 = usbhid.obj, 8 = usbstor.obj, |
9 is handled by the kernel itself, other = usbother.obj. |
The driver must be standard driver in COFF format, exporting procedure |
named "START" and a variable named "version". Loader calls "START" procedure |
as stdcall with one parameter DRV_ENTRY = 1; if initialization is successful, |
the "START" procedure is also called by shutdown code with one parameter |
DRV_EXIT = -1. |
The driver must register itself as a USB driver in "START" procedure. |
This is done by call to exported function RegUSBDriver and passing the returned |
value as result of "START" procedure. |
void* __stdcall RegUSBDriver( |
const char* name, |
void* handler, |
const USBFUNC* usbfunc |
); |
The parameter 'name' should match the name of driver, "usbhid" for usbhid.obj. |
The parameter 'handler' is optional; if it is non-NULL, it should point to |
the standard handler for IOCTL interface as in non-USB drivers. |
The parameter 'usbfunc' is a pointer to the following structure: |
struc USBFUNC |
{ |
.strucsize dd ? ; size of the structure, including this field |
.add_device dd ? ; pointer to AddDevice function in the driver |
; required |
.device_disconnect dd ? ; pointer to DeviceDisconnected function in the driver |
; optional, may be NULL |
; other functions may be added in the future |
} |
The driver should implement the function |
void* __stdcall AddDevice( |
void* pipe0, |
void* configdescr, |
void* interfacedescr |
); |
The parameter 'controlpipe' is a handle of the control pipe for endpoint zero |
of the device. It can be used as the argument of USBControlTransferAsync. |
The parameter 'configdescr' points to USB configuration descriptor |
and all associated data, as returned by GET_DESCRIPTOR request. |
The total length of all associated data is contained in the configuration |
descriptor. |
The parameter 'interfacedescr' points to USB interface descriptor corresponding |
to the interface which is initializing. This is a pointer inside data |
associated with the configuration descriptor. |
Note that one device can implement many interfaces, so AddDevice may be |
called several times with the same 'configdescr' and different 'interfacedescr'. |
The returned value NULL means that the initialization has failed. |
Any other value means that configuration was successful; the kernel does not |
try to interpret the value. It can be, for example, pointer to the internal |
data allocated with Kmalloc, or index in some internal table. Remember that |
Kmalloc() is NOT stdcall, it destroys ebx. |
The driver can implement the function |
void __stdcall DeviceDisconnected( |
void* devicedata |
); |
If this function is implemented, the kernel calls it when the device is |
disconnected, passing the returned value of AddDevice as 'devicedata'. |
The driver can use the following functions exported by the kernel. |
void* __stdcall USBOpenPipe( |
void* pipe0, |
int endpoint, |
int maxpacketsize, |
int type, |
int interval |
); |
The parameter 'pipe0' is a handle of the pipe for endpoint zero for |
the device, as passed to AddDevice. It is used to identify the device. |
The parameter 'endpoint' is endpoint number as defined by USB. Lower |
4 bits form the number itself, bit 7 - highest bit of low byte - |
is 0/1 for OUT/IN endpoints, other bits should be zero. |
The parameter 'maxpacketsize' sets the maximum packet size for this pipe. |
The parameter 'type' selects the type of the endpoint as defined by USB: |
0 = control, 1 = isochronous (not supported yet), 2 = bulk, 3 = interrupt. |
The parameter 'interval' is ignored for control and bulk endpoints. |
For interrupt endpoints, it sets the polling interval in milliseconds. |
The function returns a handle to the pipe or NULL on failure. |
void* __stdcall USBNormalTransferAsync( |
void* pipe, |
void* buffer, |
int size, |
void* callback, |
void* calldata, |
int flags |
); |
void* __stdcall USBControlTransferAsync( |
void* pipe, |
void* config, |
void* buffer, |
int size, |
void* callback, |
void* calldata, |
int flags |
); |
The first function inserts a bulk or interrupt transfer to the transfer queue |
for given pipe. Type and direction of transfer are fixed for bulk and interrupt |
endpoints and are set in USBOpenPipe. The second function inserts a control |
transfer to the transfer queue for given pipe. Direction of a control transfer |
is concluded from 'config' packet, bit 7 of byte 0 is set for IN transfers |
and cleared for OUT transfers. These function return immediately; when data |
are transferred, the callback function will be called. |
The parameter 'pipe' is a handle returned by USBOpenPipe. |
The parameter 'config' of USBControlTransferAsync points to 8-byte |
configuration packet as defined by USB. |
The parameter 'buffer' is a pointer to buffer. For IN transfers, it will be |
filled with the data. For OUT transfers, it should contain data to be |
transferred. It can be NULL for an empty transfer or if no additional data are |
required for a control transfer. |
The parameter 'size' is size of data to transfer. It can be 0 for an empty |
transfer or if no additional data are required for a control transfer. |
The parameter 'callback' is a pointer to a function which will be called |
when the transfer will be done. |
The parameter 'calldata' will be passed as is to the callback function. |
For example, it can be NULL, it can be a pointer to device data or it can be |
a pointer to data used to pass additional parameters between caller and |
callback. The transfer-specific data can also be associated with 'buffer', |
preceding (negative offsets from 'buffer') or following (offsets more than |
or equal to 'size') the buffer itself. |
The parameter 'flags' is the bitmask. |
The bit 0 is ignored for OUT transfers, for IN transfers it controls whether |
the device can transfer less data than 'size' bytes. If the bit is 0, a small |
transfer is an error; if the bit is 1, a small transfer is OK. |
All other bits are reserved and should be zero. |
The returned value is NULL if an error occured and non-NULL if the transfer |
was successfully queued. If an error will occur later, the callback function |
will be notified. |
void __stdcall CallbackFunction( |
void* pipe, |
int status, |
void* buffer, |
int length, |
void* calldata |
); |
The parameters 'pipe', 'buffer', 'calldata' are the same as for the |
corresponding USB*TransferAsync. |
The parameter 'length' is the number of bytes transferred. For |
control transfers, this includes 8 bytes from SETUP stage, so |
0 means that SETUP stage failed and 'size'+8 means full transfer. |
The parameter 'status' is nonzero if an error occured. |
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 |
; possible only for isochronous transfers |
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer |
; possible only for isochronous transfers |
USB_STATUS_DISCONNECTED = 16 ; device disconnected |
If several transfers are queued for the same pipe, their callback functions |
are called in the same order as they were queued. |
When the device is disconnected, all callback functions are called |
with USB_STATUS_DISCONNECTED. The call to DeviceDisconnected() occurs after |
all callbacks. |
/kernel/trunk/drivers/fdo.inc |
---|
0,0 → 1,439 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2007. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
; |
; Formatted Debug Output (FDO) |
; Copyright (c) 2005-2006, mike.dld |
; Created: 2005-01-29, Changed: 2006-11-10 |
; |
; For questions and bug reports, mail to mike.dld@gmail.com |
; |
; Available format specifiers are: %s, %d, %u, %x (with partial width support) |
; |
; to be defined: |
; __DEBUG__ equ 1 |
; __DEBUG_LEVEL__ equ 5 |
macro debug_func name { |
if used name |
name@of@func equ name |
} |
macro debug_beginf { |
align 4 |
name@of@func: |
} |
debug_endf fix end if |
macro DEBUGS _sign,[_str] { |
common |
local tp |
tp equ 0 |
match _arg:_num,_str \{ |
DEBUGS_N _sign,_num,_arg |
tp equ 1 |
\} |
match =0 _arg,tp _str \{ |
DEBUGS_N _sign,,_arg |
\} |
} |
macro DEBUGS_N _sign,_num,[_str] { |
common |
pushf |
pushad |
local ..str,..label,is_str |
is_str = 0 |
forward |
if _str eqtype '' |
is_str = 1 |
end if |
common |
if is_str = 1 |
jmp ..label |
..str db _str,0 |
..label: |
add esp, 4*8+4 |
mov edx, ..str |
sub esp, 4*8+4 |
else |
mov edx, _str |
end if |
if ~_num eq |
if _num eqtype eax |
if _num in <eax,ebx,ecx,edx,edi,ebp,esp> |
mov esi, _num |
else if ~_num eq esi |
movzx esi, _num |
end if |
else if _num eqtype 0 |
mov esi, _num |
else |
local tp |
tp equ 0 |
match [_arg],_num \{ |
mov esi, dword[_arg] |
tp equ 1 |
\} |
match =0 =dword[_arg],tp _num \{ |
mov esi, dword[_arg] |
tp equ 1 |
\} |
match =0 =word[_arg],tp _num \{ |
movzx esi, word[_arg] |
tp equ 1 |
\} |
match =0 =byte[_arg],tp _num \{ |
movzx esi, byte[_arg] |
tp equ 1 |
\} |
match =0,tp \{ |
'Error: specified string width is incorrect' |
\} |
end if |
else |
mov esi, 0x7FFFFFFF |
end if |
call fdo_debug_outstr |
popad |
popf |
} |
macro DEBUGD _sign,_dec { |
local tp |
tp equ 0 |
match _arg:_num,_dec \{ |
DEBUGD_N _sign,_num,_arg |
tp equ 1 |
\} |
match =0 _arg,tp _dec \{ |
DEBUGD_N _sign,,_arg |
\} |
} |
macro DEBUGD_N _sign,_num,_dec { |
pushf |
pushad |
if (~_num eq) |
if (_dec eqtype eax | _dec eqtype 0) |
'Error: precision allowed only for in-memory variables' |
end if |
if (~_num in <1,2,4>) |
if _sign |
'Error: 1, 2 and 4 are only allowed for precision in %d' |
else |
'Error: 1, 2 and 4 are only allowed for precision in %u' |
end if |
end if |
end if |
if _dec eqtype eax |
if _dec in <ebx,ecx,edx,esi,edi,ebp,esp> |
mov eax, _dec |
else if ~_dec eq eax |
if _sign = 1 |
movsx eax, _dec |
else |
movzx eax, _dec |
end if |
end if |
else if _dec eqtype 0 |
mov eax, _dec |
else |
add esp, 4*8+4 |
if _num eq |
mov eax, dword _dec |
else if _num = 1 |
if _sign = 1 |
movsx eax, byte _dec |
else |
movzx eax, byte _dec |
end if |
else if _num = 2 |
if _sign = 1 |
movsx eax, word _dec |
else |
movzx eax, word _dec |
end if |
else |
mov eax, dword _dec |
end if |
sub esp, 4*8+4 |
end if |
mov cl, _sign |
call fdo_debug_outdec |
popad |
popf |
} |
macro DEBUGH _sign,_hex { |
local tp |
tp equ 0 |
match _arg:_num,_hex \{ |
DEBUGH_N _sign,_num,_arg |
tp equ 1 |
\} |
match =0 _arg,tp _hex \{ |
DEBUGH_N _sign,,_arg |
\} |
} |
macro DEBUGH_N _sign,_num,_hex { |
pushf |
pushad |
if (~_num eq) & (~_num in <1,2,3,4,5,6,7,8>) |
'Error: 1..8 are only allowed for precision in %x' |
end if |
if _hex eqtype eax |
if _hex in <eax,ebx,ecx,edx,esi,edi,ebp,esp> |
if ~_hex eq eax |
mov eax, _hex |
end if |
mov edx, 8 |
else if _hex in <ax,bx,cx,dx,si,di,bp,sp> |
if ~_hex eq ax |
movzx eax, _hex |
end if |
if (_num eq) |
mov edx, 4 |
end if |
else if _hex in <al,ah,bl,bh,cl,ch,dl,dh> |
if ~_hex eq al |
movzx eax, _hex |
end if |
if (_num eq) |
mov edx, 2 |
end if |
end if |
else if _hex eqtype 0 |
mov eax, _hex |
else |
add esp, 4*8+4 |
mov eax, dword _hex |
sub esp, 4*8+4 |
end if |
if ~_num eq |
mov edx, _num |
else |
if ~_hex eqtype eax |
mov edx, 8 |
end if |
end if |
call fdo_debug_outhex |
popad |
popf |
} |
;----------------------------------------------------------------------------- |
debug_func fdo_debug_outchar |
debug_beginf |
pushad |
movzx ebx, al |
mov eax, 1 |
; mov ecx,sys_msg_board |
; call ecx ; sys_msg_board |
stdcall SysMsgBoardChar |
popad |
ret |
debug_endf |
debug_func fdo_debug_outstr |
debug_beginf |
mov eax, 1 |
.l1: |
dec esi |
js .l2 |
movzx ebx, byte[edx] |
or bl, bl |
jz .l2 |
; mov ecx,sys_msg_board |
; call ecx ; sys_msg_board |
stdcall SysMsgBoardChar |
inc edx |
jmp .l1 |
.l2: |
ret |
debug_endf |
debug_func fdo_debug_outdec |
debug_beginf |
or cl, cl |
jz @f |
or eax, eax |
jns @f |
neg eax |
push eax |
mov al, '-' |
call fdo_debug_outchar |
pop eax |
@@: |
push 10 |
pop ecx |
push -'0' |
.l1: |
xor edx, edx |
div ecx |
push edx |
test eax, eax |
jnz .l1 |
.l2: |
pop eax |
add al, '0' |
jz .l3 |
call fdo_debug_outchar |
jmp .l2 |
.l3: |
ret |
debug_endf |
debug_func fdo_debug_outhex |
__fdo_hexdigits db '0123456789ABCDEF' |
debug_beginf |
mov cl, dl |
neg cl |
add cl, 8 |
shl cl, 2 |
rol eax, cl |
.l1: |
rol eax, 4 |
push eax |
and eax, 0x0000000F |
mov al, [__fdo_hexdigits+eax] |
call fdo_debug_outchar |
pop eax |
dec edx |
jnz .l1 |
ret |
debug_endf |
;----------------------------------------------------------------------------- |
macro DEBUGF _level,_format,[_arg] { |
common |
if __DEBUG__ = 1 & _level >= __DEBUG_LEVEL__ |
local ..f1,f2,a1,a2,c1,c2,c3,..lbl |
_debug_str_ equ __debug_str_ # a1 |
a1 = 0 |
c2 = 0 |
c3 = 0 |
f2 = 0 |
repeat ..lbl-..f1 |
virtual at 0 |
db _format,0,0 |
load c1 word from %-1 |
end virtual |
if c1 = '%s' |
virtual at 0 |
db _format,0,0 |
store word 0 at %-1 |
load c1 from f2-c2 |
end virtual |
if c1 <> 0 |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
c2 = c2 + 1 |
f2 = %+1 |
DEBUGF_HELPER S,a1,0,_arg |
else if c1 = '%x' |
virtual at 0 |
db _format,0,0 |
store word 0 at %-1 |
load c1 from f2-c2 |
end virtual |
if c1 <> 0 |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
c2 = c2 + 1 |
f2 = %+1 |
DEBUGF_HELPER H,a1,0,_arg |
else if c1 = '%d' | c1 = '%u' |
local c4 |
if c1 = '%d' |
c4 = 1 |
else |
c4 = 0 |
end if |
virtual at 0 |
db _format,0,0 |
store word 0 at %-1 |
load c1 from f2-c2 |
end virtual |
if c1 <> 0 |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
c2 = c2 + 1 |
f2 = %+1 |
DEBUGF_HELPER D,a1,c4,_arg |
else if c1 = '\n' |
c3 = c3 + 1 |
end if |
end repeat |
virtual at 0 |
db _format,0,0 |
load c1 from f2-c2 |
end virtual |
if (c1<>0)&(f2<>..lbl-..f1-1) |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
virtual at 0 |
..f1 db _format,0 |
..lbl: |
__debug_strings equ __debug_strings,_debug_str_,<_format>,..lbl-..f1-1-c2-c3 |
end virtual |
end if |
} |
macro __include_debug_strings dummy,[_id,_fmt,_len] { |
common |
local c1,a1,a2 |
forward |
if defined _len & ~_len eq |
_id: |
a1 = 0 |
a2 = 0 |
repeat _len |
virtual at 0 |
db _fmt,0,0 |
load c1 word from %+a2-1 |
end virtual |
if (c1='%s')|(c1='%x')|(c1='%d')|(c1='%u') |
db 0 |
a2 = a2 + 1 |
else if (c1='\n') |
dw $0A0D |
a1 = a1 + 1 |
a2 = a2 + 1 |
else |
db c1 and 0x0FF |
end if |
end repeat |
db 0 |
end if |
} |
macro DEBUGF_HELPER _letter,_num,_sign,[_arg] { |
common |
local num |
num = 0 |
forward |
if num = _num |
DEBUG#_letter _sign,_arg |
end if |
num = num+1 |
common |
_num = _num+1 |
} |
macro include_debug_strings { |
if __DEBUG__ = 1 |
match dbg_str,__debug_strings \{ |
__include_debug_strings dbg_str |
\} |
end if |
} |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/drivers/imports.inc |
---|
97,6 → 97,14 |
GetDisplay,\ |
SetScreen,\ |
\ |
RegUSBDriver,\ |
USBOpenPipe,\ |
USBNormalTransferAsync,\ |
USBControlTransferAsync,\ |
\ |
DiskAdd,\ |
DiskMediaChanged,\ |
DiskDel |
DiskDel,\ |
\ |
TimerHS,\ |
CancelTimerHS |
/kernel/trunk/drivers/usbhid.asm |
---|
0,0 → 1,696 |
; standard driver stuff |
format MS COFF |
DEBUG = 1 |
; this is for DEBUGF macro from 'fdo.inc' |
__DEBUG__ = 1 |
__DEBUG_LEVEL__ = 1 |
include 'proc32.inc' |
include 'imports.inc' |
include 'fdo.inc' |
public START |
public version |
; USB constants |
DEVICE_DESCR_TYPE = 1 |
CONFIG_DESCR_TYPE = 2 |
STRING_DESCR_TYPE = 3 |
INTERFACE_DESCR_TYPE = 4 |
ENDPOINT_DESCR_TYPE = 5 |
DEVICE_QUALIFIER_DESCR_TYPE = 6 |
CONTROL_PIPE = 0 |
ISOCHRONOUS_PIPE = 1 |
BULK_PIPE = 2 |
INTERRUPT_PIPE = 3 |
; USB structures |
virtual at 0 |
config_descr: |
.bLength db ? |
.bDescriptorType db ? |
.wTotalLength dw ? |
.bNumInterfaces db ? |
.bConfigurationValue db ? |
.iConfiguration db ? |
.bmAttributes db ? |
.bMaxPower db ? |
.sizeof: |
end virtual |
virtual at 0 |
interface_descr: |
.bLength db ? |
.bDescriptorType db ? |
.bInterfaceNumber db ? |
.bAlternateSetting db ? |
.bNumEndpoints db ? |
.bInterfaceClass db ? |
.bInterfaceSubClass db ? |
.bInterfaceProtocol db ? |
.iInterface db ? |
.sizeof: |
end virtual |
virtual at 0 |
endpoint_descr: |
.bLength db ? |
.bDescriptorType db ? |
.bEndpointAddress db ? |
.bmAttributes db ? |
.wMaxPacketSize dw ? |
.bInterval db ? |
.sizeof: |
end virtual |
; Driver data for all devices |
virtual at 0 |
device_data: |
.type dd ? ; 1 = keyboard, 2 = mouse |
.intpipe dd ? ; interrupt pipe handle |
.packetsize dd ? |
.packet rb 8 ; packet with data from device |
.control rb 8 ; control packet to device |
.sizeof: |
end virtual |
; Driver data for mouse |
virtual at device_data.sizeof |
mouse_data: |
; no additional data |
.sizeof: |
end virtual |
; Driver data for keyboard |
virtual at device_data.sizeof |
keyboard_data: |
.handle dd ? ; keyboard handle from RegKeyboard |
.configpipe dd ? ; config pipe handle |
.prevpacket rb 8 ; previous packet with data from device |
.timer dd ? ; auto-repeat timer handle |
.repeatkey db ? ; auto-repeat key code |
.ledstate db ? ; state of LEDs |
align 4 |
.sizeof: |
end virtual |
section '.flat' code readable align 16 |
; The start procedure. |
START: |
; 1. Test whether the procedure is called with the argument DRV_ENTRY. |
; If not, return 0. |
xor eax, eax ; initialize return value |
cmp dword [esp+4], 1 ; compare the argument |
jnz .nothing |
; 2. Register self as a USB driver. |
; The name is my_driver = 'usbhid'; IOCTL interface is not supported; |
; usb_functions is an offset of a structure with callback functions. |
stdcall RegUSBDriver, my_driver, eax, usb_functions |
; 3. Return the returned value of RegUSBDriver. |
.nothing: |
ret 4 |
; This procedure is called when new HID device is detected. |
; It initializes the device. |
AddDevice: |
; Arguments are addressed through esp. In this point of the function, |
; [esp+4] = a handle of the config pipe, [esp+8] points to config_descr |
; structure, [esp+12] points to interface_descr structure. |
; 1. Check device type. Currently only mice and keyboards with |
; boot protocol are supported. |
; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and |
; bInterfaceProtocol are subsequent in interface_descr, just one |
; memory reference is used for both. |
mov edx, [esp+12] |
push ebx ; save used register to be stdcall |
mov cx, word [edx+interface_descr.bInterfaceSubClass] |
; 1b. For boot protocol, subclass must be 1 and protocol must be either 1 for |
; a keyboard or 2 for a mouse. Check. |
cmp cx, 0x0101 |
jz .keyboard |
cmp cx, 0x0201 |
jz .mouse |
; 1c. If the device is neither a keyboard nor a mouse, print a message and |
; go to 6c. |
DEBUGF 1,'K : unknown HID device\n' |
jmp .nothing |
; 1d. If the device is a keyboard or a mouse, print a message and continue |
; configuring. |
.keyboard: |
DEBUGF 1,'K : USB keyboard detected\n' |
push keyboard_data.sizeof |
jmp .common |
.mouse: |
DEBUGF 1,'K : USB mouse detected\n' |
push mouse_data.sizeof |
.common: |
; 2. Allocate memory for device data. |
pop eax ; get size of device data |
; 2a. Call the kernel, saving and restoring register edx. |
push edx |
call Kmalloc |
pop edx |
; 2b. Check result. If failed, say a message and go to 6c. |
test eax, eax |
jnz @f |
DEBUGF 1,'K : no memory\n' |
jmp .nothing |
@@: |
xchg eax, ebx |
; HID devices use one IN interrupt endpoint for polling the device |
; and an optional OUT interrupt endpoint. We do not use the later, |
; but must locate the first. Look for the IN interrupt endpoint. |
; 3. Get the upper bound of all descriptors' data. |
mov eax, [esp+8+4] ; configuration descriptor |
movzx ecx, [eax+config_descr.wTotalLength] |
add eax, ecx |
; 4. Loop over all descriptors until |
; either end-of-data reached - this is fail |
; or interface descriptor found - this is fail, all further data |
; correspond to that interface |
; or endpoint descriptor found. |
; 4a. Loop start: eax points to the interface descriptor. |
.lookep: |
; 4b. Get next descriptor. |
movzx ecx, byte [edx] ; the first byte of all descriptors is length |
add edx, ecx |
; 4c. Check that at least two bytes are readable. The opposite is an error. |
inc edx |
cmp edx, eax |
jae .errorep |
dec edx |
; 4d. Check that this descriptor is not interface descriptor. The opposite is |
; an error. |
cmp byte [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE |
jz .errorep |
; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue |
; the loop. |
cmp byte [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE |
jnz .lookep |
; 5. Check that the descriptor contains all required data and all data are |
; readable. If so, proceed to 7. |
cmp byte [edx+endpoint_descr.bLength], endpoint_descr.sizeof |
jb .errorep |
sub eax, endpoint_descr.sizeof |
cmp edx, eax |
jbe @f |
; 6. An error occured during processing endpoint descriptor. |
.errorep: |
; 6a. Print a message. |
DEBUGF 1,'K : error: invalid endpoint descriptor\n' |
; 6b. Free memory allocated for device data. |
.free: |
xchg eax, ebx |
call Kfree |
.nothing: |
; 6c. Return an error. |
xor eax, eax |
pop ebx |
ret 12 |
@@: |
; 7. Check that the endpoint is IN interrupt endpoint. If not, go to 6. |
test [edx+endpoint_descr.bEndpointAddress], 80h |
jz .errorep |
mov cl, [edx+endpoint_descr.bmAttributes] |
and cl, 3 |
cmp cl, INTERRUPT_PIPE |
jnz .errorep |
; 8. Open pipe for the endpoint. |
; 8a. Load parameters from the descriptor. |
movzx ecx, [edx+endpoint_descr.bEndpointAddress] |
movzx eax, [edx+endpoint_descr.bInterval] |
movzx edx, [edx+endpoint_descr.wMaxPacketSize] |
; 8b. Call the kernel, saving and restoring edx. |
push edx |
stdcall USBOpenPipe, [esp+4+24], ecx, edx, INTERRUPT_PIPE, eax |
pop edx |
; 8c. Check result. If failed, go to 6b. |
test eax, eax |
jz .free |
; We use 12 bytes for device type, interrupt pipe and interrupt packet size, |
; 8 bytes for a packet and 8 bytes for previous packet, used by a keyboard. |
; 9. Initialize device data. |
mov [ebx+device_data.intpipe], eax |
push 8 |
pop ecx |
cmp edx, ecx |
jb @f |
mov edx, ecx |
@@: |
xor eax, eax |
mov [ebx+device_data.packetsize], edx |
mov dword [ebx+device_data.packet], eax |
mov dword [ebx+device_data.packet+4], eax |
mov edx, [esp+12+4] ; interface descriptor |
movzx ecx, [edx+interface_descr.bInterfaceProtocol] |
mov [ebx+device_data.type], ecx |
cmp ecx, 1 |
jnz @f |
mov [ebx+keyboard_data.handle], eax |
mov [ebx+keyboard_data.timer], eax |
mov [ebx+keyboard_data.repeatkey], al |
mov dword [ebx+keyboard_data.prevpacket], eax |
mov dword [ebx+keyboard_data.prevpacket+4], eax |
mov eax, [esp+4+4] |
mov [ebx+keyboard_data.configpipe], eax |
@@: |
; 10. Send the control packet SET_PROTOCOL(Boot Protocol) to the interface. |
lea eax, [ebx+device_data.control] |
mov dword [eax], 21h + (0Bh shl 8) + (0 shl 16) ; class request to interface + SET_PROTOCOL + Boot protocol |
and dword [eax+4], 0 |
mov dl, [edx+interface_descr.bInterfaceNumber] |
mov [eax+4], dl |
; Callback function is mouse_configured for mice and keyboard_configured1 for keyboards. |
mov edx, keyboard_configured1 |
cmp ecx, 1 |
jz @f |
mov edx, mouse_configured |
@@: |
stdcall USBControlTransferAsync, [esp+4+28], eax, 0, 0, edx, ebx, 0 |
; 11. Return with pointer to device data as returned value. |
xchg eax, ebx |
pop ebx |
ret 12 |
; This function is called when SET_PROTOCOL command for keyboard is done, |
; either successful or unsuccessful. |
keyboard_configured1: |
xor edx, edx |
; 1. Check the status of the transfer. |
; If the transfer was failed, go to the common error handler. |
cmp dword [esp+8], edx ; status is zero? |
jnz keyboard_data_ready.error |
; 2. Send the control packet SET_IDLE(infinity). HID auto-repeat is not useful. |
mov eax, [esp+20] |
push edx ; flags for USBControlTransferAsync |
push eax ; userdata for USBControlTransferAsync |
add eax, device_data.control |
mov dword [eax], 21h + (0Ah shl 8) + (0 shl 24) ; class request to interface + SET_IDLE + no autorepeat |
stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \ |
eax, edx, edx, keyboard_configured2; , <userdata>, <flags> |
; 3. Return. |
ret 20 |
; This function is called when SET_IDLE command for keyboard is done, |
; either successful or unsuccessful. |
keyboard_configured2: |
; Check the status of the transfer and go to the corresponding label |
; in the main handler. |
cmp dword [esp+8], 0 |
jnz keyboard_data_ready.error |
mov edx, [esp+20] |
push edx |
stdcall RegKeyboard, usbkbd_functions, edx |
pop edx |
mov [edx+keyboard_data.handle], eax |
jmp keyboard_data_ready.next |
; This function is called when another interrupt packet arrives, |
; processed either successfully or unsuccessfully. |
; It should parse the packet and initiate another transfer with |
; the same callback function. |
keyboard_data_ready: |
; 1. Check the status of the transfer. |
mov eax, [esp+8] |
test eax, eax |
jnz .error |
; Parse the packet, comparing with the previous packet. |
; For boot protocol, USB keyboard packet consists of the first byte |
; with status keys that are currently pressed. The second byte should |
; be ignored, and other 5 bytes denote keys that are currently pressed. |
push esi ebx ; save used registers to be stdcall |
; 2. Process control keys. |
; 2a. Initialize before loop for control keys. edx = mask for control bits |
; that were changed. |
mov ebx, [esp+20+8] |
movzx edx, byte [ebx+device_data.packet] ; get state of control keys |
xor dl, byte [ebx+keyboard_data.prevpacket] ; compare with previous state |
; 2b. If state of control keys has not changed, advance to 3. |
jz .nocontrol |
; 2c. Otherwise, loop over control keys; esi = bit number. |
xor esi, esi |
.controlloop: |
; 2d. Skip bits that have not changed. |
bt edx, esi |
jnc .controlnext |
push edx ; save register which is possibly modified by API |
; The state of the current control key has changed. |
; 2e. For extended control keys, send the prefix 0xE0. |
mov al, [control_keys+esi] |
test al, al |
jns @f |
push eax |
mov ecx, 0xE0 |
call SetKeyboardData |
pop eax |
and al, 0x7F |
@@: |
; 2f. If the current state of the control key is "pressed", send normal |
; scancode. Otherwise, the key is released, so set the high bit in scancode. |
movzx ecx, al |
bt dword [ebx+device_data.packet], esi |
jc @f |
or cl, 0x80 |
@@: |
call SetKeyboardData |
pop edx ; restore register which was possibly modified by API |
.controlnext: |
; 2g. We have 8 control keys. |
inc esi |
cmp esi, 8 |
jb .controlloop |
.nocontrol: |
; 3. Initialize before loop for normal keys. esi = index. |
push 2 |
pop esi |
.normalloop: |
; 4. Process one key which was pressed in the previous packet. |
; 4a. Get the next pressed key from the previous packet. |
movzx eax, byte [ebx+esi+keyboard_data.prevpacket] |
; 4b. Ignore special codes. |
cmp al, 3 |
jbe .normalnext1 |
; 4c. Ignore keys that are still pressed in the current packet. |
lea ecx, [ebx+device_data.packet] |
call haskey |
jz .normalnext1 |
; 4d. Say warning about keys with strange codes. |
cmp eax, normal_keys_number |
jae .badkey1 |
movzx ecx, [normal_keys+eax] |
jecxz .badkey1 |
; 4e. For extended keys, send the prefix 0xE0. |
push ecx ; save keycode |
test cl, cl |
jns @f |
push ecx |
mov ecx, 0xE0 |
call SetKeyboardData |
pop ecx |
@@: |
; 4f. Send the release event. |
or cl, 0x80 |
call SetKeyboardData |
; 4g. If this key is autorepeating, stop the timer. |
pop ecx ; restore keycode |
cmp cl, [ebx+keyboard_data.repeatkey] |
jnz .normalnext1 |
mov eax, [ebx+keyboard_data.timer] |
test eax, eax |
jz .normalnext1 |
stdcall CancelTimerHS, eax |
and [ebx+keyboard_data.timer], 0 |
jmp .normalnext1 |
.badkey1: |
DEBUGF 1,'K : unknown keycode: %x\n',al |
.normalnext1: |
; 5. Process one key which is pressed in the current packet. |
; 5a. Get the next pressed key from the current packet. |
movzx eax, byte [ebx+esi+device_data.packet] |
; 5b. Ignore special codes. |
cmp al, 3 |
jbe .normalnext2 |
; 5c. Ignore keys that were already pressed in the previous packet. |
lea ecx, [ebx+keyboard_data.prevpacket] |
call haskey |
jz .normalnext2 |
; 5d. Say warning about keys with strange codes. |
cmp eax, normal_keys_number |
jae .badkey2 |
movzx ecx, [normal_keys+eax] |
jecxz .badkey2 |
; 5e. For extended keys, send the prefix 0xE0. |
push ecx ; save keycode |
test cl, cl |
jns @f |
push ecx |
mov ecx, 0xE0 |
call SetKeyboardData |
pop ecx |
@@: |
; 5f. Send the press event. |
and cl, not 0x80 |
call SetKeyboardData |
; 5g. Stop the current auto-repeat timer, if present. |
mov eax, [ebx+keyboard_data.timer] |
test eax, eax |
jz @f |
stdcall CancelTimerHS, eax |
@@: |
; 5h. Start the auto-repeat timer. |
pop ecx ; restore keycode |
mov [ebx+keyboard_data.repeatkey], cl |
stdcall TimerHS, 25, 5, autorepeat_timer, ebx |
mov [ebx+keyboard_data.timer], eax |
jmp .normalnext2 |
.badkey2: |
DEBUGF 1,'K : unknown keycode: %x\n',al |
.normalnext2: |
; 6. Advance to next key. |
inc esi |
cmp esi, 8 |
jb .normalloop |
; 7. Save the packet data for future reference. |
mov eax, dword [ebx+device_data.packet] |
mov dword [ebx+keyboard_data.prevpacket], eax |
mov eax, dword [ebx+device_data.packet+4] |
mov dword [ebx+keyboard_data.prevpacket+4], eax |
pop ebx esi ; restore registers to be stdcall |
.next: |
; 8. Initiate transfer on the interrupt pipe. |
mov eax, [esp+20] |
push 1 ; flags for USBNormalTransferAsync |
push eax ; userdata for USBNormalTransferAsync |
add eax, device_data.packet |
stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \ |
eax, dword [eax+device_data.packetsize-device_data.packet], \ |
keyboard_data_ready;, <userdata>, <flags> |
; 9. Return. |
.nothing: |
ret 20 |
.error: |
; An error has occured. |
; 10. If an error is caused by the disconnect, do nothing, it is handled |
; in DeviceDisconnected. Otherwise, say a message. |
cmp eax, 16 |
jz @f |
push esi |
mov esi, errormsgkbd |
call SysMsgBoardStr |
pop esi |
@@: |
ret 20 |
; Auxiliary procedure for keyboard_data_ready. |
haskey: |
push 2 |
pop edx |
@@: |
cmp byte [ecx+edx], al |
jz @f |
inc edx |
cmp edx, 7 |
jbe @b |
@@: |
ret |
; Timer function for auto-repeat. |
autorepeat_timer: |
mov eax, [esp+4] |
movzx ecx, [eax+keyboard_data.repeatkey] |
test cl, cl |
jns @f |
push ecx |
mov ecx, 0xE0 |
call SetKeyboardData |
pop ecx |
and cl, not 0x80 |
@@: |
call SetKeyboardData |
ret 4 |
; This function is called to update LED state on the keyboard. |
SetKeyboardLights: |
mov eax, [esp+4] |
add eax, device_data.control |
mov dword [eax], 21h + (9 shl 8) + (2 shl 24) |
; class request to interface + SET_REPORT + Output zero report |
mov byte [eax+6], 1 |
mov edx, [esp+8] |
shr dl, 1 |
jnc @f |
or dl, 4 |
@@: |
lea ecx, [eax+keyboard_data.ledstate-device_data.control] |
mov [ecx], dl |
stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \ |
eax, ecx, 1, keyboard_data_ready.nothing, 0, 0 |
ret 8 |
; This function is called when it is safe to free keyboard data. |
CloseKeyboard: |
mov eax, [esp+4] |
push ebx |
call Kfree |
pop ebx |
ret 4 |
; This function is called when SET_PROTOCOL command for mouse is done, |
; either successful or unsuccessful. |
mouse_configured: |
; Check the status of the transfer and go to the corresponding label |
; in the main handler. |
cmp dword [esp+8], 0 |
jnz mouse_data_ready.error |
mov eax, [esp+20] |
add eax, device_data.packet |
jmp mouse_data_ready.next |
; This function is called when another interrupt packet arrives, |
; processed either successfully or unsuccessfully. |
; It should parse the packet and initiate another transfer with |
; the same callback function. |
mouse_data_ready: |
; 1. Check the status of the transfer. |
mov eax, [esp+8] |
test eax, eax |
jnz .error |
mov edx, [esp+16] |
; 2. Parse the packet. |
; For boot protocol, USB mouse packet consists of at least 3 bytes. |
; The first byte is state of mouse buttons, the next two bytes are |
; x and y movements. |
; Normal mice do not distinguish between boot protocol and report protocol; |
; in this case, scroll data are also present. Advanced mice, however, |
; support two different protocols, boot protocol is used for compatibility |
; and does not contain extended buttons or scroll data. |
mov eax, [esp+12] ; buffer |
push eax |
xor ecx, ecx |
cmp edx, 4 |
jbe @f |
movsx ecx, byte [eax+4] |
@@: |
push ecx |
xor ecx, ecx |
cmp edx, 3 |
jbe @f |
movsx ecx, byte [eax+3] |
neg ecx |
@@: |
push ecx |
xor ecx, ecx |
cmp edx, 2 |
jbe @f |
movsx ecx, byte [eax+2] |
neg ecx |
@@: |
push ecx |
movsx ecx, byte [eax+1] |
push ecx |
movzx ecx, byte [eax] |
push ecx |
call SetMouseData |
pop eax |
.next: |
; 3. Initiate transfer on the interrupt pipe. |
stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \ |
eax, dword [eax+device_data.packetsize-device_data.packet], mouse_data_ready, eax, 1 |
; 4. Return. |
ret 20 |
.error: |
; An error has occured. |
; 5. If an error is caused by the disconnect, do nothing, it is handled |
; in DeviceDisconnected. Otherwise, say a message. |
cmp eax, 16 |
jz @f |
push esi |
mov esi, errormsgmouse |
call SysMsgBoardStr |
pop esi |
@@: |
ret 20 |
; This function is called when the device is disconnected. |
DeviceDisconnected: |
push ebx ; save used register to be stdcall |
; 1. Say a message. Use different messages for keyboards and mice. |
mov ebx, [esp+4+4] |
push esi |
mov esi, disconnectmsgk |
cmp byte [ebx+device_data.type], 1 |
jz @f |
mov esi, disconnectmsgm |
@@: |
stdcall SysMsgBoardStr |
pop esi |
; 2. If device is keyboard, then we must unregister it as a keyboard and |
; possibly stop the auto-repeat timer. |
cmp byte [ebx+device_data.type], 1 |
jnz .nokbd |
mov eax, [ebx+keyboard_data.timer] |
test eax, eax |
jz @f |
stdcall CancelTimerHS, eax |
@@: |
mov ecx, [ebx+keyboard_data.handle] |
jecxz .nokbd |
stdcall DelKeyboard, ecx |
; If keyboard is registered, then we should free data in CloseKeyboard, not here. |
jmp .nothing |
.nokbd: |
; 3. Free the device data. |
xchg eax, ebx |
call Kfree |
; 4. Return. |
.nothing: |
pop ebx ; restore used register to be stdcall |
ret 4 ; purge one dword argument to be stdcall |
; strings |
my_driver db 'usbhid',0 |
errormsgmouse db 'K : USB transfer error, disabling mouse',10,0 |
errormsgkbd db 'K : USB transfer error, disabling keyboard',10,0 |
disconnectmsgm db 'K : USB mouse disconnected',10,0 |
disconnectmsgk db 'K : USB keyboard disconnected',10,0 |
; data for keyboard: correspondence between HID usage keys and PS/2 scancodes. |
EX = 80h |
label control_keys byte |
db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX |
label normal_keys byte |
db 00h, 00h, 00h, 00h, 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h ; 0x |
db 32h, 31h, 18h, 19h, 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h ; 1x |
db 04h, 05h, 06h, 07h, 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah ; 2x |
db 1Bh, 2Bh, 2Bh, 27h, 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h ; 3x |
db 41h, 42h, 43h, 44h, 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX ; 4x |
db 4Bh+EX,50h+EX,48h+EX,45h,35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h,51h,4Bh,4Ch,4Dh,47h ; 5x |
db 48h, 49h, 52h, 53h, 56h,5Dh+EX,5Eh+EX,59h,64h,65h,66h, 67h, 68h, 69h, 6Ah, 6Bh ; 6x |
db 6Ch, 6Dh, 6Eh, 76h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h ; 7x |
db 00h, 00h, 00h, 00h, 00h, 7Eh, 00h, 73h, 70h, 7Dh, 79h, 7Bh, 5Ch, 00h, 00h, 00h ; 8x |
db 0F2h,0F1h,78h, 77h, 76h |
normal_keys_number = $ - normal_keys |
; Exported variable: kernel API version. |
align 4 |
version dd 50005h |
; Structure with callback functions. |
usb_functions: |
dd 12 |
dd AddDevice |
dd DeviceDisconnected |
; Structure with callback functions for keyboards. |
usbkbd_functions: |
dd 12 |
dd CloseKeyboard |
dd SetKeyboardLights |
; for DEBUGF macro |
include_debug_strings |
; for uninitialized data |
section '.data' data readable writable align 16 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/drivers/usbstor.asm |
---|
0,0 → 1,1609 |
; standard driver stuff |
format MS COFF |
DEBUG = 1 |
DUMP_PACKETS = 0 |
; this is for DEBUGF macro from 'fdo.inc' |
__DEBUG__ = 1 |
__DEBUG_LEVEL__ = 1 |
include 'proc32.inc' |
include 'imports.inc' |
include 'fdo.inc' |
public START |
public version |
; USB constants |
DEVICE_DESCR_TYPE = 1 |
CONFIG_DESCR_TYPE = 2 |
STRING_DESCR_TYPE = 3 |
INTERFACE_DESCR_TYPE = 4 |
ENDPOINT_DESCR_TYPE = 5 |
DEVICE_QUALIFIER_DESCR_TYPE = 6 |
CONTROL_PIPE = 0 |
ISOCHRONOUS_PIPE = 1 |
BULK_PIPE = 2 |
INTERRUPT_PIPE = 3 |
; USB structures |
virtual at 0 |
config_descr: |
.bLength db ? |
.bDescriptorType db ? |
.wTotalLength dw ? |
.bNumInterfaces db ? |
.bConfigurationValue db ? |
.iConfiguration db ? |
.bmAttributes db ? |
.bMaxPower db ? |
.sizeof: |
end virtual |
virtual at 0 |
interface_descr: |
.bLength db ? |
.bDescriptorType db ? |
.bInterfaceNumber db ? |
.bAlternateSetting db ? |
.bNumEndpoints db ? |
.bInterfaceClass db ? |
.bInterfaceSubClass db ? |
.bInterfaceProtocol db ? |
.iInterface db ? |
.sizeof: |
end virtual |
virtual at 0 |
endpoint_descr: |
.bLength db ? |
.bDescriptorType db ? |
.bEndpointAddress db ? |
.bmAttributes db ? |
.wMaxPacketSize dw ? |
.bInterval db ? |
.sizeof: |
end virtual |
; Mass storage protocol constants, USB layer |
REQUEST_GETMAXLUN = 0xFE ; get max lun |
REQUEST_BORESET = 0xFF ; bulk-only reset |
; Mass storage protocol structures, USB layer |
; Sent from host to device in the first stage of an operation. |
struc command_block_wrapper |
{ |
.Signature dd ? ; the constant 'USBC' |
.Tag dd ? ; identifies response with request |
.Length dd ? ; length of data-transport phase |
.Flags db ? ; one of CBW_FLAG_* |
CBW_FLAG_OUT = 0 |
CBW_FLAG_IN = 80h |
.LUN db ? ; addressed unit |
.CommandLength db ? ; the length of the following field |
.Command rb 16 |
.sizeof: |
} |
virtual at 0 |
command_block_wrapper command_block_wrapper |
end virtual |
; Sent from device to host in the last stage of an operation. |
struc command_status_wrapper |
{ |
.Signature dd ? ; the constant 'USBS' |
.Tag dd ? ; identifies response with request |
.LengthRest dd ? ; .Length - (size of data which were transferred) |
.Status db ? ; one of CSW_STATUS_* |
CSW_STATUS_OK = 0 |
CSW_STATUS_FAIL = 1 |
CSW_STATUS_FATAL = 2 |
.sizeof: |
} |
virtual at 0 |
command_status_wrapper command_status_wrapper |
end virtual |
; Constants of SCSI layer |
SCSI_REQUEST_SENSE = 3 |
SCSI_INQUIRY = 12h |
SCSI_READ_CAPACITY = 25h |
SCSI_READ10 = 28h |
SCSI_WRITE10 = 2Ah |
; Result of SCSI REQUEST SENSE command. |
SENSE_UNKNOWN = 0 |
SENSE_RECOVERED_ERROR = 1 |
SENSE_NOT_READY = 2 |
SENSE_MEDIUM_ERROR = 3 |
SENSE_HARDWARE_ERROR = 4 |
SENSE_ILLEGAL_REQUEST = 5 |
SENSE_UNIT_ATTENTION = 6 |
SENSE_DATA_PROTECT = 7 |
SENSE_BLANK_CHECK = 8 |
; 9 is vendor-specific |
SENSE_COPY_ABORTED = 10 |
SENSE_ABORTED_COMMAND = 11 |
SENSE_EQUAL = 12 |
SENSE_VOLUME_OVERFLOW = 13 |
SENSE_MISCOMPARE = 14 |
; 15 is reserved |
; Structures of SCSI layer |
; Result of SCSI INQUIRY request. |
struc inquiry_data |
{ |
.PeripheralDevice db ? ; lower 5 bits are PeripheralDeviceType |
; upper 3 bits are PeripheralQualifier |
.RemovableMedium db ? ; upper bit is RemovableMedium |
; other bits are for compatibility |
.Version db ? ; lower 3 bits are ANSI-Approved version |
; next 3 bits are ECMA version |
; upper 2 bits are ISO version |
.ResponseDataFormat db ? ; lower 4 bits are ResponseDataFormat |
; bit 6 is TrmIOP |
; bit 7 is AENC |
.AdditionalLength db ? |
dw ? ; reserved |
.Flags db ? |
.VendorID rb 8 ; vendor ID, big-endian |
.ProductID rb 16 ; product ID, big-endian |
.ProductRevBE dd ? ; product revision, big-endian |
.sizeof: |
} |
virtual at 0 |
inquiry_data inquiry_data |
end virtual |
struc sense_data |
{ |
.ErrorCode db ? ; lower 7 bits are error code: |
; 70h = current error, |
; 71h = deferred error |
; upper bit is InformationValid |
.SegmentNumber db ? ; number of segment descriptor |
; for commands COPY [+VERIFY], COMPARE |
.SenseKey db ? ; bits 0-3 are one of SENSE_* |
; bit 4 is reserved |
; bit 5 is IncorrectLengthIndicator |
; bits 6 and 7 are used by |
; sequential-access devices |
.Information dd ? ; command-specific |
.AdditionalLength db ? ; length of data starting here |
.CommandInformation dd ? ; command-specific |
.AdditionalSenseCode db ? ; \ more detailed error code |
.AdditionalSenseQual db ? ; / standard has a large table of them |
.FRUCode db ? ; which part of device has failed |
; (device-specific, not regulated) |
.SenseKeySpecific rb 3 ; depends on SenseKey |
.sizeof: |
} |
virtual at 0 |
sense_data sense_data |
end virtual |
; Device data |
; USB Mass storage device has one or more logical units, identified by LUN, |
; logical unit number. The highest value of LUN, that is, number of units |
; minus 1, can be obtained via control request Get Max LUN. |
virtual at 0 |
usb_device_data: |
.ConfigPipe dd ? ; configuration pipe |
.OutPipe dd ? ; pipe for OUT bulk endpoint |
.InPipe dd ? ; pipe for IN bulk endpoint |
.MaxLUN dd ? ; maximum Logical Unit Number |
.LogicalDevices dd ? ; pointer to array of usb_unit_data |
; 1 for a connected USB device, 1 for each disk device |
; the structure can be freed when .NumReferences decreases to zero |
.NumReferences dd ? ; number of references |
.ConfigRequest rb 8 ; buffer for configuration requests |
.LengthRest dd ? ; Length - (size of data which were transferred) |
; All requests to a given device are serialized, |
; only one request to a given device can be processed at a time. |
; The current request and all pending requests are organized in the following |
; queue, the head being the current request. |
; NB: the queue must be device-wide due to the protocol: |
; data stage is not tagged (unlike command_*_wrapper), so the only way to know |
; what request the data are associated with is to guarantee that only one |
; request is processing at the time. |
.RequestsQueue rd 2 |
.QueueLock rd 3 ; protects .RequestsQueue |
.InquiryData inquiry_data ; information about device |
; data for the current request |
.Command command_block_wrapper |
.DeviceDisconnected db ? |
.Status command_status_wrapper |
.Sense sense_data |
.sizeof: |
end virtual |
; Information about one logical device. |
virtual at 0 |
usb_unit_data: |
.Parent dd ? ; pointer to parent usb_device_data |
.LUN db ? ; index in usb_device_data.LogicalDevices array |
.DiskIndex db ? ; for name "usbhd<index>" |
.MediaPresent db ? |
db ? ; alignment |
.DiskDevice dd ? ; handle of disk device or NULL |
.SectorSize dd ? ; sector size |
; For some devices, the first request to the medium fails with 'unit not ready'. |
; When the code sees this status, it retries the command several times. |
; Two following variables track the retry count and total time for those; |
; total time is currently used only for debug output. |
.UnitReadyAttempts dd ? |
.TimerTicks dd ? |
.sizeof: |
end virtual |
; This is the structure for items in the queue usb_device_data.RequestsQueue. |
virtual at 0 |
request_queue_item: |
.Next dd ? ; next item in the queue |
.Prev dd ? ; prev item in the queue |
.ReqBuilder dd ? ; procedure to fill command_block_wrapper |
.Buffer dd ? ; input or output data |
; (length is command_block_wrapper.Length) |
.Callback dd ? ; procedure to call in the end of transfer |
.UserData dd ? ; passed as-is to .Callback |
; There are 3 possible stages of any request, one of them optional: |
; command stage (host sends command_block_wrapper to device), |
; optional data stage, |
; status stage (device sends command_status_wrapper to host). |
; Also, if a request fails, the code queues additional request |
; SCSI_REQUEST_SENSE; sense_data from SCSI_REQUEST_SENSE |
; contains some information about the error. |
.Stage db ? |
.sizeof: |
end virtual |
section '.flat' code readable align 16 |
; The start procedure. |
proc START |
virtual at esp |
dd ? ; return address |
.reason dd ? ; DRV_ENTRY or DRV_EXIT |
end virtual |
; 1. Test whether the procedure is called with the argument DRV_ENTRY. |
; If not, return 0. |
xor eax, eax ; initialize return value |
cmp [.reason], 1 ; compare the argument |
jnz .nothing |
; 2. Initialize: we have one global mutex. |
mov ecx, free_numbers_lock |
call MutexInit |
; 3. Register self as a USB driver. |
; The name is my_driver = 'usbstor'; IOCTL interface is not supported; |
; usb_functions is an offset of a structure with callback functions. |
stdcall RegUSBDriver, my_driver, 0, usb_functions |
; 4. Return the returned value of RegUSBDriver. |
.nothing: |
ret 4 |
endp |
; Helper procedures to work with requests queue. |
; Add a request to the queue. Stdcall with 5 arguments. |
proc queue_request |
push ebx esi |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.device dd ? ; pointer to usb_device_data |
.ReqBuilder dd ? ; request_queue_item.ReqBuilder |
.Buffer dd ? ; request_queue_item.Buffer |
.Callback dd ? ; request_queue_item.Callback |
.UserData dd ? ; request_queue_item.UserData |
end virtual |
; 1. Allocate the memory for the request description. |
push request_queue_item.sizeof |
pop eax |
call Kmalloc |
test eax, eax |
jnz @f |
mov esi, nomemory |
call SysMsgBoardStr |
pop esi ebx |
ret 20 |
@@: |
; 2. Fill user-provided parts of the request description. |
push edi |
xchg eax, ebx |
lea esi, [.ReqBuilder+4] |
lea edi, [ebx+request_queue_item.ReqBuilder] |
movsd ; ReqBuilder |
movsd ; Buffer |
movsd ; Callback |
movsd ; UserData |
pop edi |
; 3. Set stage to zero: not started. |
mov [ebx+request_queue_item.Stage], 0 |
; 4. Lock the queue. |
mov esi, [.device] |
lea ecx, [esi+usb_device_data.QueueLock] |
call MutexLock |
; 5. Insert the request to the tail of the queue. |
add esi, usb_device_data.RequestsQueue |
mov edx, [esi+request_queue_item.Prev] |
mov [ebx+request_queue_item.Next], esi |
mov [ebx+request_queue_item.Prev], edx |
mov [edx+request_queue_item.Next], ebx |
mov [esi+request_queue_item.Prev], ebx |
; 6. Test whether the queue was empty |
; and the request should be started immediately. |
cmp [esi+request_queue_item.Next], ebx |
jnz .unlock |
; 8. If the step 6 shows that the request is the first in the queue, |
; start it. |
sub esi, usb_device_data.RequestsQueue |
call setup_request |
jmp .nothing |
.unlock: |
call MutexUnlock |
; 9. Return. |
.nothing: |
pop esi ebx |
ret 20 |
endp |
; The current request is completed. Call the callback, |
; remove the request from the queue, start the next |
; request if there is one. |
; esi points to usb_device_data |
proc complete_request |
; 1. Print common debug messages on fails. |
if DEBUG |
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL |
jb .normal |
jz .fail |
DEBUGF 1, 'K : Fatal error during execution of command %x\n', [esi+usb_device_data.Command.Command]:2 |
jmp .normal |
.fail: |
DEBUGF 1, 'K : Command %x failed\n', [esi+usb_device_data.Command.Command]:2 |
.normal: |
end if |
; 2. Get the current request. |
mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
; 3. Call the callback. |
stdcall [ebx+request_queue_item.Callback], esi, [ebx+request_queue_item.UserData] |
; 4. Lock the queue. |
lea ecx, [esi+usb_device_data.QueueLock] |
call MutexLock |
; 5. Remove the request. |
lea edx, [esi+usb_device_data.RequestsQueue] |
mov eax, [ebx+request_queue_item.Next] |
mov [eax+request_queue_item.Prev], edx |
mov [edx+request_queue_item.Next], eax |
; 6. Free the request memory. |
push eax edx |
xchg eax, ebx |
call Kfree |
pop edx ebx |
; 7. If there is a next request, start processing. |
cmp ebx, edx |
jnz setup_request |
; 8. Unlock the queue and return. |
lea ecx, [esi+usb_device_data.QueueLock] |
call MutexUnlock |
ret |
endp |
; Start processing the request. Called either by queue_request |
; or when the previous request has been processed. |
; Do not call directly, use queue_request. |
; Must be called when queue is locked; unlocks the queue when returns. |
proc setup_request |
xor eax, eax |
; 1. If DeviceDisconnected has been run, then all handles of pipes |
; are invalid, so we must fail immediately. |
; (That is why this function needs the locked queue: this |
; guarantee that either DeviceDisconnected has been already run, or |
; DeviceDisconnected will not return before the queue is unlocked.) |
cmp [esi+usb_device_data.DeviceDisconnected], al |
jnz .fatal |
; 2. If the previous command has encountered a fatal error, |
; perform reset recovery. |
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
jb .norecovery |
; 2a. Send Bulk-Only Mass Storage Reset command to config pipe. |
lea edx, [esi+usb_device_data.ConfigRequest] |
mov word [edx], (REQUEST_BORESET shl 8) + 21h ; class request |
mov word [edx+6], ax ; length = 0 |
stdcall USBControlTransferAsync, [esi+usb_device_data.ConfigPipe], edx, eax, eax, recovery_callback1, esi, eax |
; 2b. Fail here = fatal error. |
test eax, eax |
jz .fatal |
; 2c. Otherwise, unlock the queue and return. recovery_callback1 will continue processing. |
.unlock_return: |
lea ecx, [esi+usb_device_data.QueueLock] |
call MutexUnlock |
ret |
.norecovery: |
; 3. Send the command. Fail (no memory or device disconnected) = fatal error. |
; Otherwise, go to 2c. |
call request_stage1 |
test eax, eax |
jnz .unlock_return |
.fatal: |
; 4. Fatal error. Set status = FATAL, unlock the queue, complete the request. |
mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
lea ecx, [esi+usb_device_data.QueueLock] |
call MutexUnlock |
jmp complete_request |
endp |
; Initiate USB transfer for the first stage of a request (send command). |
proc request_stage1 |
mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
; 1. Set the stage to 1 = command stage. |
inc [ebx+request_queue_item.Stage] |
; 2. Generate the command. Zero-initialize and use the caller-provided proc. |
lea edx, [esi+usb_device_data.Command] |
xor eax, eax |
mov [edx+command_block_wrapper.CommandLength], 12 |
mov dword [edx+command_block_wrapper.Command], eax |
mov dword [edx+command_block_wrapper.Command+4], eax |
mov dword [edx+command_block_wrapper.Command+8], eax |
mov dword [edx+command_block_wrapper.Command+12], eax |
inc [edx+command_block_wrapper.Tag] |
stdcall [ebx+request_queue_item.ReqBuilder], edx, [ebx+request_queue_item.UserData] |
; 4. Initiate USB transfer. |
lea edx, [esi+usb_device_data.Command] |
if DUMP_PACKETS |
DEBUGF 1,'K : USBSTOR out:' |
mov eax, edx |
mov ecx, command_block_wrapper.sizeof |
call debug_dump |
DEBUGF 1,'\n' |
end if |
stdcall USBNormalTransferAsync, [esi+usb_device_data.OutPipe], edx, command_block_wrapper.sizeof, request_callback1, esi, 0 |
ret |
endp |
if DUMP_PACKETS |
proc debug_dump |
test ecx, ecx |
jz .done |
.loop: |
test ecx, 0Fh |
jnz @f |
DEBUGF 1,'\nK :' |
@@: |
DEBUGF 1,' %x',[eax]:2 |
inc eax |
dec ecx |
jnz .loop |
.done: |
ret |
endp |
end if |
; Called when the Reset command is completed, |
; either successfully or not. |
proc recovery_callback1 |
virtual at esp |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
cmp [.status], 0 |
jnz .error |
; todo: reset pipes |
push ebx esi |
mov esi, [.calldata+8] |
call request_stage1 |
pop esi ebx |
test eax, eax |
jz .error |
ret 20 |
.error: |
DEBUGF 1, 'K : error %d while resetting', [.status] |
jmp request_callback1.common_error |
endp |
; Called when the first stage of request is completed, |
; either successfully or not. |
proc request_callback1 |
virtual at esp |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
; 1. Initialize. |
mov ecx, [.calldata] |
mov eax, [.status] |
; 2. Test for error. |
test eax, eax |
jnz .error |
; No error. |
; 3. Increment the stage. |
mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next] |
inc [edx+request_queue_item.Stage] |
; 4. If there is no data, skip this stage. |
cmp [ecx+usb_device_data.Command.Length], 0 |
jz ..request_get_status |
; 5. Initiate USB transfer. If this fails, go to the error handler. |
mov eax, [ecx+usb_device_data.InPipe] |
cmp [ecx+usb_device_data.Command.Flags], 0 |
js @f |
mov eax, [ecx+usb_device_data.OutPipe] |
if DUMP_PACKETS |
DEBUGF 1,'K : USBSTOR out:' |
push eax ecx |
mov eax, [edx+request_queue_item.Buffer] |
mov ecx, [ecx+usb_device_data.Command.Length] |
call debug_dump |
pop ecx eax |
DEBUGF 1,'\n' |
end if |
@@: |
stdcall USBNormalTransferAsync, eax, [edx+request_queue_item.Buffer], [ecx+usb_device_data.Command.Length], request_callback2, ecx, 0 |
test eax, eax |
jz .error |
; 6. Return. |
ret 20 |
.error: |
; Error. |
; 7. Print debug message and complete the request as failed. |
DEBUGF 1,'K : error %d after %d bytes in request stage\n',eax,[.length] |
.common_error: |
; TODO: add recovery after STALL |
mov ecx, [.calldata] |
mov [ecx+usb_device_data.Status.Status], CSW_STATUS_FATAL |
push ebx esi |
mov esi, ecx |
call complete_request |
pop esi ebx |
ret 20 |
endp |
; Called when the second stage of request is completed, |
; either successfully or not. |
proc request_callback2 |
virtual at esp |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
if DUMP_PACKETS |
mov eax, [.calldata] |
mov eax, [eax+usb_device_data.InPipe] |
cmp [.pipe], eax |
jnz @f |
DEBUGF 1,'K : USBSTOR in:' |
push eax ecx |
mov eax, [.buffer+8] |
mov ecx, [.length+8] |
call debug_dump |
pop ecx eax |
DEBUGF 1,'\n' |
@@: |
end if |
; 1. Initialize. |
mov ecx, [.calldata] |
mov eax, [.status] |
; 2. Test for error. |
test eax, eax |
jnz .error |
; No error. |
..request_get_status: |
; 3. Increment the stage. |
mov edx, [ecx+usb_device_data.RequestsQueue+request_queue_item.Next] |
inc [edx+request_queue_item.Stage] |
; 4. Initiate USB transfer. If this fails, go to the error handler. |
lea edx, [ecx+usb_device_data.Status] |
stdcall USBNormalTransferAsync, [ecx+usb_device_data.InPipe], edx, command_status_wrapper.sizeof, request_callback3, ecx, 0 |
test eax, eax |
jz .error |
ret 20 |
.error: |
; Error. |
; 7. Print debug message and complete the request as failed. |
DEBUGF 1,'K : error %d after %d bytes in data stage\n',eax,[.length] |
jmp request_callback1.common_error |
endp |
; Called when the third stage of request is completed, |
; either successfully or not. |
proc request_callback3 |
virtual at esp |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
if DUMP_PACKETS |
DEBUGF 1,'K : USBSTOR in:' |
mov eax, [.buffer] |
mov ecx, [.length] |
call debug_dump |
DEBUGF 1,'\n' |
end if |
; 1. Initialize. |
mov eax, [.status] |
; 2. Test for error. |
test eax, eax |
jnz .transfer_error |
; Transfer is OK. |
; 3. Validate the status. Invalid status = fatal error. |
push ebx esi |
mov esi, [.calldata+8] |
mov ebx, [esi+usb_device_data.RequestsQueue+request_queue_item.Next] |
cmp [esi+usb_device_data.Status.Signature], 'USBS' |
jnz .invalid |
mov eax, [esi+usb_device_data.Command.Tag] |
cmp [esi+usb_device_data.Status.Tag], eax |
jnz .invalid |
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
ja .invalid |
; 4. The status block is valid. Check the status code. |
jz .complete |
; 5. If this command was not REQUEST_SENSE, copy status data to safe place. |
; Otherwise, the original command has failed, so restore the fail status. |
cmp byte [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE |
jz .request_sense |
mov eax, [esi+usb_device_data.Status.LengthRest] |
mov [esi+usb_device_data.LengthRest], eax |
cmp [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL |
jz .fail |
.complete: |
call complete_request |
.nothing: |
pop esi ebx |
ret 20 |
.request_sense: |
mov [esi+usb_device_data.Status.Status], CSW_STATUS_FAIL |
jmp .complete |
.invalid: |
; 6. Invalid status block. Say error, set status to fatal and complete request. |
push esi |
mov esi, invresponse |
call SysMsgBoardStr |
pop esi |
mov [esi+usb_device_data.Status.Status], CSW_STATUS_FATAL |
jmp .complete |
.fail: |
; 7. The command has failed. |
; If this command was not REQUEST_SENSE, schedule the REQUEST_SENSE command |
; to determine the reason of fail. Otherwise, assume that there is no error data. |
cmp [esi+usb_device_data.Command.Command], SCSI_REQUEST_SENSE |
jz .fail_request_sense |
mov [ebx+request_queue_item.ReqBuilder], request_sense_req |
lea eax, [esi+usb_device_data.Sense] |
mov [ebx+request_queue_item.Buffer], eax |
call request_stage1 |
test eax, eax |
jnz .nothing |
.fail_request_sense: |
DEBUGF 1,'K : fail during REQUEST SENSE\n' |
mov byte [esi+usb_device_data.Sense], 0 |
jmp .complete |
.transfer_error: |
; TODO: add recovery after STALL |
DEBUGF 1,'K : error %d after %d bytes in status stage\n',eax,[.length] |
jmp request_callback1.common_error |
endp |
; Builder for SCSI_REQUEST_SENSE request. |
; edx = first argument = pointer to usb_device_data.Command, |
; second argument = custom data given to queue_request (ignored). |
proc request_sense_req |
mov [edx+command_block_wrapper.Length], sense_data.sizeof |
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
mov byte [edx+command_block_wrapper.Command+0], SCSI_REQUEST_SENSE |
mov byte [edx+command_block_wrapper.Command+4], sense_data.sizeof |
ret 8 |
endp |
; This procedure is called when new mass-storage device is detected. |
; It initializes the device. |
; Technically, initialization implies sending several USB queries, |
; so it is split in several procedures. The first is AddDevice, |
; other are callbacks which will be called at some time in the future, |
; when the device will respond. |
; The general scheme: |
; * AddDevice parses descriptors, opens pipes; if everything is ok, |
; AddDevice sends REQUEST_GETMAXLUN with callback known_lun_callback; |
; * known_lun_callback allocates memory for LogicalDevices and sends |
; SCSI_TEST_UNIT_READY to all logical devices with test_unit_ready_callback; |
; * test_unit_ready_callback checks whether the unit is ready; |
; if not, it repeats the same request several times; |
; if ok or there were too many attempts, it sends SCSI_INQUIRY with |
; callback inquiry_callback; |
; * inquiry_callback checks that a logical device is a block device |
; and the unit was ready; if so, it notifies the kernel about new disk device. |
proc AddDevice |
push ebx esi |
virtual at esp |
rd 2 ; saved registers ebx, esi |
dd ? ; return address |
.pipe0 dd ? ; handle of the config pipe |
.config dd ? ; pointer to config_descr |
.interface dd ? ; pointer to interface_descr |
end virtual |
; 1. Check device type. Currently only SCSI-command-set Bulk-only devices |
; are supported. |
; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and |
; bInterfaceProtocol are subsequent in interface_descr, just one |
; memory reference is used for both. |
mov esi, [.interface] |
xor ebx, ebx |
mov cx, word [esi+interface_descr.bInterfaceSubClass] |
; 1b. For Mass-storage SCSI-command-set Bulk-only devices subclass must be 6 |
; and protocol must be 50h. Check. |
cmp cx, 0x5006 |
jz .known |
; There are devices with subclass 5 which use the same protocol 50h. |
; The difference is not important for the code except for this test, |
; so allow them to proceed also. |
cmp cx, 0x5005 |
jz .known |
; 1c. If the device is unknown, print a message and go to 11c. |
mov esi, unkdevice |
call SysMsgBoardStr |
jmp .nothing |
; 1d. If the device uses known command set, print a message and continue |
; configuring. |
.known: |
push esi |
mov esi, okdevice |
call SysMsgBoardStr |
pop esi |
; 2. Allocate memory for internal device data. |
; 2a. Call the kernel. |
mov eax, usb_device_data.sizeof |
call Kmalloc |
; 2b. Check return value. |
test eax, eax |
jnz @f |
; 2c. If failed, say a message and go to 11c. |
mov esi, nomemory |
call SysMsgBoardStr |
jmp .nothing |
@@: |
; 2d. If succeeded, zero the contents and continue configuring. |
xchg ebx, eax ; ebx will point to usb_device_data |
xor eax, eax |
mov [ebx+usb_device_data.OutPipe], eax |
mov [ebx+usb_device_data.InPipe], eax |
mov [ebx+usb_device_data.MaxLUN], eax |
mov [ebx+usb_device_data.LogicalDevices], eax |
mov dword [ebx+usb_device_data.ConfigRequest], eax |
mov dword [ebx+usb_device_data.ConfigRequest+4], eax |
mov [ebx+usb_device_data.Status.Status], al |
mov [ebx+usb_device_data.DeviceDisconnected], al |
; 2e. There is one reference: a connected USB device. |
inc eax |
mov [ebx+usb_device_data.NumReferences], eax |
; 2f. Save handle of configuration pipe for reset recovery. |
mov eax, [.pipe0] |
mov [ebx+usb_device_data.ConfigPipe], eax |
; 2g. Save the interface number for configuration requests. |
mov al, [esi+interface_descr.bInterfaceNumber] |
mov [ebx+usb_device_data.ConfigRequest+4], al |
; 2h. Initialize common fields in command wrapper. |
mov [ebx+usb_device_data.Command.Signature], 'USBC' |
mov [ebx+usb_device_data.Command.Tag], 'xxxx' |
; 2i. Initialize requests queue. |
lea eax, [ebx+usb_device_data.RequestsQueue] |
mov [eax+request_queue_item.Next], eax |
mov [eax+request_queue_item.Prev], eax |
lea ecx, [ebx+usb_device_data.QueueLock] |
call MutexInit |
; Bulk-only mass storage devices use one OUT bulk endpoint for sending |
; command/data and one IN bulk endpoint for receiving data/status. |
; Look for those endpoints. |
; 3. Get the upper bound of all descriptors' data. |
mov edx, [.config] ; configuration descriptor |
movzx ecx, [edx+config_descr.wTotalLength] |
add edx, ecx |
; 4. Loop over all descriptors until |
; either end-of-data reached - this is fail |
; or interface descriptor found - this is fail, all further data |
; correspond to that interface |
; or both endpoint descriptors found. |
; 4a. Loop start: esi points to the interface descriptor, |
.lookep: |
; 4b. Get next descriptor. |
movzx ecx, byte [esi] ; the first byte of all descriptors is length |
add esi, ecx |
; 4c. Check that at least two bytes are readable. The opposite is an error. |
inc esi |
cmp esi, edx |
jae .errorep |
dec esi |
; 4d. Check that this descriptor is not interface descriptor. The opposite is |
; an error. |
cmp byte [esi+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE |
jz .errorep |
; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue |
; the loop. |
cmp byte [esi+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE |
jnz .lookep |
; 5. Check that the descriptor contains all required data and all data are |
; readable. The opposite is an error. |
cmp byte [esi+endpoint_descr.bLength], endpoint_descr.sizeof |
jb .errorep |
lea ecx, [esi+endpoint_descr.sizeof] |
cmp ecx, edx |
ja .errorep |
; 6. Check that the endpoint is bulk endpoint. The opposite is an error. |
mov cl, [esi+endpoint_descr.bmAttributes] |
and cl, 3 |
cmp cl, BULK_PIPE |
jnz .errorep |
; 7. Get the direction of this endpoint. |
movzx ecx, [esi+endpoint_descr.bEndpointAddress] |
shr ecx, 7 |
; 8. Test whether a pipe for this direction is already opened. If so, continue |
; the loop. |
cmp [ebx+usb_device_data.OutPipe+ecx*4], 0 |
jnz .lookep |
; 9. Open pipe for this endpoint. |
; 9a. Save registers. |
push ecx edx |
; 9b. Load parameters from the descriptor. |
movzx ecx, [esi+endpoint_descr.bEndpointAddress] |
movzx edx, [esi+endpoint_descr.wMaxPacketSize] |
movzx eax, [esi+endpoint_descr.bInterval] ; not used for USB1, may be important for USB2 |
; 9c. Call the kernel. |
stdcall USBOpenPipe, [ebx+usb_device_data.ConfigPipe], ecx, edx, BULK_PIPE, eax |
; 9d. Restore registers. |
pop edx ecx |
; 9e. Check result. If failed, go to 11b. |
test eax, eax |
jz .free |
; 9f. Save result. |
mov [ebx+usb_device_data.OutPipe+ecx*4], eax |
; 10. Test whether the second pipe is already opened. If not, continue loop. |
xor ecx, 1 |
cmp [ebx+usb_device_data.OutPipe+ecx*4], 0 |
jz .lookep |
jmp .created |
; 11. An error occured during processing endpoint descriptor. |
.errorep: |
; 11a. Print a message. |
DEBUGF 1,'K : error: invalid endpoint descriptor\n' |
.free: |
; 11b. Free the allocated usb_device_data. |
xchg eax, ebx |
call Kfree |
.nothing: |
; 11c. Return an error. |
xor eax, eax |
jmp .return |
.created: |
; 12. Pipes are opened. Send GetMaxLUN control request. |
lea eax, [ebx+usb_device_data.ConfigRequest] |
mov byte [eax], 0A1h ; class request from interface |
mov byte [eax+1], REQUEST_GETMAXLUN |
mov byte [eax+6], 1 ; transfer 1 byte |
lea ecx, [ebx+usb_device_data.MaxLUN] |
if DUMP_PACKETS |
DEBUGF 1,'K : GETMAXLUN: %x %x %x %x %x %x %x %x\n',[eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2 |
end if |
stdcall USBControlTransferAsync, [ebx+usb_device_data.ConfigPipe], eax, ecx, 1, known_lun_callback, ebx, 0 |
; 13. Return with pointer to device data as returned value. |
xchg eax, ebx |
.return: |
pop esi ebx |
ret 12 |
endp |
; This function is called when REQUEST_GETMAXLUN is done, |
; either successful or unsuccessful. |
proc known_lun_callback |
push ebx esi |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
; 1. Check the status. If the request failed, assume that MaxLUN is zero. |
mov ebx, [.calldata] |
mov eax, [.status] |
test eax, eax |
jz @f |
DEBUGF 1, 'K : GETMAXLUN failed with status %d, assuming zero\n', eax |
mov [ebx+usb_device_data.MaxLUN], 0 |
@@: |
; 2. Allocate the memory for logical devices. |
mov eax, [ebx+usb_device_data.MaxLUN] |
inc eax |
DEBUGF 1,'K : %d logical unit(s)\n',eax |
imul eax, usb_unit_data.sizeof |
push ebx |
call Kmalloc |
pop ebx |
; If failed, print a message and do nothing. |
test eax, eax |
jnz @f |
mov esi, nomemory |
call SysMsgBoardStr |
pop esi ebx |
ret 20 |
@@: |
mov [ebx+usb_device_data.LogicalDevices], eax |
; 3. Initialize logical devices and initiate TEST_UNIT_READY request. |
xchg esi, eax |
xor ecx, ecx |
.looplun: |
mov [esi+usb_unit_data.Parent], ebx |
mov [esi+usb_unit_data.LUN], cl |
xor eax, eax |
mov [esi+usb_unit_data.MediaPresent], al |
mov [esi+usb_unit_data.DiskDevice], eax |
mov [esi+usb_unit_data.SectorSize], eax |
mov [esi+usb_unit_data.UnitReadyAttempts], eax |
push ecx |
call GetTimerTicks |
mov [esi+usb_unit_data.TimerTicks], eax |
stdcall queue_request, ebx, test_unit_ready_req, 0, test_unit_ready_callback, esi |
pop ecx |
inc ecx |
add esi, usb_unit_data.sizeof |
cmp ecx, [ebx+usb_device_data.MaxLUN] |
jbe .looplun |
; 4. Return. |
pop esi ebx |
ret 20 |
endp |
; Builder for SCSI INQUIRY request. |
; edx = first argument = pointer to usb_device_data.Command, |
; second argument = custom data given to queue_request. |
proc inquiry_req |
mov eax, [esp+8] |
mov al, [eax+usb_unit_data.LUN] |
mov [edx+command_block_wrapper.Length], inquiry_data.sizeof |
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
mov [edx+command_block_wrapper.LUN], al |
mov byte [edx+command_block_wrapper.Command+0], SCSI_INQUIRY |
mov byte [edx+command_block_wrapper.Command+4], inquiry_data.sizeof |
ret 8 |
endp |
; Called when SCSI INQUIRY request is completed. |
proc inquiry_callback |
; 1. Check the status. |
mov ecx, [esp+4] |
cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK |
jnz .fail |
; 2. The command has completed successfully. |
; Print a message showing device type, ignore anything but block devices. |
mov al, [ecx+usb_device_data.InquiryData.PeripheralDevice] |
and al, 1Fh |
DEBUGF 1,'K : peripheral device type is %x\n',al |
test al, al |
jnz .nothing |
DEBUGF 1,'K : direct-access mass storage device detected\n' |
; 3. We have found a new disk device. Increment number of references. |
lock inc [ecx+usb_device_data.NumReferences] |
; Unfortunately, we are now in the context of the USB thread, |
; so we can't notify the kernel immediately: it would try to do something |
; with a new disk, those actions would be synchronous and would require |
; waiting for results of USB requests, but we need to exit this callback |
; to allow the USB thread to continue working and handling those requests. |
; 4. Thus, create a temporary kernel thread which would do it. |
mov edx, [esp+8] |
push ebx ecx |
push 51 |
pop eax |
push 1 |
pop ebx |
mov ecx, new_disk_thread |
; edx = parameter |
int 0x40 |
pop ecx ebx |
cmp eax, -1 |
jnz .nothing |
; on error, reverse step 3 |
lock dec [ecx+usb_device_data.NumReferences] |
.nothing: |
ret 8 |
.fail: |
; 4. The command has failed. Print a message and do nothing. |
push esi |
mov esi, inquiry_fail |
call SysMsgBoardStr |
pop esi |
ret 8 |
endp |
; Builder for SCSI TEST_UNIT_READY request. |
; edx = first argument = pointer to usb_device_data.Command, |
; second argument = custom data given to queue_request. |
proc test_unit_ready_req |
mov eax, [esp+8] |
mov al, [eax+usb_unit_data.LUN] |
mov [edx+command_block_wrapper.Length], 0 |
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
mov [edx+command_block_wrapper.LUN], al |
ret 8 |
endp |
; Called when SCSI TEST_UNIT_READY request is completed. |
proc test_unit_ready_callback |
virtual at esp |
dd ? ; return address |
.device dd ? |
.calldata dd ? |
end virtual |
; 1. Check the status. |
mov ecx, [.device] |
mov edx, [.calldata] |
cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_OK |
jnz .fail |
; 2. The command has completed successfully, |
; possibly after some repetitions. Print a debug message showing |
; number and time of those. Remember that media is ready and go to 4. |
DEBUGF 1,'K : media is ready\n' |
call GetTimerTicks |
sub eax, [edx+usb_unit_data.TimerTicks] |
DEBUGF 1,'K : %d attempts, %d ticks\n',[edx+usb_unit_data.UnitReadyAttempts],eax |
inc [edx+usb_unit_data.MediaPresent] |
jmp .inquiry |
.fail: |
; 3. The command has failed. |
; Retry the same request up to 3 times with 10ms delay; |
; if limit of retries is not reached, exit from the function. |
; Otherwise, go to 4. |
inc [edx+usb_unit_data.UnitReadyAttempts] |
cmp [edx+usb_unit_data.UnitReadyAttempts], 3 |
jz @f |
push ecx edx esi |
push 10 |
pop esi |
call Sleep |
pop esi edx ecx |
stdcall queue_request, ecx, test_unit_ready_req, 0, test_unit_ready_callback, edx |
ret 8 |
@@: |
DEBUGF 1,'K : media not ready\n' |
.inquiry: |
; 4. initiate INQUIRY request. |
lea eax, [ecx+usb_device_data.InquiryData] |
stdcall queue_request, ecx, inquiry_req, eax, inquiry_callback, edx |
ret 8 |
endp |
; Temporary thread for initial actions with a new disk device. |
proc new_disk_thread |
sub esp, 32 |
virtual at esp |
.name rb 32 ; device name |
.param dd ? ; contents of edx at the moment of int 0x40/eax=51 |
dd ? ; stack segment |
end virtual |
; We are ready to notify the kernel about a new disk device. |
mov esi, [.param] |
; 1. Generate name. |
; 1a. Find a free index. |
mov ecx, free_numbers_lock |
call MutexLock |
xor eax, eax |
@@: |
bsf edx, [free_numbers+eax] |
jnz @f |
add eax, 4 |
cmp eax, 4*4 |
jnz @b |
call MutexUnlock |
push esi |
mov esi, noindex |
call SysMsgBoardStr |
pop esi |
jmp .drop_reference |
@@: |
; 1b. Mark the index as busy. |
btr [free_numbers+eax], edx |
lea eax, [eax*8+edx] |
push eax |
call MutexUnlock |
pop eax |
; 1c. Generate a name of the form "usbhd<index>" in the stack. |
mov dword [esp], 'usbh' |
lea edi, [esp+5] |
mov byte [edi-1], 'd' |
push eax |
push -'0' |
push 10 |
pop ecx |
@@: |
cdq |
div ecx |
push edx |
test eax, eax |
jnz @b |
@@: |
pop eax |
add al, '0' |
stosb |
jnz @b |
pop ecx |
mov edx, esp |
; 3d. Store the index in usb_unit_data to free it later. |
mov [esi+usb_unit_data.DiskIndex], cl |
; 4. Notify the kernel about a new disk. |
; 4a. Add a disk. |
; stdcall queue_request, ecx, read_capacity_req, eax, read_capacity_callback, eax |
stdcall DiskAdd, disk_functions, edx, esi, 0 |
mov ebx, eax |
; 4b. If it failed, release the index and do nothing. |
test eax, eax |
jz .free_index |
; 4c. Notify the kernel that a media is present. |
stdcall DiskMediaChanged, eax, 1 |
; 5. Lock the requests queue, check that device is not disconnected, |
; store the disk handle, unlock the requests queue. |
mov ecx, [esi+usb_unit_data.Parent] |
add ecx, usb_device_data.QueueLock |
call MutexLock |
cmp byte [ecx+usb_device_data.DeviceDisconnected-usb_device_data.QueueLock], 0 |
jnz .disconnected |
mov [esi+usb_unit_data.DiskDevice], ebx |
call MutexUnlock |
jmp .exit |
.disconnected: |
call MutexUnlock |
stdcall disk_close, ebx |
jmp .exit |
.free_index: |
mov ecx, free_numbers_lock |
call MutexLock |
movzx eax, [esi+usb_unit_data.DiskIndex] |
bts [free_numbers], eax |
call MutexUnlock |
.drop_reference: |
mov esi, [esi+usb_unit_data.Parent] |
lock dec [esi+usb_device_data.NumReferences] |
jnz .exit |
mov eax, [esi+usb_device_data.LogicalDevices] |
call Kfree |
xchg eax, esi |
call Kfree |
.exit: |
or eax, -1 |
int 0x40 |
endp |
; This function is called when the device is disconnected. |
proc DeviceDisconnected |
push ebx esi |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.device dd ? |
end virtual |
; 1. Say a message. |
mov esi, disconnectmsg |
call SysMsgBoardStr |
; 2. Lock the requests queue, set .DeviceDisconnected to 1, |
; unlock the requests queue. |
; Locking is required for synchronization with queue_request: |
; all USB callbacks are executed in the same thread and are |
; synchronized automatically, but queue_request can be running |
; from any thread which wants to do something with a filesystem. |
; Without locking, it would be possible that queue_request has |
; been started, has checked that device is not yet disconnected, |
; then DeviceDisconnected completes and all handles become invalid, |
; then queue_request tries to use them. |
mov esi, [.device] |
lea ecx, [esi+usb_device_data.QueueLock] |
call MutexLock |
mov [esi+usb_device_data.DeviceDisconnected], 1 |
call MutexUnlock |
; 3. Drop one reference to the structure and check whether |
; that was the last reference. |
lock dec [esi+usb_device_data.NumReferences] |
jz .free |
; 4. If not, there are some additional references due to disk devices; |
; notify the kernel that those disks are deleted. |
; Note that new disks cannot be added while we are looping here, |
; because new_disk_thread checks for .DeviceDisconnected. |
mov ebx, [esi+usb_device_data.MaxLUN] |
mov esi, [esi+usb_device_data.LogicalDevices] |
inc ebx |
.diskdel: |
mov eax, [esi+usb_unit_data.DiskDevice] |
test eax, eax |
jz @f |
stdcall DiskDel, eax |
@@: |
add esi, usb_unit_data.sizeof |
dec ebx |
jnz .diskdel |
; In this case, some operations with those disks are still possible, |
; so we can't do anything more now. disk_close will take care of the rest. |
.return: |
pop esi ebx |
ret 4 |
; 5. If there are no disk devices, free all resources which were allocated. |
.free: |
mov eax, [esi+usb_device_data.LogicalDevices] |
test eax, eax |
jz @f |
call Kfree |
@@: |
xchg eax, esi |
call Kfree |
jmp .return |
endp |
; Disk functions. |
DISK_STATUS_OK = 0 ; success |
DISK_STATUS_GENERAL_ERROR = -1; if no other code is suitable |
DISK_STATUS_INVALID_CALL = 1 ; invalid input parameters |
DISK_STATUS_NO_MEDIA = 2 ; no media present |
DISK_STATUS_END_OF_MEDIA = 3 ; end of media while reading/writing data |
; Called when all operations with the given disk are done. |
proc disk_close |
push ebx esi |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.userdata dd ? |
end virtual |
mov esi, [.userdata] |
mov ecx, free_numbers_lock |
call MutexLock |
movzx eax, [esi+usb_unit_data.DiskIndex] |
bts [free_numbers], eax |
call MutexUnlock |
mov esi, [esi+usb_unit_data.Parent] |
lock dec [esi+usb_device_data.NumReferences] |
jnz .nothing |
mov eax, [esi+usb_device_data.LogicalDevices] |
call Kfree |
xchg eax, esi |
call Kfree |
.nothing: |
pop esi ebx |
ret 4 |
endp |
; Returns sector size, capacity and flags of the media. |
proc disk_querymedia stdcall uses ebx esi edi, \ |
userdata:dword, mediainfo:dword |
; 1. Create event for waiting. |
xor esi, esi |
xor ecx, ecx |
call CreateEvent |
test eax, eax |
jz .generic_fail |
push eax |
push edx |
push ecx |
push 0 |
push 0 |
virtual at ebp-.localsize |
.locals: |
; two following dwords are the output of READ_CAPACITY |
.LastLBABE dd ? |
.SectorSizeBE dd ? |
.Status dd ? |
; two following dwords identify an event |
.event_code dd ? |
.event dd ? |
rd 3 ; saved registers |
.localsize = $ - .locals |
dd ? ; saved ebp |
dd ? ; return address |
.userdata dd ? |
.mediainfo dd ? |
end virtual |
; 2. Initiate SCSI READ_CAPACITY request. |
mov eax, [userdata] |
mov ecx, [eax+usb_unit_data.Parent] |
mov edx, esp |
stdcall queue_request, ecx, read_capacity_req, edx, read_capacity_callback, edx |
; 3. Wait for event. This destroys it. |
mov eax, [.event] |
mov ebx, [.event_code] |
call WaitEvent |
; 4. Get the status and results. |
pop ecx |
bswap ecx ; .LastLBA |
pop edx |
bswap edx ; .SectorSize |
pop eax ; .Status |
; 5. If the request has completed successfully, store results. |
test eax, eax |
jnz @f |
DEBUGF 1,'K : sector size is %d, last sector is %d\n',edx,ecx |
mov ebx, [mediainfo] |
mov [ebx], eax ; flags = 0 |
mov [ebx+4], edx ; sectorsize |
add ecx, 1 |
adc eax, 0 |
mov [ebx+8], ecx |
mov [ebx+12], eax ; capacity |
mov eax, [userdata] |
mov [eax+usb_unit_data.SectorSize], edx |
xor eax, eax |
@@: |
; 6. Restore the stack and return. |
pop ecx |
pop ecx |
ret |
.generic_fail: |
or eax, -1 |
ret |
endp |
; Builder for SCSI READ_CAPACITY request. |
; edx = first argument = pointer to usb_device_data.Command, |
; second argument = custom data given to queue_request, |
; pointer to disk_querymedia.locals. |
proc read_capacity_req |
mov eax, [esp+8] |
mov eax, [eax+disk_querymedia.userdata-disk_querymedia.locals] |
mov al, [eax+usb_unit_data.LUN] |
mov [edx+command_block_wrapper.Length], 8 |
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
mov [edx+command_block_wrapper.LUN], al |
mov byte [edx+command_block_wrapper.Command+0], SCSI_READ_CAPACITY |
ret 8 |
endp |
; Called when SCSI READ_CAPACITY request is completed. |
proc read_capacity_callback |
; Transform the status to return value of disk_querymedia |
; and set the event. |
mov ecx, [esp+4] |
xor eax, eax |
cmp [ecx+usb_device_data.Status.Status], al |
jz @f |
or eax, -1 |
@@: |
mov ecx, [esp+8] |
mov [ecx+disk_querymedia.Status-disk_querymedia.locals], eax |
push ebx esi edi |
mov eax, [ecx+disk_querymedia.event-disk_querymedia.locals] |
mov ebx, [ecx+disk_querymedia.event_code-disk_querymedia.locals] |
xor edx, edx |
xor esi, esi |
call RaiseEvent |
pop edi esi ebx |
ret 8 |
endp |
disk_write: |
mov al, SCSI_WRITE10 |
jmp disk_read_write |
disk_read: |
mov al, SCSI_READ10 |
; Reads from the device or writes to the device. |
proc disk_read_write stdcall uses ebx esi edi, \ |
userdata:dword, buffer:dword, startsector:qword, numsectors:dword |
; 1. Initialize. |
push eax ; .command |
mov eax, [userdata] |
mov eax, [eax+usb_unit_data.SectorSize] |
push eax ; .SectorSize |
push 0 ; .processed |
mov eax, [numsectors] |
mov eax, [eax] |
; 2. The transfer length for SCSI_{READ,WRITE}10 commands can not be greater |
; than 0xFFFF, so split the request to slices with <= 0xFFFF sectors. |
max_sectors_at_time = 0xFFFF |
.split: |
push eax ; .length_rest |
cmp eax, max_sectors_at_time |
jb @f |
mov eax, max_sectors_at_time |
@@: |
sub [esp], eax |
push eax ; .length_cur |
; 3. startsector must fit in 32 bits, otherwise abort the request. |
cmp dword [startsector+4], 0 |
jnz .generic_fail |
; 4. Create event for waiting. |
xor esi, esi |
xor ecx, ecx |
call CreateEvent |
test eax, eax |
jz .generic_fail |
push eax ; .event |
push edx ; .event_code |
push ecx ; .status |
virtual at ebp-.localsize |
.locals: |
.status dd ? |
.event_code dd ? |
.event dd ? |
.length_cur dd ? |
.length_rest dd ? |
.processed dd ? |
.SectorSize dd ? |
.command db ? |
rb 3 |
rd 3 ; saved registers |
.localsize = $ - .locals |
dd ? ; saved ebp |
dd ? ; return address |
.userdata dd ? |
.buffer dd ? |
.startsector dq ? |
.numsectors dd ? |
end virtual |
; 5. Initiate SCSI READ10 or WRITE10 request. |
mov eax, [userdata] |
mov ecx, [eax+usb_unit_data.Parent] |
stdcall queue_request, ecx, read_write_req, [buffer], read_write_callback, esp |
; 6. Wait for event. This destroys it. |
mov eax, [.event] |
mov ebx, [.event_code] |
call WaitEvent |
; 7. Get the status. If the operation has failed, abort. |
pop eax ; .status |
pop ecx ecx ; cleanup .event_code, .event |
pop ecx ; .length_cur |
test eax, eax |
jnz .return |
; 8. Otherwise, continue the loop started at step 2. |
add dword [startsector], ecx |
adc dword [startsector+4], eax |
imul ecx, [.SectorSize] |
add [buffer], ecx |
pop eax |
test eax, eax |
jnz .split |
push eax |
.return: |
; 9. Restore the stack, store .processed to [numsectors], return. |
pop ecx ; .length_rest |
pop ecx ; .processed |
mov edx, [numsectors] |
mov [edx], ecx |
pop ecx ; .SectorSize |
pop ecx ; .command |
ret |
.generic_fail: |
or eax, -1 |
pop ecx ; .length_cur |
jmp .return |
endp |
; Builder for SCSI READ10 or WRITE10 request. |
; edx = first argument = pointer to usb_device_data.Command, |
; second argument = custom data given to queue_request, |
; pointer to disk_read_write.locals. |
proc read_write_req |
mov eax, [esp+8] |
mov ecx, [eax+disk_read_write.userdata-disk_read_write.locals] |
mov cl, [ecx+usb_unit_data.LUN] |
mov [edx+command_block_wrapper.LUN], cl |
mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals] |
imul ecx, [eax+disk_read_write.SectorSize-disk_read_write.locals] |
mov [edx+command_block_wrapper.Length], ecx |
mov cl, [eax+disk_read_write.command-disk_read_write.locals] |
mov [edx+command_block_wrapper.Flags], CBW_FLAG_OUT |
cmp cl, SCSI_READ10 |
jnz @f |
mov [edx+command_block_wrapper.Flags], CBW_FLAG_IN |
@@: |
mov byte [edx+command_block_wrapper.Command], cl |
mov ecx, dword [eax+disk_read_write.startsector-disk_read_write.locals] |
bswap ecx |
mov dword [edx+command_block_wrapper.Command+2], ecx |
mov ecx, [eax+disk_read_write.length_cur-disk_read_write.locals] |
xchg cl, ch |
mov word [edx+command_block_wrapper.Command+7], cx |
ret 8 |
endp |
; Called when SCSI READ10 or WRITE10 request is completed. |
proc read_write_callback |
; 1. Initialize. |
push ebx esi edi |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.device dd ? |
.calldata dd ? |
end virtual |
mov ecx, [.device] |
mov esi, [.calldata] |
; 2. Get the number of sectors which were read. |
; If the status is OK or FAIL, the field .LengthRest is valid. |
; Otherwise, it is invalid, so assume zero sectors. |
xor eax, eax |
cmp [ecx+usb_device_data.Status.Status], CSW_STATUS_FAIL |
ja .sectors_calculated |
mov eax, [ecx+usb_device_data.LengthRest] |
xor edx, edx |
div [esi+disk_read_write.SectorSize-disk_read_write.locals] |
test edx, edx |
jz @f |
inc eax |
@@: |
mov edx, eax |
mov eax, [esi+disk_read_write.length_cur-disk_read_write.locals] |
sub eax, edx |
jae .sectors_calculated |
xor eax, eax |
.sectors_calculated: |
; 3. Increase the total number of processed sectors. |
add [esi+disk_read_write.processed-disk_read_write.locals], eax |
; 4. Set status to OK if all sectors were read, to ERROR otherwise. |
cmp eax, [esi+disk_read_write.length_cur-disk_read_write.locals] |
setz al |
movzx eax, al |
dec eax |
mov [esi+disk_read_write.status-disk_read_write.locals], eax |
; 5. Set the event. |
mov eax, [esi+disk_read_write.event-disk_read_write.locals] |
mov ebx, [esi+disk_read_write.event_code-disk_read_write.locals] |
xor edx, edx |
xor esi, esi |
call RaiseEvent |
; 6. Return. |
pop edi esi ebx |
ret 8 |
endp |
; strings |
my_driver db 'usbstor',0 |
disconnectmsg db 'K : USB mass storage device disconnected',13,10,0 |
nomemory db 'K : no memory',13,10,0 |
unkdevice db 'K : unknown mass storage device',13,10,0 |
okdevice db 'K : USB mass storage device detected',13,10,0 |
transfererror db 'K : USB transfer error, disabling mass storage',13,10,0 |
invresponse db 'K : invalid response from mass storage device',13,10,0 |
fatalerr db 'K : mass storage device reports fatal error',13,10,0 |
inquiry_fail db 'K : INQUIRY command failed',13,10,0 |
;read_capacity_fail db 'K : READ CAPACITY command failed',13,10,0 |
;read_fail db 'K : READ command failed',13,10,0 |
noindex db 'K : failed to generate disk name',13,10,0 |
; Exported variable: kernel API version. |
align 4 |
version dd 50005h |
; Structure with callback functions. |
usb_functions: |
dd usb_functions_end - usb_functions |
dd AddDevice |
dd DeviceDisconnected |
usb_functions_end: |
disk_functions: |
dd disk_functions_end - disk_functions |
dd disk_close |
dd 0 ; closemedia |
dd disk_querymedia |
dd disk_read |
dd disk_write |
dd 0 ; flush |
dd 0 ; adjust_cache_size: use default cache |
disk_functions_end: |
free_numbers_lock rd 3 |
; 128 devices should be enough for everybody |
free_numbers dd -1, -1, -1, -1 |
; for DEBUGF macro |
include_debug_strings |
; for uninitialized data |
section '.data' data readable writable align 16 |
Property changes: |
Added: svn:eol-style |
+native |
\ No newline at end of property |
/kernel/trunk/kernel.asm |
---|
805,6 → 805,8 |
stdcall load_driver, szVidintel |
call usb_init |
; SET PRELIMINARY WINDOW STACK AND POSITIONS |
mov esi, boot_windefs |
/kernel/trunk/kernel32.inc |
---|
220,6 → 220,9 |
include "bus/pci/pci32.inc" |
; USB functions |
include "bus/usb/init.inc" |
; Floppy drive controller |
include "blkdev/fdc.inc" |