init.inc
kolibrios-nextgen_kolibrios-kernel/kernel/bus/usb/init.inc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2013-2015. 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"
