Subversion Repositories Kolibri OS

Compare Revisions

No changes between revisions

Regard whitespace Rev 3519 → Rev 3520

/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"