protocol.inc
kolibrios-nextgen_kolibrios-kernel/kernel/bus/usb/protocol.inc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License    ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; Implementation of the USB protocol for device enumeration.
; Manage a USB device when it becomes ready for USB commands:
; configure, enumerate, load the corresponding driver(s),
; pass device information to the driver.

; =============================================================================
; ================================= Constants =================================
; =============================================================================
; USB standard request codes
USB_GET_STATUS        = 0
USB_CLEAR_FEATURE     = 1
USB_SET_FEATURE       = 3
USB_SET_ADDRESS       = 5
USB_GET_DESCRIPTOR    = 6
USB_SET_DESCRIPTOR    = 7
USB_GET_CONFIGURATION = 8
USB_SET_CONFIGURATION = 9
USB_GET_INTERFACE     = 10
USB_SET_INTERFACE     = 11
USB_SYNCH_FRAME       = 12

; USB standard descriptor types
USB_DEVICE_DESCR             = 1
USB_CONFIG_DESCR             = 2
USB_STRING_DESCR             = 3
USB_INTERFACE_DESCR          = 4
USB_ENDPOINT_DESCR           = 5
USB_DEVICE_QUALIFIER_DESCR   = 6
USB_OTHER_SPEED_CONFIG_DESCR = 7
USB_INTERFACE_POWER_DESCR    = 8

; Compile-time setting. If set, the code will dump all descriptors as they are
; read to the debug board.
USB_DUMP_DESCRIPTORS = 1

; According to the USB specification (9.2.6.3),
; any device must response to SET_ADDRESS in 50 ms, or 5 timer ticks.
; Of course, our world is far from ideal.
; I have seen devices that just NAK everything when being reset from working
; state, but start to work after second reset.
; Our strategy is as follows: give 2 seconds for the first attempt,
; this should be enough for normal devices and not too long to detect buggy ones.
; If the device continues NAKing, reset it and retry several times,
; doubling the interval: 2s -> 4s -> 8s -> 16s. Give up after that.
; Numbers are quite arbitrary.
TIMEOUT_SET_ADDRESS_INITIAL = 200
TIMEOUT_SET_ADDRESS_LAST    = 1600

; =============================================================================
; ================================ Structures =================================
; =============================================================================
; USB descriptors. See USB specification for detailed explanations.
; First two bytes of every descriptor have the same meaning.
struct usb_descr
bLength                 db      ?
; Size of this descriptor in bytes
bDescriptorType         db      ?
; One of USB_*_DESCR constants.
ends

; USB device descriptor
struct usb_device_descr usb_descr
bcdUSB                  dw      ?
; USB Specification Release number in BCD, e.g. 110h = USB 1.1
bDeviceClass            db      ?
; USB Device Class Code
bDeviceSubClass         db      ?
; USB Device Subclass Code
bDeviceProtocol         db      ?
; USB Device Protocol Code
bMaxPacketSize0         db      ?
; Maximum packet size for zero endpoint
idVendor                dw      ?
; Vendor ID
idProduct               dw      ?
; Product ID
bcdDevice               dw      ?
; Device release number in BCD
iManufacturer           db      ?
; Index of string descriptor describing manufacturer
iProduct                db      ?
; Index of string descriptor describing product
iSerialNumber           db      ?
; Index of string descriptor describing serial number
bNumConfigurations      db      ?
; Number of possible configurations
ends

; USB configuration descriptor
struct usb_config_descr usb_descr
wTotalLength            dw      ?
; Total length of data returned for this configuration
bNumInterfaces          db      ?
; Number of interfaces in this configuration
bConfigurationValue     db      ?
; Value for SET_CONFIGURATION control request
iConfiguration          db      ?
; Index of string descriptor describing this configuration
bmAttributes            db      ?
; Bit 6 is SelfPowered, bit 5 is RemoteWakeupSupported,
; bit 7 must be 1, other bits must be 0
bMaxPower               db      ?
; Maximum power consumption from the bus in 2mA units
ends

; USB interface descriptor
struct usb_interface_descr usb_descr
; The following two fields work in pair. Sometimes one interface can work
; in different modes; e.g. videostream from web-cameras requires different
; bandwidth depending on resolution/quality/compression settings.
; Each mode of each interface has its own descriptor with its own endpoints
; following; all descriptors for one interface have the same bInterfaceNumber,
; and different bAlternateSetting.
; By default, any interface operates in mode with bAlternateSetting = 0.
; Often this is the only mode. If there are another modes, the active mode
; is selected by SET_INTERFACE(bAlternateSetting) control request.
bInterfaceNumber        db      ?
bAlternateSetting       db      ?
bNumEndpoints           db      ?
; Number of endpoints used by this interface, excluding zero endpoint
bInterfaceClass         db      ?
; USB Interface Class Code
bInterfaceSubClass      db      ?
; USB Interface Subclass Code
bInterfaceProtocol      db      ?
; USB Interface Protocol Code
iInterface              db      ?
; Index of string descriptor describing this interface
ends

; USB endpoint descriptor
struct usb_endpoint_descr usb_descr
bEndpointAddress        db      ?
; Lower 4 bits form endpoint number,
; upper bit is 0 for OUT endpoints and 1 for IN endpoints,
; other bits must be zero
bmAttributes            db      ?
; Lower 2 bits form transfer type, one of *_PIPE,
; other bits must be zero for non-isochronous endpoints;
; refer to the USB specification for meaning in isochronous case
wMaxPacketSize          dw      ?
; Lower 11 bits form maximum packet size,
; next two bits specify the number of additional transactions per microframe
; for high-speed periodic endpoints, other bits must be zero.
bInterval               db      ?
; Interval for polling endpoint for data transfers.
; Isochronous and high-speed interrupt endpoints: poll every 2^(bInterval-1)
; (micro)frames
; Full/low-speed interrupt endpoints: poll every bInterval frames
; High-speed bulk/control OUT endpoints: maximum NAK rate
ends

; =============================================================================
; =================================== Code ====================================
; =============================================================================

; When a new device is ready to be configured, a controller-specific code
; calls usb_new_device.
; The sequence of further actions:
; * open pipe for the zero endpoint (usb_new_device);
;   maximum packet size is not known yet, but it must be at least 8 bytes,
;   so it is safe to send packets with <= 8 bytes
; * issue SET_ADDRESS control request (usb_new_device)
; * set the new device address in the pipe (usb_set_address_callback)
; * notify a controller-specific code that initialization of other ports
;   can be started (usb_set_address_callback)
; * issue GET_DESCRIPTOR control request for first 8 bytes of device descriptor
;   (usb_after_set_address)
; * first 8 bytes of device descriptor contain the true packet size for zero
;   endpoint, so set the true packet size (usb_get_descr8_callback)
; * first 8 bytes of a descriptor contain the full size of this descriptor,
;   issue GET_DESCRIPTOR control request for the full device descriptor
;   (usb_after_set_endpoint_size)
; * issue GET_DESCRIPTOR control request for first 8 bytes of configuration
;   descriptor (usb_get_descr_callback)
; * issue GET_DESCRIPTOR control request for full configuration descriptor
;   (usb_know_length_callback)
; * issue SET_CONFIGURATION control request (usb_set_config_callback)
; * parse configuration descriptor, load the corresponding driver(s),
;   pass the configuration descriptor to the driver and let the driver do
;   the further work (usb_got_config_callback)

; This function is called from controller-specific part
; when a new device is ready to be configured.
; in: ecx -> pseudo-pipe, part of usb_pipe
; in: esi -> usb_controller
; in: [esi+usb_controller.ResettingHub] is the pointer to usb_hub for device,
;     NULL if the device is connected to the root hub
; in: [esi+usb_controller.ResettingPort] is the port for the device, zero-based
; in: [esi+usb_controller.ResettingSpeed] is the speed of the device, one of
;     USB_SPEED_xx.
; out: eax = 0 <=> failed, the caller should disable the port.
proc usb_new_device
        push    ebx edi         ; save used registers to be stdcall
; 1. Check whether we're here because we were trying to reset
; already-registered device in hope to fix something serious.
; If so, skip allocation and go to 6.
        movzx   eax, [esi+usb_controller.ResettingPort]
        mov     edx, [esi+usb_controller.ResettingHub]
        test    edx, edx
        jz      .test_roothub
        mov     edx, [edx+usb_hub.ConnectedDevicesPtr]
        mov     ebx, [edx+eax*4]
        jmp     @f
.test_roothub:
        mov     ebx, [esi+usb_controller.DevicesByPort+eax*4]
@@:
        test    ebx, ebx
        jnz     .try_set_address
; 2. Allocate resources. Any device uses the following resources:
; - device address in the bus
; - memory for device data
; - pipe for zero endpoint
; If some allocation fails, we must undo our actions. Closing the pipe
; is a hard task, so we avoid it and open the pipe as the last resource.
; The order for other two allocations is quite arbitrary.
; 2a. Allocate a bus address.
        push    ecx
        call    usb_set_address_request
        pop     ecx
; 2b. If failed, just return zero.
        test    eax, eax
        jz      .nothing
; 2c. Allocate memory for device data.
; For now, we need sizeof.usb_device_data and extra 8 bytes for GET_DESCRIPTOR
; input and output, see usb_after_set_address. Later we will reallocate it
; to actual size needed for descriptors.
        movi    eax, sizeof.usb_device_data + 8
        push    ecx
        call    malloc
        pop     ecx
; 2d. If failed, free the bus address and return zero.
        test    eax, eax
        jz      .nomemory
; 2e. Open pipe for endpoint zero.
; For now, we do not know the actual maximum packet size;
; for full-speed devices it can be any of 8, 16, 32, 64 bytes,
; low-speed devices must have 8 bytes, high-speed devices must have 64 bytes.
; Thus, we must use some fake "maximum packet size" until the actual size
; will be known. However, the maximum packet size must be at least 8, and
; initial stages of the configuration process involves only packets of <= 8
; bytes, they will be transferred correctly as long as
; the fake "maximum packet size" is also at least 8.
; Thus, any number >= 8 is suitable for actual hardware.
; However, software emulation of EHCI in VirtualBox assumes that high-speed
; control transfers are those originating from pipes with max packet size = 64,
; even on early stages of the configuration process. This is incorrect,
; but we have no specific preferences, so let VirtualBox be happy and use 64
; as the fake "maximum packet size".
        push    eax
; We will need many zeroes.
; "push edi" is one byte, "push 0" is two bytes; save space, use edi.
        xor     edi, edi
        stdcall usb_open_pipe, ecx, edi, 64, edi, edi
; Put pointer to pipe into ebx. "xchg eax,reg" is one byte, mov is two bytes.
        xchg    eax, ebx
        pop     eax
; 2f. If failed, free the memory, the bus address and return zero.
        test    ebx, ebx
        jz      .freememory
; 3. Store pointer to device data in the pipe structure.
        mov     [ebx+usb_pipe.DeviceData], eax
; 4. Init device data, using usb_controller.Resetting* variables.
        mov     [eax+usb_device_data.Timer], edi
        mov     dword [eax+usb_device_data.DeviceDescriptor], TIMEOUT_SET_ADDRESS_INITIAL
        mov     [eax+usb_device_data.TTHub], edi
        mov     [eax+usb_device_data.TTPort], 0
        mov     [eax+usb_device_data.NumInterfaces], edi
        mov     [eax+usb_device_data.DeviceDescrSize], 0
        mov     dl, [esi+usb_controller.ResettingSpeed]
        mov     [eax+usb_device_data.Speed], dl
        mov     [eax+usb_device_data.NumPipes], 1
        push    ebx
        cmp     dl, USB_SPEED_HS
        jz      .nott
        mov     ebx, [esi+usb_controller.ResettingHub]
        test    ebx, ebx
        jz      .nott
        mov     cl, [esi+usb_controller.ResettingPort]
        mov     edx, [ebx+usb_hub.ConfigPipe]
        mov     edx, [edx+usb_pipe.DeviceData]
        cmp     [edx+usb_device_data.TTHub], 0
        jz      @f
        mov     cl, [edx+usb_device_data.TTPort]
        mov     ebx, [edx+usb_device_data.TTHub]
        jmp     .has_tt
@@:
        cmp     [edx+usb_device_data.Speed], USB_SPEED_HS
        jnz     .nott
.has_tt:
        mov     [eax+usb_device_data.TTHub], ebx
        mov     [eax+usb_device_data.TTPort], cl
.nott:
        pop     ebx
        mov     [eax+usb_device_data.ConfigDataSize], edi
        mov     [eax+usb_device_data.Interfaces], edi
        movzx   ecx, [esi+usb_controller.ResettingPort]
        mov     [eax+usb_device_data.Port], cl
        mov     edx, [esi+usb_controller.ResettingHub]
        mov     [eax+usb_device_data.Hub], edx
; 5. Store pointer to the config pipe in the hub data.
; Config pipe serves as device identifier.
; Root hubs use the array inside usb_controller structure,
; non-root hubs use the array immediately after usb_hub structure.
        test    edx, edx
        jz      .roothub
        mov     edx, [edx+usb_hub.ConnectedDevicesPtr]
        mov     [edx+ecx*4], ebx
        jmp     @f
.roothub:
        mov     [esi+usb_controller.DevicesByPort+ecx*4], ebx
@@:
        call    usb_reinit_pipe_list
; 6. Issue SET_ADDRESS control request, using buffer filled in step 2a.
; 6a. Configure timer to force reset after timeout.
; Note: we can't use self-destructing timer, because we need to be able to cancel it,
; and for self-destructing timer we could have race condition in cancelling/destructing.
;        DEBUGF 1,'K : pipe %x\n',ebx
.try_set_address:
        xor     edi, edi
        mov     edx, [ebx+usb_pipe.DeviceData]
        stdcall timer_hs, [edx+usb_device_data.DeviceDescriptor], 7FFFFFFFh, usb_abort_pipe, ebx
        test    eax, eax
        jz      .nothing
        mov     edx, [ebx+usb_pipe.DeviceData]
        mov     [edx+usb_device_data.Timer], eax
; 6b. If it succeeded, setup timer to configure wait timeout.
        lea     eax, [esi+usb_controller.SetAddressBuffer]
        stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi
; Use the return value from usb_control_async as our return value;
; if it is zero, then something has failed.
.nothing:
; 7. Return.
        pop     edi ebx         ; restore used registers to be stdcall
        ret
; Handlers of failures in steps 2b, 2d, 2f.
.freememory:
        call    free
        jmp     .freeaddr
.nomemory:
        dbgstr 'No memory for device data'
.freeaddr:
        mov     ecx, dword [esi+usb_controller.SetAddressBuffer+2]
        bts     [esi+usb_controller.ExistingAddresses], ecx
        xor     eax, eax
        jmp     .nothing
endp

; Helper procedure for usb_new_device.
; Allocates a new USB address and fills usb_controller.SetAddressBuffer
; with data for SET_ADDRESS(allocated_address) request.
; out: eax = 0 <=> failed
; Destroys edi.
proc usb_set_address_request
; There are 128 bits, one for each possible address.
; Note: only the USB thread works with usb_controller.ExistingAddresses,
; so there is no need for synchronization.
; We must find a bit set to 1 and clear it.
; 1. Find the first dword which has a nonzero bit = which is nonzero.
        mov     ecx, 128/32
        lea     edi, [esi+usb_controller.ExistingAddresses]
        xor     eax, eax
        repz scasd
; 2. If all dwords are zero, return an error.
        jz      .error
; 3. The dword at [edi-4] is nonzero. Find the lowest nonzero bit.
        bsf     eax, [edi-4]
; Now eax = bit number inside the dword at [edi-4].
; 4. Clear the bit.
        btr     [edi-4], eax
; 5. Generate the address by edi = memory address and eax = bit inside dword.
; Address = eax + 8 * (edi-4 - (esi+usb_controller.ExistingAddress)).
        sub     edi, esi
        lea     edi, [eax+(edi-4-usb_controller.ExistingAddresses)*8]
; 6. Store the allocated address in SetAddressBuffer and fill remaining fields.
; Note that usb_controller is zeroed at allocation, so only command byte needs
; to be filled.
        mov     byte [esi+usb_controller.SetAddressBuffer+1], USB_SET_ADDRESS
        mov     dword [esi+usb_controller.SetAddressBuffer+2], edi
; 7. Return non-zero value in eax.
        inc     eax
.nothing:
        ret
.error:
        dbgstr 'cannot allocate USB address'
        xor     eax, eax
        jmp     .nothing
endp

; This procedure is called by USB stack when SET_ADDRESS request initiated by
; usb_new_device is completed, either successfully or unsuccessfully.
; Note that USB stack uses esi = pointer to usb_controller.
proc usb_set_address_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
        push    ebx     ; save ebx to be stdcall
        mov     ebx, [pipe]
; 1. In any case, cancel the timer.
        mov     eax, [ebx+usb_pipe.DeviceData]
        stdcall cancel_timer_hs, [eax+usb_device_data.Timer]
        mov     eax, [ebx+usb_pipe.DeviceData]
        mov     [eax+usb_device_data.Timer], 0
; Load data to registers for further references.
        mov     ecx, dword [esi+usb_controller.SetAddressBuffer+2]
        mov     eax, [esi+usb_controller.HardwareFunc]
; 2. Check whether the device has accepted new address. If so, proceed to 3.
; Otherwise, go to 4 if killed by usb_set_address_timeout or to 5 otherwise.
        cmp     [status], USB_STATUS_CANCELLED
        jz      .timeout
        cmp     [status], 0
        jnz     .error
; 3. Address accepted.
; 3a. The controller-specific structure for the control pipe still uses
; zero address. Call the controller-specific function to change it to
; the actual address.
; Note that the hardware could cache the controller-specific structure,
; so setting the address could take some time until the cache is evicted.
; Thus, the call is asynchronous; meet us in usb_after_set_address when it will
; be safe to continue.
;        dbgstr 'address set in device'
        call    [eax+usb_hardware_func.SetDeviceAddress]
; 3b. If the port is in non-root hub, clear 'reset in progress' flag.
; In any case, proceed to 6.
        mov     eax, [esi+usb_controller.ResettingHub]
        test    eax, eax
        jz      .return
        and     [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
.return:
; 6. Address configuration done, we can proceed with other ports.
; Call the worker function for that.
        call    usb_test_pending_port
.wakeup:
        push    esi edi
        call    usb_wakeup
        pop     edi esi
.nothing:
        pop     ebx     ; restore ebx to be stdcall
        ret
.timeout:
; 4. Device continues to NAK the request. Reset it and retry.
        mov     edx, [ebx+usb_pipe.DeviceData]
        mov     ecx, [edx+usb_device_data.DeviceDescriptor]
        add     ecx, ecx
        cmp     ecx, TIMEOUT_SET_ADDRESS_LAST
        ja      .error
        mov     [edx+usb_device_data.DeviceDescriptor], ecx
        dbgstr 'Timeout in USB device initialization, trying to reset...'
        cmp     [esi+usb_controller.ResettingHub], 0
        jz      .reset_roothub
        push    esi
        mov     esi, [esi+usb_controller.ResettingHub]
        call    usb_hub_initiate_reset
        pop     esi
        jmp     .nothing
.reset_roothub:
        movzx   ecx, [esi+usb_controller.ResettingPort]
        call    [eax+usb_hardware_func.InitiateReset]
        jmp     .wakeup
.error:
; 5. Device error: device not responding, disconnect etc.
        DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status]
; 5a. The address has not been accepted. Mark it as free.
        bts     dword [esi+usb_controller.ExistingAddresses], ecx
; 5b. Disable the port with bad device.
; For the root hub, call the controller-specific function and go to 6.
; For non-root hubs, let the hub code do its work and return (the request
; could take some time, the hub code is responsible for proceeding).
        cmp     [esi+usb_controller.ResettingHub], 0
        jz      .roothub
        mov     eax, [esi+usb_controller.ResettingHub]
        call    usb_hub_disable_resetting_port
        jmp     .nothing
.roothub:
        movzx   ecx, [esi+usb_controller.ResettingPort]
        call    [eax+usb_hardware_func.PortDisable]
        jmp     .return
endp

; This procedure is called from usb_subscription_done when the hardware cache
; is cleared after request from usb_set_address_callback.
; in: ebx -> usb_pipe
proc usb_after_set_address
;        dbgstr 'address set for controller'
; Issue control transfer GET_DESCRIPTOR(DEVICE_DESCR) for first 8 bytes.
; Remember, we still do not know the actual packet size;
; 8-bytes-request is safe.
; usb_new_device has allocated 8 extra bytes besides sizeof.usb_device_data;
; use them for both input and output.
        mov     eax, [ebx+usb_pipe.DeviceData]
        add     eax, usb_device_data.DeviceDescriptor
        mov     dword [eax], \
                80h + \         ; device-to-host, standard, device-wide
                (USB_GET_DESCRIPTOR shl 8) + \  ; request
                (0 shl 16) + \  ; descriptor index: there is only one
                (USB_DEVICE_DESCR shl 24)       ; descriptor type
        mov     dword [eax+4], 8 shl 16         ; data length
        stdcall usb_control_async, ebx, eax, eax, 8, usb_get_descr8_callback, eax, 0
        ret
endp

; This procedure is called by USB stack when GET_DESCRIPTOR(DEVICE_DESCR)
; request initiated by usb_after_set_address is completed, either successfully
; or unsuccessfully.
; Note that USB stack uses esi = pointer to usb_controller.
proc usb_get_descr8_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
;       mov     eax, [buffer]
;       DEBUGF 1,'K : descr8: l=%x; %x %x %x %x %x %x %x %x\n',[length],\
;               [eax]:2,[eax+1]:2,[eax+2]:2,[eax+3]:2,[eax+4]:2,[eax+5]:2,[eax+6]:2,[eax+7]:2
        push    edi ebx         ; save used registers to be stdcall
        mov     ebx, [pipe]
; 1. Check whether the operation was successful.
; If not, say something to the debug board and stop the initialization.
        cmp     [status], 0
        jnz     .error
; 2. Length of descriptor must be at least sizeof.usb_device_descr bytes.
; If not, say something to the debug board and stop the initialization.
        mov     eax, [ebx+usb_pipe.DeviceData]
        cmp     [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bLength], sizeof.usb_device_descr
        jb      .error
; 3. Now first 8 bytes of device descriptor are known;
; set DeviceDescrSize accordingly.
        mov     [eax+usb_device_data.DeviceDescrSize], 8
; 4. The controller-specific structure for the control pipe still uses
; the fake "maximum packet size". Call the controller-specific function to
; change it to the actual packet size from the device.
; Note that the hardware could cache the controller-specific structure,
; so changing it could take some time until the cache is evicted.
; Thus, the call is asynchronous; meet us in usb_after_set_endpoint_size
; when it will be safe to continue.
        movzx   ecx, [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bMaxPacketSize0]
        mov     eax, [esi+usb_controller.HardwareFunc]
        call    [eax+usb_hardware_func.SetEndpointPacketSize]
.nothing:
; 5. Return.
        pop     ebx edi         ; restore used registers to be stdcall
        ret
.error:
        dbgstr 'error with USB device descriptor'
        jmp     .nothing
endp

; This procedure is called from usb_subscription_done when the hardware cache
; is cleared after request from usb_get_descr8_callback.
; in: ebx -> usb_pipe
proc usb_after_set_endpoint_size
; 1. Reallocate memory for device data:
; add memory for now-known size of device descriptor and extra 8 bytes
; for further actions.
; 1a. Allocate new memory.
        mov     eax, [ebx+usb_pipe.DeviceData]
        movzx   eax, [eax+usb_device_data.DeviceDescriptor+usb_device_descr.bLength]
; save length for step 2
        push    eax
        add     eax, sizeof.usb_device_data + 8
        call    malloc
; 1b. If failed, say something to the debug board and stop the initialization.
        test    eax, eax
        jz      .nomemory
; 1c. Copy data from old memory to new memory and switch the pointer in usb_pipe.
        push    eax
        push    esi edi
        mov     esi, [ebx+usb_pipe.DeviceData]
        mov     [ebx+usb_pipe.DeviceData], eax
        mov     edi, eax
        mov     eax, esi
        mov     ecx, sizeof.usb_device_data / 4
        rep movsd
        pop     edi esi
        call    usb_reinit_pipe_list
; 1d. Free the old memory.
        call    free
        pop     eax
; 2. Issue control transfer GET_DESCRIPTOR(DEVICE) for full descriptor.
; restore length saved in step 1a
        pop     edx
        add     eax, sizeof.usb_device_data
        mov     dword [eax], \
                80h + \         ; device-to-host, standard, device-wide
                (USB_GET_DESCRIPTOR shl 8) + \  ; request
                (0 shl 16) + \  ; descriptor index: there is only one
                (USB_DEVICE_DESCR shl 24)       ; descriptor type
        and     dword [eax+4], 0
        mov     [eax+6], dl     ; data length
        stdcall usb_control_async, ebx, eax, eax, edx, usb_get_descr_callback, eax, 0
; 3. Return.
        ret
.nomemory:
        dbgstr 'No memory for device data'
        ret
endp

; This procedure is called by USB stack when GET_DESCRIPTOR(DEVICE)
; request initiated by usb_after_set_endpoint_size is completed,
; either successfully or unsuccessfully.
proc usb_get_descr_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
; Note: the prolog is the same as in usb_get_descr8_callback.
        push    edi ebx         ; save used registers to be stdcall
; 1. Check whether the operation was successful.
; If not, say something to the debug board and stop the initialization.
        cmp     [status], 0
        jnz     usb_get_descr8_callback.error
; The full descriptor is known, dump it if specified by compile-time option.
if USB_DUMP_DESCRIPTORS
        mov     eax, [buffer]
        mov     ecx, [length]
        sub     ecx, 8
        jbe     .skipdebug
        DEBUGF 1,'K : device descriptor:'
@@:
        DEBUGF 1,' %x',[eax]:2
        inc     eax
        dec     ecx
        jnz     @b
        DEBUGF 1,'\n'
.skipdebug:
end if
; 2. Check that bLength is the same as was in the previous request.
; If not, say something to the debug board and stop the initialization.
; It is important, because usb_after_set_endpoint_size has allocated memory
; according to the old bLength. Note that [length] for control transfers
; includes 8 bytes of setup packet, so data length = [length] - 8.
        mov     eax, [buffer]
        movzx   ecx, [eax+usb_device_descr.bLength]
        add     ecx, 8
        cmp     [length], ecx
        jnz     usb_get_descr8_callback.error
; Amuse the user if she is watching the debug board.
        mov     cl, [eax+usb_device_descr.bNumConfigurations]
        DEBUGF 1,'K : found USB device with ID %x:%x, %d configuration(s)\n',\
                [eax+usb_device_descr.idVendor]:4,\
                [eax+usb_device_descr.idProduct]:4,\
                cl
; 3. If there are no configurations, stop the initialization.
        cmp     [eax+usb_device_descr.bNumConfigurations], 0
        jz      .nothing
; 4. Copy length of device descriptor to device data structure.
        movzx   edx, [eax+usb_device_descr.bLength]
        mov     [eax+usb_device_data.DeviceDescrSize-usb_device_data.DeviceDescriptor], dl
; 5. Issue control transfer GET_DESCRIPTOR(CONFIGURATION). We do not know
; the full length of that descriptor, so start with first 8 bytes, they contain
; the full length.
; usb_after_set_endpoint_size has allocated 8 extra bytes after the
; device descriptor, use them for both input and output.
        add     eax, edx
        mov     dword [eax], \
                80h + \         ; device-to-host, standard, device-wide
                (USB_GET_DESCRIPTOR shl 8) + \  ; request
                (0 shl 16) + \  ; descriptor index: there is only one
                (USB_CONFIG_DESCR shl 24)       ; descriptor type
        mov     dword [eax+4], 8 shl 16         ; data length
        stdcall usb_control_async, [pipe], eax, eax, 8, usb_know_length_callback, eax, 0
.nothing:
; 6. Return.
        pop     ebx edi         ; restore used registers to be stdcall
        ret
endp

; This procedure is called by USB stack when GET_DESCRIPTOR(CONFIGURATION)
; request initiated by usb_get_descr_callback is completed,
; either successfully or unsuccessfully.
proc usb_know_length_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
        push    ebx             ; save used registers to be stdcall
; 1. Check whether the operation was successful.
; If not, say something to the debug board and stop the initialization.
        cmp     [status], 0
        jnz     .error
; 2. Get the total length of data associated with config descriptor and store
; it in device data structure. The total length must be at least
; sizeof.usb_config_descr bytes; if not, say something to the debug board and
; stop the initialization.
        mov     eax, [buffer]
        mov     edx, [pipe]
        movzx   ecx, [eax+usb_config_descr.wTotalLength]
        mov     eax, [edx+usb_pipe.DeviceData]
        cmp     ecx, sizeof.usb_config_descr
        jb      .error
        mov     [eax+usb_device_data.ConfigDataSize], ecx
; 3. Reallocate memory for device data:
; include usb_device_data structure, device descriptor,
; config descriptor with all associated data, and extra bytes
; sufficient for 8 bytes control packet and for one usb_interface_data struc.
; Align extra bytes to dword boundary.
if sizeof.usb_interface_data > 8
.extra_size = sizeof.usb_interface_data
else
.extra_size = 8
end if
; 3a. Allocate new memory.
        movzx   edx, [eax+usb_device_data.DeviceDescrSize]
        lea     eax, [ecx+edx+sizeof.usb_device_data+.extra_size+3]
        and     eax, not 3
        push    eax
        call    malloc
        pop     edx
; 3b. If failed, say something to the debug board and stop the initialization.
        test    eax, eax
        jz      .nomemory
; 3c. Copy data from old memory to new memory and switch the pointer in usb_pipe.
        push    eax
        mov     ebx, [pipe]
        push    esi edi
        mov     esi, [ebx+usb_pipe.DeviceData]
        mov     edi, eax
        mov     [ebx+usb_pipe.DeviceData], eax
        mov     eax, esi
        movzx   ecx, [esi+usb_device_data.DeviceDescrSize]
        sub     edx, .extra_size
        mov     [esi+usb_device_data.Interfaces], edx
        add     ecx, sizeof.usb_device_data + 8
        mov     edx, ecx
        shr     ecx, 2
        and     edx, 3
        rep movsd
        mov     ecx, edx
        rep movsb
        pop     edi esi
        call    usb_reinit_pipe_list
; 3d. Free old memory.
        call    free
        pop     eax
; 4. Issue control transfer GET_DESCRIPTOR(CONFIGURATION) for full descriptor.
        movzx   ecx, [eax+usb_device_data.DeviceDescrSize]
        mov     edx, [eax+usb_device_data.ConfigDataSize]
        lea     eax, [eax+ecx+sizeof.usb_device_data]
        mov     dword [eax], \
                80h + \         ; device-to-host, standard, device-wide
                (USB_GET_DESCRIPTOR shl 8) + \  ; request
                (0 shl 16) + \  ; descriptor index: there is only one
                (USB_CONFIG_DESCR shl 24)       ; descriptor type
        and     dword [eax+4], 0
        mov     word [eax+6], dx        ; data length
        stdcall usb_control_async, [pipe], eax, eax, edx, usb_set_config_callback, eax, 0
.nothing:
; 5. Return.
        pop     ebx             ; restore used registers to be stdcall
        ret
.error:
        dbgstr 'error with USB configuration descriptor'
        jmp     .nothing
.nomemory:
        dbgstr 'No memory for device data'
        jmp     .nothing
endp

; This procedure is called by USB stack when GET_DESCRIPTOR(CONFIGURATION)
; request initiated by usb_know_length_callback is completed,
; either successfully or unsuccessfully.
proc usb_set_config_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
; Note that the prolog is the same as in usb_know_length_callback.
        push    ebx             ; save used registers to be stdcall
; 1. Check whether the operation was successful.
; If not, say something to the debug board and stop the initialization.
        xor     ecx, ecx
        mov     ebx, [pipe]
        cmp     [status], ecx
        jnz     usb_know_length_callback.error
; The full descriptor is known, dump it if specified by compile-time option.
if USB_DUMP_DESCRIPTORS
        mov     eax, [buffer]
        mov     ecx, [length]
        sub     ecx, 8
        jbe     .skip_debug
        DEBUGF 1,'K : config descriptor:'
@@:
        DEBUGF 1,' %x',[eax]:2
        inc     eax
        dec     ecx
        jnz     @b
        DEBUGF 1,'\n'
.skip_debug:
        xor     ecx, ecx
end if
; 2. Issue control transfer SET_CONFIGURATION to activate this configuration.
; Usually this is the only configuration.
; Use extra bytes allocated by usb_know_length_callback;
; offset from device data start is stored in Interfaces.
        mov     eax, [ebx+usb_pipe.DeviceData]
        mov     edx, [buffer]
        add     eax, [eax+usb_device_data.Interfaces]
        mov     dl, [edx+usb_config_descr.bConfigurationValue]
        mov     dword [eax], USB_SET_CONFIGURATION shl 8
        mov     dword [eax+4], ecx
        mov     byte [eax+2], dl
        stdcall usb_control_async, [pipe], eax, ecx, ecx, usb_got_config_callback, [buffer], ecx
        pop     ebx             ; restore used registers to be stdcall
        ret
endp

; This procedure is called by USB stack when SET_CONFIGURATION
; request initiated by usb_set_config_callback is completed,
; either successfully or unsuccessfully.
; If successfully, the device is configured and ready to work,
; pass the device to the corresponding driver(s).
proc usb_got_config_callback stdcall, pipe:dword, status:dword, buffer:dword, length:dword, calldata:dword
locals
InterfacesData  dd      ?
NumInterfaces   dd      ?
driver          dd      ?
endl
; 1. If there was an error, say something to the debug board and stop the
; initialization.
        cmp     [status], 0
        jz      @f
        dbgstr 'USB error in SET_CONFIGURATION'
        ret
@@:
        push    ebx edi         ; save used registers to be stdcall
; 2. Sanity checks: the total length must be the same as before (because we
; have allocated memory assuming the old value), length of config descriptor
; must be at least sizeof.usb_config_descr (we use fields from it),
; there must be at least one interface.
        mov     ebx, [pipe]
        mov     ebx, [ebx+usb_pipe.DeviceData]
        mov     eax, [calldata]
        mov     edx, [ebx+usb_device_data.ConfigDataSize]
        cmp     [eax+usb_config_descr.wTotalLength], dx
        jnz     .invalid
        cmp     [eax+usb_config_descr.bLength], 9
        jb      .invalid
        movzx   edx, [eax+usb_config_descr.bNumInterfaces]
        test    edx, edx
        jnz     @f
.invalid:
        dbgstr 'error: invalid configuration descriptor'
        jmp     .nothing
@@:
; 3. Store the number of interfaces in device data structure.
        mov     [ebx+usb_device_data.NumInterfaces], edx
; 4. If there is only one interface (which happens quite often),
; the memory allocated in usb_know_length_callback is sufficient.
; Otherwise (which also happens quite often), reallocate device data.
; 4a. Check whether there is only one interface. If so, skip this step.
        cmp     edx, 1
        jz      .has_memory
; 4b. Allocate new memory.
        mov     eax, [ebx+usb_device_data.Interfaces]
        lea     eax, [eax+edx*sizeof.usb_interface_data]
        call    malloc
; 4c. If failed, say something to the debug board and
; stop the initialization.
        test    eax, eax
        jnz     @f
        dbgstr 'No memory for device data'
        jmp     .nothing
@@:
; 4d. Copy data from old memory to new memory and switch the pointer in usb_pipe.
        push    eax
        push    esi
        mov     ebx, [pipe]
        mov     edi, eax
        mov     esi, [ebx+usb_pipe.DeviceData]
        mov     [ebx+usb_pipe.DeviceData], eax
        mov     eax, esi
        mov     ecx, [esi+usb_device_data.Interfaces]
        shr     ecx, 2
        rep movsd
        pop     esi
        call    usb_reinit_pipe_list
; 4e. Free old memory.
        call    free
        pop     ebx
.has_memory:
; 5. Initialize interfaces table: zero all contents.
        mov     edi, [ebx+usb_device_data.Interfaces]
        add     edi, ebx
        mov     [InterfacesData], edi
        mov     ecx, [ebx+usb_device_data.NumInterfaces]
if sizeof.usb_interface_data <> 8
You have changed sizeof.usb_interface_data? Modify this place too.
end if
        add     ecx, ecx
        xor     eax, eax
        rep stosd
; No interfaces are found yet.
        mov     [NumInterfaces], eax
; 6. Get the pointer to config descriptor data.
; Note: if there was reallocation, [buffer] is not valid anymore,
; so calculate value based on usb_device_data.
        movzx   eax, [ebx+usb_device_data.DeviceDescrSize]
        lea     eax, [eax+ebx+sizeof.usb_device_data]
        mov     [calldata], eax
        mov     ecx, [ebx+usb_device_data.ConfigDataSize]
; 7. Loop over all descriptors,
; scan for interface descriptors with bAlternateSetting = 0,
; load the corresponding driver, call its AddDevice function.
.descriptor_loop:
; While in loop: eax points to the current descriptor,
; ecx = number of bytes left, the iteration starts only if ecx is nonzero,
; edx = size of the current descriptor.
; 7a. The first byte is always accessible; it contains the length of
; the current descriptor. Validate that the length is at least 2 bytes,
; and the entire descriptor is readable (the length is at most number of
; bytes left).
        movzx   edx, [eax+usb_descr.bLength]
        cmp     edx, sizeof.usb_descr
        jb      .invalid
        cmp     ecx, edx
        jb      .invalid
; 7b. Check descriptor type. Ignore all non-INTERFACE descriptor.
        cmp     byte [eax+usb_descr.bDescriptorType], USB_INTERFACE_DESCR
        jz      .interface
.next_descriptor:
; 7c. Advance pointer, decrease length left, if there is still something left,
; continue the loop.
        add     eax, edx
        sub     ecx, edx
        jnz     .descriptor_loop
.done:
.nothing:
        pop     edi ebx         ; restore used registers to be stdcall
        ret
.interface:
; 7d. Validate the descriptor length.
        cmp     edx, sizeof.usb_interface_descr
        jb      .next_descriptor
; 7e. If bAlternateSetting is nonzero, this descriptor actually describes
; another mode of already known interface and belongs to the already loaded
; driver; amuse the user and continue to 7c.
        cmp     byte [eax+usb_interface_descr.bAlternateSetting], 0
        jz      @f
        DEBUGF 1,'K : note: alternate setting with %x/%x/%x\n',\
                [eax+usb_interface_descr.bInterfaceClass]:2,\
                [eax+usb_interface_descr.bInterfaceSubClass]:2,\
                [eax+usb_interface_descr.bInterfaceProtocol]:2
        jmp     .next_descriptor
@@:
; 7f. Check that the new interface does not overflow allocated table.
        mov     edx, [NumInterfaces]
        inc     edx
        cmp     edx, [ebx+usb_device_data.NumInterfaces]
        ja      .invalid
; 7g. We have found a new interface. Advance bookkeeping vars.
        mov     [NumInterfaces], edx
        add     [InterfacesData], sizeof.usb_interface_data
; 7h. Save length left and pointer to the current interface descriptor.
        push    ecx eax
; Amuse the user if she is watching the debug board.
        DEBUGF 1,'K : USB interface class/subclass/protocol = %x/%x/%x\n',\
                [eax+usb_interface_descr.bInterfaceClass]:2,\
                [eax+usb_interface_descr.bInterfaceSubClass]:2,\
                [eax+usb_interface_descr.bInterfaceProtocol]:2
; 7i. Select the correct driver based on interface class.
; For hubs, go to 7j. Otherwise, go to 7k.
; Note: this should be rewritten as table-based lookup when more drivers will
; be available.
        cmp     byte [eax+usb_interface_descr.bInterfaceClass], 9
        jz      .found_hub
        mov     edx, usb_hid_name
        cmp     byte [eax+usb_interface_descr.bInterfaceClass], 3
        jz      .load_driver
        mov     edx, usb_print_name
        cmp     byte [eax+usb_interface_descr.bInterfaceClass], 7
        jz      .load_driver
        mov     edx, usb_stor_name
        cmp     byte [eax+usb_interface_descr.bInterfaceClass], 8
        jz      .load_driver
        mov     edx, usb_other_name
        jmp     .load_driver
.found_hub:
; 7j. Hubs are a part of USB stack, thus, integrated into the kernel.
; Use the pointer to hub callbacks and go to 7m.
        mov     eax, usb_hub_pseudosrv - USBSRV.usb_func
        jmp     .driver_loaded
.load_driver:
; 7k. Load the corresponding driver.
        push    ebx esi edi
        stdcall get_service, edx
        pop     edi esi ebx
; 7l. If failed, say something to the debug board and go to 7p.
        test    eax, eax
        jnz     .driver_loaded
        dbgstr 'failed to load class driver'
        jmp     .next_descriptor2
.driver_loaded:
; 7m. Call AddDevice function of the driver.
; Note that top of stack contains a pointer to the current interface,
; saved by step 7h.
        mov     [driver], eax
        mov     eax, [eax+USBSRV.usb_func]
        pop     edx
        push    edx
; Note: usb_hub_init assumes that edx points to usb_interface_descr,
; ecx = length rest; if you change the code, modify usb_hub_init also.
        stdcall [eax+USBFUNC.add_device], [pipe], [calldata], edx
; 7n. If failed, say something to the debug board and go to 7p.
        test    eax, eax
        jnz     .store_data
        dbgstr 'USB device initialization failed'
        jmp     .next_descriptor2
.store_data:
; 7o. Store the returned value and the driver handle to InterfacesData.
; Note that step 7g has already advanced InterfacesData.
        mov     edx, [InterfacesData]
        mov     [edx+usb_interface_data.DriverData-sizeof.usb_interface_data], eax
        mov     eax, [driver]
        mov     [edx+usb_interface_data.DriverFunc-sizeof.usb_interface_data], eax
.next_descriptor2:
; 7p. Restore registers saved in step 7h, get the descriptor length and
; continue to 7c.
        pop     eax ecx
        movzx   edx, byte [eax+usb_descr.bLength]
        jmp     .next_descriptor
endp

; Driver names, see step 7i of usb_got_config_callback.
iglobal
usb_hid_name    db      'usbhid',0
usb_stor_name   db      'usbstor',0
usb_print_name  db      'usbprint',0
usb_other_name  db      'usbother',0
endg
