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 |