1892 lines
74 KiB
NASM
1892 lines
74 KiB
NASM
; Code for UHCI controllers.
|
|
|
|
; Standard driver stuff
|
|
format PE DLL native
|
|
entry start
|
|
__DEBUG__ equ 1
|
|
__DEBUG_LEVEL__ equ 1
|
|
section '.reloc' data readable discardable fixups
|
|
section '.text' code readable executable
|
|
include '../proc32.inc'
|
|
include '../struct.inc'
|
|
include '../macros.inc'
|
|
include '../fdo.inc'
|
|
include '../../kernel/trunk/bus/usb/common.inc'
|
|
|
|
; =============================================================================
|
|
; ================================= Constants =================================
|
|
; =============================================================================
|
|
; 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 ticks: it is small enough to be responsive 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), block size for the allocator
|
|
; must be divisible by 16; usb1_allocate_endpoint ensures this.
|
|
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).
|
|
ends
|
|
|
|
; 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+3072,...
|
|
; 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.
|
|
EhciCompanion dd ?
|
|
; Pointer to usb_controller for EHCI companion, if any, or NULL.
|
|
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), block size for the allocator must be
|
|
; divisible by 16; usb1_allocate_general_td ensures this.
|
|
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)
|
|
ends
|
|
|
|
; 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 USBHC_VERSION
|
|
dd 'UHCI'
|
|
dd sizeof.uhci_controller
|
|
dd uhci_kickoff_bios
|
|
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 uhci_alloc_pipe
|
|
dd uhci_free_pipe
|
|
dd uhci_init_pipe
|
|
dd uhci_unlink_pipe
|
|
dd uhci_alloc_td
|
|
dd uhci_free_td
|
|
dd uhci_alloc_transfer
|
|
dd uhci_insert_transfer
|
|
dd uhci_new_device
|
|
dd uhci_disable_pipe
|
|
dd uhci_enable_pipe
|
|
uhci_name db 'UHCI',0
|
|
endg
|
|
|
|
; =============================================================================
|
|
; =================================== Code ====================================
|
|
; =============================================================================
|
|
|
|
; Called once when driver is loading and once at shutdown.
|
|
; When loading, must initialize itself, register itself in the system
|
|
; and return eax = value obtained when registering.
|
|
proc start
|
|
virtual at esp
|
|
dd ? ; return address
|
|
.reason dd ? ; DRV_ENTRY or DRV_EXIT
|
|
.cmdline dd ? ; normally NULL
|
|
end virtual
|
|
cmp [.reason], DRV_ENTRY
|
|
jnz .nothing
|
|
mov ecx, uhci_ep_mutex
|
|
and dword [ecx-4], 0
|
|
invoke MutexInit
|
|
mov ecx, uhci_gtd_mutex
|
|
and dword [ecx-4], 0
|
|
invoke MutexInit
|
|
push esi edi
|
|
mov esi, [USBHCFunc]
|
|
mov edi, usbhc_api
|
|
movi ecx, sizeof.usbhc_func/4
|
|
rep movsd
|
|
pop edi esi
|
|
invoke RegUSBDriver, uhci_name, 0, uhci_hardware_func
|
|
.nothing:
|
|
ret
|
|
endp
|
|
|
|
; Controller-specific initialization function.
|
|
; Called from usb_init_controller. Initializes the hardware and
|
|
; UHCI-specific parts of software structures.
|
|
; 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
|
|
invoke GetPhysAddr
|
|
; 2b. Fill first 32 entries.
|
|
inc eax
|
|
inc eax ; set QH bit for uhci_pipe.NextQH
|
|
movi ecx, 32
|
|
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.
|
|
invoke PciRead16, dword [.bus], dword [.devfn], 4
|
|
push eax
|
|
; 4b. Disable IO access.
|
|
and al, not 1
|
|
invoke PciWrite16, dword [.bus], dword [.devfn], 4, eax
|
|
; 4c. Read&save IO base address.
|
|
invoke PciRead16, dword [.bus], dword [.devfn], 20h
|
|
and al, not 3
|
|
xchg eax, edi
|
|
; now edi = IO base
|
|
; 4d. Write 0xffff to IO base address.
|
|
invoke PciWrite16, dword [.bus], dword [.devfn], 20h, -1
|
|
; 4e. Read IO base address.
|
|
invoke PciRead16, dword [.bus], dword [.devfn], 20h
|
|
and al, not 3
|
|
cwde
|
|
not eax
|
|
inc eax
|
|
xchg eax, esi
|
|
; now esi = IO size
|
|
; 4f. Restore IO base address.
|
|
invoke PciWrite16, dword [.bus], dword [.devfn], 20h, edi
|
|
; 4g. Restore PCI command state and enable io & bus master access.
|
|
pop ecx
|
|
or ecx, 5
|
|
invoke PciWrite16, dword [.bus], dword [.devfn], 4, ecx
|
|
; 5. Reset the controller.
|
|
; 5e. Host reset.
|
|
mov edx, edi
|
|
mov ax, 2
|
|
out dx, ax
|
|
; 5f. Wait up to 10ms.
|
|
movi ecx, 10
|
|
@@:
|
|
push esi
|
|
movi esi, 1
|
|
invoke Sleep
|
|
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
|
|
movi esi, 10
|
|
invoke Sleep
|
|
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
|
|
invoke GetTimerTicks
|
|
mov [esi+uhci_controller.LastPollTime-sizeof.uhci_controller], eax
|
|
; 8. Find the EHCI companion.
|
|
; If there is one, check whether all ports are covered by that companion.
|
|
; Note: this assumes that EHCI is initialized before USB1 companions.
|
|
mov ebx, dword [.devfn]
|
|
invoke usbhc_api.usb_find_ehci_companion
|
|
mov [esi+uhci_controller.EhciCompanion-sizeof.uhci_controller], eax
|
|
; 9. Hook interrupt.
|
|
invoke PciRead8, dword [.bus], dword [.devfn], 3Ch
|
|
; al = IRQ
|
|
; DEBUGF 1,'K : UHCI %x: io=%x, irq=%x\n',esi,edi,al
|
|
movzx eax, al
|
|
invoke AttachIntHandler, eax, uhci_irq, esi
|
|
; 10. Setup controller registers.
|
|
xor eax, eax
|
|
mov edx, [esi+uhci_controller.IOBase-sizeof.uhci_controller]
|
|
; 10a. UhciStatusReg := 3Fh: clear all status bits
|
|
; (for this register 1 clears the corresponding bit, 0 does not change it).
|
|
inc edx
|
|
inc edx ; UhciStatusReg == 2
|
|
mov al, 3Fh
|
|
out dx, ax
|
|
; 10b. UhciInterruptReg := 0Dh.
|
|
inc edx
|
|
inc edx ; UhciInterruptReg == 4
|
|
mov al, 0Dh
|
|
out dx, ax
|
|
; 10c. UhciFrameNumberReg := 0.
|
|
inc edx
|
|
inc edx ; UhciFrameNumberReg == 6
|
|
mov al, 0
|
|
out dx, ax
|
|
; 10d. UhciBaseAddressReg := physical address of uhci_controller.
|
|
inc edx
|
|
inc edx ; UhciBaseAddressReg == 8
|
|
lea eax, [esi-sizeof.uhci_controller]
|
|
invoke GetPhysAddr
|
|
out dx, eax
|
|
; 10e. UhciCommandReg := Run + Configured + (MaxPacket is 64 bytes)
|
|
sub edx, UhciBaseAddressReg ; UhciCommandReg == 0
|
|
mov ax, 0C1h ; Run, Configured, MaxPacket = 64b
|
|
out dx, ax
|
|
; 11. Do initial scan of existing devices.
|
|
call uhci_poll_roothub
|
|
; 12. Return pointer to usb_controller.
|
|
xchg eax, esi
|
|
ret
|
|
.fail:
|
|
; On error, pop the pointer saved at step 1 and return zero.
|
|
; Note that the main code branch restores the stack at step 8 and never fails
|
|
; after step 8.
|
|
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.
|
|
invoke PciRead16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 20h
|
|
and eax, 0xFFFC
|
|
xchg eax, edx
|
|
; 2. Stop the controller and disable all interrupts.
|
|
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.
|
|
invoke PciWrite16, dword [esi+PCIDEV.bus], dword [esi+PCIDEV.devfn], 0xC0, 0AF00h
|
|
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
|
|
invoke usbhc_api.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
|
|
invoke usbhc_api.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
|
|
invoke GetTimerTicks
|
|
sub eax, [esi+uhci_controller.LastPollTime-sizeof.uhci_controller]
|
|
sub eax, UHCI_POLL_INTERVAL
|
|
jl .nopoll
|
|
.force_poll:
|
|
invoke GetTimerTicks
|
|
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).
|
|
invoke usbhc_api.usb_disconnect_stage2
|
|
; 5. Test whether USB_CONNECT_DELAY for a connected device is over.
|
|
; Call uhci_new_port for all such devices.
|
|
xor ecx, ecx
|
|
cmp [esi+usb_controller.NewConnected], ecx
|
|
jz .skip_newconnected
|
|
.portloop:
|
|
bt [esi+usb_controller.NewConnected], ecx
|
|
jnc .noconnect
|
|
; If this port is shared with the EHCI companion and we see the connect event,
|
|
; then the device is USB1 dropped by EHCI,
|
|
; so EHCI has already waited for debounce delay, we can proceed immediately.
|
|
cmp [esi+uhci_controller.EhciCompanion-sizeof.uhci_controller], 0
|
|
jz .portloop.test_time
|
|
dbgstr 'port is shared with EHCI, skipping initial debounce'
|
|
jmp .connected
|
|
.portloop.test_time:
|
|
invoke GetTimerTicks
|
|
sub eax, [esi+usb_controller.ConnectedTime+ecx*4]
|
|
sub eax, USB_CONNECT_DELAY
|
|
jge .connected
|
|
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.
|
|
invoke GetTimerTicks
|
|
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.
|
|
invoke GetTimerTicks
|
|
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.
|
|
invoke usbhc_api.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]
|
|
invoke MutexLock
|
|
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-sizeof.uhci_gtd]
|
|
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
|
|
invoke MutexUnlock
|
|
call uhci_process_finalized_td
|
|
pop ecx
|
|
invoke MutexLock
|
|
jmp .tdloop
|
|
.tddone:
|
|
invoke MutexUnlock
|
|
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.
|
|
invoke usbhc_api.usb_unlink_td
|
|
; DEBUGF 1,'K : finalized TD:\n'
|
|
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-20],[ebx-16],[ebx-12],[ebx-8]
|
|
; DEBUGF 1,'K : %x %x %x %x\n',[ebx-4],[ebx],[ebx+4],[ebx+8]
|
|
; 2. If this is IN transfer into special buffer, copy the data
|
|
; to target location.
|
|
mov edx, [ebx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd]
|
|
and edx, not 1 ; clear lsb (used for another goal)
|
|
jz .nocopy
|
|
cmp byte [ebx+uhci_gtd.Token-sizeof.uhci_gtd], 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-sizeof.uhci_gtd]
|
|
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-sizeof.uhci_gtd]
|
|
mov ecx, [ebx+uhci_gtd.Token-sizeof.uhci_gtd]
|
|
; 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-sizeof.uhci_gtd], 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 sizeof.uhci_gtd <> 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-sizeof.uhci_pipe],[eax+4-sizeof.uhci_pipe]
|
|
; 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-sizeof.uhci_pipe]
|
|
test eax, eax
|
|
jz @f
|
|
stdcall uhci_free_td, eax
|
|
@@:
|
|
mov eax, [ebx+usb_gtd.Pipe]
|
|
mov [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe], 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.
|
|
invoke usbhc_api.usb_is_final_packet
|
|
jnc .found_final
|
|
mov ebx, [ebx+usb_gtd.NextVirt]
|
|
.look_final:
|
|
invoke usbhc_api.usb_unlink_td
|
|
invoke usbhc_api.usb_is_final_packet
|
|
jnc .found_final
|
|
push [ebx+usb_gtd.NextVirt]
|
|
stdcall uhci_free_td, ebx
|
|
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.
|
|
movi ecx, USB_STATUS_UNDERRUN
|
|
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-sizeof.uhci_gtd], 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-sizeof.uhci_gtd]
|
|
and al, not 0xF
|
|
mov edx, [ebx+usb_gtd.Pipe]
|
|
mov [edx+uhci_pipe.HeadTD-sizeof.uhci_pipe], 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-sizeof.uhci_pipe]
|
|
jz @f
|
|
stdcall uhci_free_td, ebx
|
|
@@:
|
|
pop ebx
|
|
invoke usbhc_api.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-sizeof.uhci_gtd]
|
|
and al, not 0xF
|
|
inc eax ; set Halted bit
|
|
mov [edx+uhci_pipe.HeadTD-sizeof.uhci_pipe], 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.
|
|
invoke usbhc_api.usb_process_gtd
|
|
; 7. Free the current descriptor (if allowed) and return the next one.
|
|
; 7a. Save pointer to the next descriptor.
|
|
push [ebx+usb_gtd.NextVirt]
|
|
; 7b. Free the descriptor, unless it is saved as ErrorTD.
|
|
mov eax, [ebx+usb_gtd.Pipe]
|
|
cmp [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe], 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-sizeof.uhci_pipe]
|
|
mov eax, [eax+uhci_gtd.Token-sizeof.uhci_gtd]
|
|
xor eax, [ebx+uhci_gtd.Token-sizeof.uhci_gtd]
|
|
test eax, 1 shl 19
|
|
jz .nothing
|
|
; 3. Lock the transfer queue.
|
|
add ecx, usb_pipe.Lock
|
|
invoke MutexLock
|
|
; 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-sizeof.uhci_gtd+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-sizeof.uhci_pipe-usb_pipe.Lock+2], 1 shl (19-16)
|
|
; 6. Unlock the transfer queue.
|
|
invoke MutexUnlock
|
|
.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 : UHCI %x connect status changed, %x/%x\n',esi,bx,ax
|
|
; yep. Regardless of the current status, note disconnect event;
|
|
; if there is something connected, store the connect time and note connect event.
|
|
; In any way, do not process
|
|
bts [esi+usb_controller.NewDisconnected], ecx
|
|
test al, 1
|
|
jz .disconnect
|
|
invoke GetTimerTicks
|
|
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.
|
|
invoke GetTimerTicks
|
|
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 : UHCI %x status %x/',esi,ax
|
|
and ah, not 2
|
|
out dx, ax
|
|
; 2. Status bits in UHCI are invalid during reset signalling.
|
|
; Wait a millisecond while status bits become valid again.
|
|
push esi
|
|
movi esi, 1
|
|
invoke Sleep
|
|
pop esi
|
|
; 3. ConnectStatus bit is zero during reset and becomes 1 during step 2;
|
|
; some controllers interpret this as a (fake) connect event.
|
|
; 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.
|
|
invoke GetTimerTicks
|
|
DEBUGF 1,'K : reset done\n'
|
|
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 : UHCI %x status %x\n',esi,ax
|
|
; 2. If the device has been disconnected, stop the initialization.
|
|
test al, 1
|
|
jnz @f
|
|
dbgstr 'USB port disabled after reset'
|
|
jmp [usbhc_api.usb_test_pending_port]
|
|
@@:
|
|
; 3. Copy LowSpeed bit to bit 0 of eax and call the worker procedure
|
|
; to notify the protocol layer about new UHCI device.
|
|
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 [usbhc_api.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.
|
|
invoke usbhc_api.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-sizeof.uhci_pipe], cl
|
|
jmp [usbhc_api.usb_subscription_done]
|
|
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-sizeof.uhci_pipe]
|
|
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-sizeof.uhci_pipe], (1 shl 21) - 1
|
|
or [ebx+uhci_pipe.Token-sizeof.uhci_pipe], ecx
|
|
; uhci_pipe.Token field is purely for software bookkeeping and does not affect
|
|
; the hardware; thus, we can continue initialization immediately.
|
|
jmp [usbhc_api.usb_subscription_done]
|
|
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-12
|
|
.speed db ?
|
|
rb 3
|
|
.bandwidth dd ?
|
|
.target dd ?
|
|
rd 2
|
|
.config_pipe dd ?
|
|
.endpoint dd ?
|
|
.maxpacket dd ?
|
|
.type dd ?
|
|
.interval dd ?
|
|
end virtual
|
|
; 1. Initialize ErrorTD to zero.
|
|
and [edi+uhci_pipe.ErrorTD-sizeof.uhci_pipe], 0
|
|
; 2. Initialize HeadTD to the physical address of the first TD.
|
|
push eax ; store pointer to the first TD for step 4
|
|
sub eax, sizeof.uhci_gtd
|
|
invoke GetPhysAddr
|
|
mov [edi+uhci_pipe.HeadTD-sizeof.uhci_pipe], 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-sizeof.uhci_pipe]
|
|
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-sizeof.uhci_pipe], eax
|
|
bt eax, 20
|
|
setc [.speed]
|
|
; 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",
|
|
; zero OrigBufferInfo.
|
|
pop edx ; restore pointer saved in step 2
|
|
mov [edx+uhci_gtd.Token-sizeof.uhci_gtd], eax
|
|
and byte [edx+uhci_gtd.Token+2-sizeof.uhci_gtd], 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
|
|
and [edx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 0
|
|
mov [edx+uhci_gtd.ControlStatus-sizeof.uhci_gtd], eax
|
|
mov [edx+uhci_gtd.NextTD-sizeof.uhci_gtd], 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]
|
|
movi ecx, 64
|
|
call usb1_select_interrupt_list
|
|
test edx, edx
|
|
jz .return0
|
|
.insert:
|
|
mov [edi+usb_pipe.BaseList], edx
|
|
; 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-sizeof.uhci_pipe], ecx
|
|
lea eax, [edi-sizeof.uhci_pipe]
|
|
invoke GetPhysAddr
|
|
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 <> sizeof.uhci_pipe
|
|
.err uhci_unlink_pipe assumes that uhci_static_ep.SoftwarePart == sizeof.uhci_pipe
|
|
end if
|
|
proc uhci_unlink_pipe
|
|
cmp [ebx+usb_pipe.Type], INTERRUPT_PIPE
|
|
jnz @f
|
|
mov eax, [ebx+uhci_pipe.Token-sizeof.uhci_pipe]
|
|
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
|
|
@@:
|
|
ret
|
|
endp
|
|
|
|
; This procedure temporarily removes the given pipe from hardware queue,
|
|
; keeping it in software lists.
|
|
; esi -> usb_controller, ebx -> usb_pipe
|
|
proc uhci_disable_pipe
|
|
mov eax, [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe]
|
|
mov edx, [ebx+usb_pipe.PrevVirt]
|
|
; Note: edx could be either usb_pipe or usb_static_ep;
|
|
; fortunately, NextQH and SoftwarePart have same offsets in both.
|
|
mov [edx+uhci_pipe.NextQH-sizeof.uhci_pipe], eax
|
|
ret
|
|
endp
|
|
|
|
; This procedure reinserts the given pipe from hardware queue
|
|
; after ehci_disable_pipe, with clearing transfer queue.
|
|
; esi -> usb_controller, ebx -> usb_pipe
|
|
; edx -> current descriptor, eax -> new last descriptor
|
|
proc uhci_enable_pipe
|
|
; 1. Copy DataToggle bit from edx to pipe.
|
|
mov ecx, [edx+uhci_gtd.Token-sizeof.uhci_gtd]
|
|
xor ecx, [ebx+uhci_pipe.Token-sizeof.uhci_pipe]
|
|
and ecx, 1 shl 19
|
|
xor [ebx+uhci_pipe.Token-sizeof.uhci_pipe], ecx
|
|
; 2. Store new last descriptor as the current HeadTD.
|
|
sub eax, sizeof.uhci_gtd
|
|
invoke GetPhysAddr
|
|
mov [ebx+uhci_pipe.HeadTD-sizeof.uhci_pipe], eax
|
|
; 3. Reinsert the pipe to hardware queue.
|
|
lea eax, [ebx-sizeof.uhci_pipe]
|
|
invoke GetPhysAddr
|
|
inc eax
|
|
inc eax
|
|
mov edx, [ebx+usb_pipe.PrevVirt]
|
|
mov ecx, [edx+uhci_pipe.NextQH-sizeof.uhci_pipe]
|
|
mov [ebx+uhci_pipe.NextQH-sizeof.uhci_pipe], ecx
|
|
mov [edx+uhci_pipe.NextQH-sizeof.uhci_pipe], eax
|
|
ret
|
|
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-sizeof.uhci_pipe]
|
|
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-sizeof.uhci_gtd], not (1 shl (29-24))
|
|
and byte [ecx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], not 1
|
|
@@:
|
|
; 6. Update toggle bit in uhci_pipe structure from current value of [token].
|
|
mov edx, [token]
|
|
xor edx, [ebx+uhci_pipe.Token-sizeof.uhci_pipe]
|
|
and edx, 1 shl 19
|
|
xor [ebx+uhci_pipe.Token-sizeof.uhci_pipe], edx
|
|
.nothing:
|
|
ret
|
|
.fail:
|
|
mov edi, uhci_hardware_func
|
|
mov eax, [td]
|
|
invoke usbhc_api.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.
|
|
mov eax, [.packetSize]
|
|
add eax, eax
|
|
add eax, sizeof.uhci_original_buffer
|
|
invoke Kmalloc
|
|
; 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 uhci_alloc_td
|
|
pop edx
|
|
; If failed, free the temporary buffer (if it was allocated) and return zero.
|
|
test eax, eax
|
|
jz .fail
|
|
; 3. Initialize controller-independent parts of both TDs.
|
|
push edx
|
|
invoke usbhc_api.usb_init_transfer
|
|
; 4. Initialize the next TD:
|
|
; mark it as last one (this will be changed when further packets will be
|
|
; allocated), copy Token field from uhci_pipe.Token zeroing bit 20,
|
|
; generate ControlStatus field, mark as Active
|
|
; (for last descriptor, this will be changed by uhci_insert_transfer),
|
|
; zero OrigBufferInfo (otherwise uhci_free_td would try to free it).
|
|
and [eax+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 0
|
|
mov [eax+uhci_gtd.NextTD-sizeof.uhci_gtd], 1 ; no next TD
|
|
mov edx, [ebx+uhci_pipe.Token-sizeof.uhci_pipe]
|
|
mov [eax+uhci_gtd.Token-sizeof.uhci_gtd], edx
|
|
and byte [eax+uhci_gtd.Token+2-sizeof.uhci_gtd], 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-sizeof.uhci_gtd], 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-sizeof.uhci_gtd]
|
|
; 5b. Store physical address of the next TD.
|
|
push eax
|
|
sub eax, sizeof.uhci_gtd
|
|
invoke GetPhysAddr
|
|
; for Control/Bulk pipes, use Depth traversal unless this is the first TD
|
|
; in the transfer stage;
|
|
; uhci_insert_transfer will set Depth traversal for the first TD and clear
|
|
; it in the last TD
|
|
test [ebx+usb_pipe.Type], 1
|
|
jnz @f
|
|
cmp ecx, [ebx+usb_pipe.LastTD]
|
|
jz @f
|
|
or eax, 4
|
|
@@:
|
|
mov [ecx+uhci_gtd.NextTD-sizeof.uhci_gtd], 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-sizeof.uhci_gtd]
|
|
test edx, edx
|
|
jz @f
|
|
mov eax, [edx+uhci_original_buffer.UsedBuffer]
|
|
@@:
|
|
invoke GetPhysAddr
|
|
.hasphysbuf:
|
|
mov [ecx+uhci_gtd.Buffer-sizeof.uhci_gtd], 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-sizeof.uhci_gtd], 1 shl (29-24) ; disallow short packets
|
|
or byte [ecx+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd], 1
|
|
@@:
|
|
; 5e. Get Token field: combine [.token] with [.packetSize].
|
|
shl edx, 21
|
|
or edx, eax
|
|
mov [ecx+uhci_gtd.Token-sizeof.uhci_gtd], 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
|
|
invoke Kfree
|
|
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-sizeof.uhci_gtd], not (1 shl (23-16)) ; clear Active bit
|
|
or byte [ecx+uhci_gtd.ControlStatus+3-sizeof.uhci_gtd], 1 shl (24-24) ; set InterruptOnComplete bit
|
|
mov eax, [esp+4]
|
|
or byte [eax+uhci_gtd.ControlStatus+2-sizeof.uhci_gtd], 1 shl (23-16) ; set Active bit
|
|
test [ebx+usb_pipe.Type], 1
|
|
jnz @f
|
|
or byte [eax+uhci_gtd.NextTD-sizeof.uhci_gtd], 4 ; set Depth bit
|
|
@@:
|
|
ret
|
|
endp
|
|
|
|
; Allocates one endpoint structure for OHCI.
|
|
; Returns pointer to software part (usb_pipe) in eax.
|
|
proc uhci_alloc_pipe
|
|
push ebx
|
|
mov ebx, uhci_ep_mutex
|
|
invoke usbhc_api.usb_allocate_common, (sizeof.uhci_pipe + sizeof.usb_pipe + 0Fh) and not 0Fh
|
|
test eax, eax
|
|
jz @f
|
|
add eax, sizeof.uhci_pipe
|
|
@@:
|
|
pop ebx
|
|
ret
|
|
endp
|
|
|
|
; Free memory associated with pipe.
|
|
; For UHCI, this includes usb_pipe structure and ErrorTD, if present.
|
|
proc uhci_free_pipe
|
|
mov eax, [esp+4]
|
|
mov eax, [eax+uhci_pipe.ErrorTD-sizeof.uhci_pipe]
|
|
test eax, eax
|
|
jz @f
|
|
stdcall uhci_free_td, eax
|
|
@@:
|
|
sub dword [esp+4], sizeof.uhci_pipe
|
|
jmp [usbhc_api.usb_free_common]
|
|
endp
|
|
|
|
; Allocates one general transfer descriptor structure for UHCI.
|
|
; Returns pointer to software part (usb_gtd) in eax.
|
|
proc uhci_alloc_td
|
|
push ebx
|
|
mov ebx, uhci_gtd_mutex
|
|
invoke usbhc_api.usb_allocate_common, (sizeof.uhci_gtd + sizeof.usb_gtd + 0Fh) and not 0Fh
|
|
test eax, eax
|
|
jz @f
|
|
add eax, sizeof.uhci_gtd
|
|
@@:
|
|
pop ebx
|
|
ret
|
|
endp
|
|
|
|
; Free all memory associated with one TD.
|
|
; For UHCI, this includes memory for uhci_gtd itself
|
|
; and the temporary buffer, if present.
|
|
proc uhci_free_td
|
|
mov eax, [esp+4]
|
|
mov eax, [eax+uhci_gtd.OrigBufferInfo-sizeof.uhci_gtd]
|
|
and eax, not 1
|
|
jz .nobuf
|
|
invoke Kfree
|
|
.nobuf:
|
|
sub dword [esp+4], sizeof.uhci_gtd
|
|
jmp [usbhc_api.usb_free_common]
|
|
endp
|
|
|
|
include 'usb1_scheduler.inc'
|
|
define_controller_name uhci
|
|
|
|
section '.data' readable writable
|
|
include '../peimport.inc'
|
|
include_debug_strings
|
|
IncludeIGlobals
|
|
IncludeUGlobals
|
|
align 4
|
|
usbhc_api usbhc_func
|
|
uhci_ep_first_page dd ?
|
|
uhci_ep_mutex MUTEX
|
|
uhci_gtd_first_page dd ?
|
|
uhci_gtd_mutex MUTEX
|