;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2013-2024. All rights reserved. ;; ;; Distributed under terms of the GNU General Public License ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Initialization of the USB subsystem. ; Provides usb_init procedure, includes all needed files. ; General notes: ; * There is one entry point for external kernel code: usb_init is called ; from initialization code and initializes USB subsystem. ; * There are several entry points for API; see the docs for description. ; * There are several functions which are called from controller-specific ; parts of USB subsystem. The most important is usb_new_device, ; which is called when a new device has been connected (over some time), ; has been reset and is ready to start configuring. ; * IRQ handlers are very restricted. They can not take any locks, ; since otherwise a deadlock is possible: imagine that a code has taken the ; lock and was interrupted by IRQ handler. Now IRQ handler would wait for ; releasing the lock, and a lock owner would wait for exiting IRQ handler ; to get the control. ; * Thus, there is the special USB thread which processes almost all activity. ; IRQ handlers do the minimal processing and wake this thread. ; * Also the USB thread wakes occasionally to process tasks which can be ; predicted without interrupts. These include e.g. a periodic roothub ; scanning in UHCI and initializing in USB_CONNECT_DELAY ticks ; after connecting a new device. ; * The main procedure of USB thread, usb_thread_proc, does all its work ; by querying usb_hardware_func.ProcessDeferred for every controller ; and usb_hub_process_deferred for every hub. ; ProcessDeferred does controller-specific actions and calculates the time ; when it should be invoked again, possibly infinite. ; usb_thread_proc selects the minimum from all times returned by ; ProcessDeferred and sleeps until this moment is reached or the thread ; is awakened by IRQ handler. iglobal uhci_service_name: db 'UHCI',0 ohci_service_name: db 'OHCI',0 ehci_service_name: db 'EHCI',0 xhci_service_name: db 'XHCI',0 endg ; Initializes the USB subsystem. proc usb_init ; 1. Initialize all locks. mov ecx, usb_controllers_list_mutex call mutex_init ; 2. Kick off BIOS from all USB controllers, calling the corresponding function ; *hci_kickoff_bios. Also count USB controllers for the next step. ; Note: USB1 companion(s) must go before the corresponding EHCI controller, ; otherwise BIOS could see a device moving from EHCI to a companion; ; first, this always wastes time; ; second, some BIOSes are buggy, do not expect that move and try to refer to ; previously-assigned controller instead of actual; sometimes that leads to ; hangoff. ; Thus, process controllers in PCI order. mov esi, pcidev_list push 0 .kickoff: mov esi, [esi+PCIDEV.fd] cmp esi, pcidev_list jz .done_kickoff cmp word [esi+PCIDEV.class+1], 0x0C03 jnz .kickoff mov ebx, uhci_service_name cmp byte [esi+PCIDEV.class], 0x00 jz .do_kickoff mov ebx, ohci_service_name cmp byte [esi+PCIDEV.class], 0x10 jz .do_kickoff mov ebx, ehci_service_name cmp byte [esi+PCIDEV.class], 0x20 jz .do_kickoff mov ebx, xhci_service_name cmp byte [esi+PCIDEV.class], 0x30 jnz .kickoff .do_kickoff: inc dword [esp] push ebx esi stdcall get_service, ebx pop esi ebx test eax, eax jz .driver_fail mov edx, [eax+USBSRV.usb_func] cmp [edx+usb_hardware_func.Version], USBHC_VERSION jnz .driver_invalid mov [esi+PCIDEV.owner], eax call [edx+usb_hardware_func.BeforeInit] jmp .kickoff .driver_fail: DEBUGF 1,'K : failed to load driver %s\n',ebx jmp .kickoff .driver_invalid: DEBUGF 1,'K : driver %s has wrong version\n',ebx jmp .kickoff .done_kickoff: pop eax ; 3. If no controllers were found, exit. ; Otherwise, run the USB thread. test eax, eax jz .nothing call create_usb_thread jz .nothing ; 4. Initialize all USB controllers, calling usb_init_controller for each. ; Note: USB1 companion(s) should go before the corresponding EHCI controller, ; although this is not strictly necessary (this way, a companion would not try ; to initialize high-speed device only to see a disconnect when EHCI takes ; control). ; Thus, process all EHCI controllers in the first loop, all USB1 controllers ; in the second loop. (One loop in reversed PCI order could also be used, ; but seems less natural.) ; 4a. Loop over all PCI devices, call usb_init_controller ; for all EHCI controllers. mov eax, pcidev_list .scan_xhci: mov eax, [eax+PCIDEV.fd] cmp eax, pcidev_list jz .done_xhci cmp [eax+PCIDEV.class], 0x0C0330 jnz .scan_xhci call usb_init_controller jmp .scan_xhci .done_xhci: mov eax, pcidev_list .scan_ehci: mov eax, [eax+PCIDEV.fd] cmp eax, pcidev_list jz .done_ehci cmp [eax+PCIDEV.class], 0x0C0320 jnz .scan_ehci call usb_init_controller jmp .scan_ehci .done_ehci: ; 4b. Loop over all PCI devices, call usb_init_controller ; for all UHCI and OHCI controllers. mov eax, pcidev_list .scan_usb1: mov eax, [eax+PCIDEV.fd] cmp eax, pcidev_list jz .done_usb1 cmp [eax+PCIDEV.class], 0x0C0300 jz @f cmp [eax+PCIDEV.class], 0x0C0310 jnz .scan_usb1 @@: call usb_init_controller jmp .scan_usb1 .done_usb1: .nothing: ret endp uglobal align 4 usb_event dd ? endg ; Helper function for usb_init. Creates and initializes the USB thread. proc create_usb_thread ; 1. Create the thread. push edi movi ebx, 1 mov ecx, usb_thread_proc xor edx, edx call new_sys_threads pop edi ; If failed, say something to the debug board and return with ZF set. test eax, eax jns @f DEBUGF 1,'K : cannot create kernel thread for USB, error %d\n',eax .clear: xor eax, eax jmp .nothing @@: ; 2. Wait while the USB thread initializes itself. @@: call change_task cmp [usb_event], 0 jz @b ; 3. If initialization failed, the USB thread sets [usb_event] to -1. ; Return with ZF set or cleared corresponding to the result. cmp [usb_event], -1 jz .clear .nothing: ret endp ; Helper function for IRQ handlers. Wakes the USB thread if ebx is nonzero. proc usb_wakeup_if_needed test ebx, ebx jz usb_wakeup.nothing usb_wakeup: xor edx, edx mov eax, [usb_event] mov ebx, [eax+EVENT.id] xor esi, esi call raise_event .nothing: ret endp ; Main loop of the USB thread. proc usb_thread_proc ; 1. Initialize: create event to allow wakeup by interrupt handlers. xor esi, esi mov ecx, MANUAL_DESTROY call create_event test eax, eax jnz @f ; If failed, set [usb_event] to -1 and terminate myself. dbgstr 'cannot create event for USB thread' or [usb_event], -1 jmp sys_end @@: mov [usb_event], eax push -1 ; initial timeout: infinite usb_thread_wait: ; 2. Main loop: wait for either wakeup event or timeout. pop ecx ; get timeout mov eax, [usb_event] mov ebx, [eax+EVENT.id] call wait_event_timeout push -1 ; default timeout: infinite ; 3. Main loop: call worker functions of all controllers; ; if some function schedules wakeup in timeout less than the current value, ; replace that value with the returned timeout. mov esi, usb_controllers_list @@: mov esi, [esi+usb_controller.Next] cmp esi, usb_controllers_list jz .controllers_done mov eax, [esi+usb_controller.HardwareFunc] call [eax+usb_hardware_func.ProcessDeferred] cmp [esp], eax jb @b mov [esp], eax jmp @b .controllers_done: ; 4. Main loop: call hub worker function for all hubs, ; similarly calculating minimum of all returned timeouts. ; When done, continue to 2. mov esi, usb_hubs_list @@: mov esi, [esi+usb_hub.Next] cmp esi, usb_hubs_list jz usb_thread_wait call usb_hub_process_deferred cmp [esp], eax jb @b mov [esp], eax jmp @b endp iglobal align 4 usb_controllers_list: dd usb_controllers_list dd usb_controllers_list usb_hubs_list: dd usb_hubs_list dd usb_hubs_list endg uglobal align 4 usb_controllers_list_mutex MUTEX endg include "memory.inc" include "common.inc" include "hccommon.inc" include "pipe.inc" include "protocol.inc" include "hub.inc"