forked from KolibriOS/kolibrios
USB support
git-svn-id: svn://kolibrios.org@3520 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
parent
bcdfe175d7
commit
c1284fc3b6
@ -129,6 +129,8 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
|
||||
|
@ -129,6 +129,8 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
|
||||
|
@ -130,6 +130,8 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
|
||||
|
@ -129,6 +129,8 @@ FASM_PROGRAMS:=\
|
||||
drivers/sound.obj:DRIVERS/SOUND.OBJ:$(KERNEL)/drivers/sound.asm \
|
||||
drivers/intelac97.obj:DRIVERS/INTELAC97.OBJ:$(KERNEL)/drivers/intelac97.asm \
|
||||
drivers/tmpdisk.obj:DRIVERS/TMPDISK.OBJ:$(KERNEL)/drivers/tmpdisk.asm \
|
||||
drivers/usbhid.obj:DRIVERS/USBHID.OBJ:$(KERNEL)/drivers/usbhid.asm \
|
||||
drivers/usbstor.obj:DRIVERS/USBSTOR.OBJ:$(KERNEL)/drivers/usbstor.asm \
|
||||
drivers/vt823x.obj:DRIVERS/VT823X.OBJ:$(KERNEL)/drivers/vt823x.asm \
|
||||
drivers/vidintel.obj:DRIVERS/VIDINTEL.OBJ:$(KERNEL)/drivers/vidintel.asm \
|
||||
File|Managers/kfar:File|Managers/KFAR:$(PROGS)/fs/kfar/trunk/kfar.asm \
|
||||
|
1914
kernel/trunk/bus/usb/ehci.inc
Normal file
1914
kernel/trunk/bus/usb/ehci.inc
Normal file
File diff suppressed because it is too large
Load Diff
472
kernel/trunk/bus/usb/hccommon.inc
Normal file
472
kernel/trunk/bus/usb/hccommon.inc
Normal file
@ -0,0 +1,472 @@
|
||||
; USB Host Controller support code: hardware-independent part,
|
||||
; common for all controller types.
|
||||
|
||||
; =============================================================================
|
||||
; ================================= Constants =================================
|
||||
; =============================================================================
|
||||
; USB device must have at least 100ms of stable power before initializing can
|
||||
; proceed; one timer tick is 10ms, so enforce delay in 10 ticks
|
||||
USB_CONNECT_DELAY = 10
|
||||
; USB requires at least 10 ms for reset signalling. Normally, this is one timer
|
||||
; tick. However, it is possible that we start reset signalling in the end of
|
||||
; interval between timer ticks and then we test time in the start of the next
|
||||
; interval; in this case, the delta between [timer_ticks] is 1, but the real
|
||||
; time passed is significantly less than 10 ms. To avoid this, we add an extra
|
||||
; tick; this guarantees that at least 10 ms have passed.
|
||||
USB_RESET_TIME = 2
|
||||
; USB requires at least 10 ms of reset recovery, a delay between reset
|
||||
; signalling and any commands to device. Add an extra tick for the same reasons
|
||||
; as with the previous constant.
|
||||
USB_RESET_RECOVERY_TIME = 2
|
||||
|
||||
; =============================================================================
|
||||
; ================================ Structures =================================
|
||||
; =============================================================================
|
||||
; Controller descriptor.
|
||||
; This structure represents the common (controller-independent) part
|
||||
; of a controller for the USB code. The corresponding controller-dependent
|
||||
; part *hci_controller is located immediately before usb_controller.
|
||||
struct usb_controller
|
||||
; Two following fields organize all controllers in the global linked list.
|
||||
Next dd ?
|
||||
Prev dd ?
|
||||
HardwareFunc dd ?
|
||||
; Pointer to usb_hardware_func structure with controller-specific functions.
|
||||
NumPorts dd ?
|
||||
; Number of ports in the root hub.
|
||||
SetAddressBuffer rb 8
|
||||
; Buffer for USB control command SET_ADDRESS.
|
||||
ExistingAddresses rd 128/32
|
||||
; Bitmask for 128 bits; bit i is cleared <=> address i is free for allocating
|
||||
; for new devices. Bit 0 is always set.
|
||||
;
|
||||
; The hardware is allowed to cache some data from hardware structures.
|
||||
; Regular operations are designed considering this,
|
||||
; but sometimes it is required to wait for synchronization of hardware cache
|
||||
; with modified structures in memory.
|
||||
; The code keeps two queues of pipes waiting for synchronization,
|
||||
; one for asynchronous (bulk/control) pipes, one for periodic pipes, hardware
|
||||
; cache is invalidated under different conditions for those types.
|
||||
; Both queues are organized in the same way, as single-linked lists.
|
||||
; There are three special positions: the head of list (new pipes are added
|
||||
; here), the first pipe to be synchronized at the current iteration,
|
||||
; the tail of list (all pipes starting from here are synchronized).
|
||||
WaitPipeListAsync dd ?
|
||||
WaitPipeListPeriodic dd ?
|
||||
; List heads.
|
||||
WaitPipeRequestAsync dd ?
|
||||
WaitPipeRequestPeriodic dd ?
|
||||
; Pending request to hardware to refresh cache for items from WaitPipeList*.
|
||||
; (Pointers to some items in WaitPipeList* or NULLs).
|
||||
ReadyPipeHeadAsync dd ?
|
||||
ReadyPipeHeadPeriodic dd ?
|
||||
; Items of RemovingList* which were released by hardware and are ready
|
||||
; for further processing.
|
||||
; (Pointers to some items in WaitPipeList* or NULLs).
|
||||
NewConnected dd ?
|
||||
; bit mask of recently connected ports of the root hub,
|
||||
; bit set = a device was recently connected to the corresponding port;
|
||||
; after USB_CONNECT_DELAY ticks of stable status these ports are moved to
|
||||
; PendingPorts
|
||||
NewDisconnected dd ?
|
||||
; bit mask of disconnected ports of the root hub,
|
||||
; bit set = a device in the corresponding port was disconnected,
|
||||
; disconnect processing is required.
|
||||
PendingPorts dd ?
|
||||
; bit mask of ports which are ready to be initialized
|
||||
ControlLock MUTEX ?
|
||||
; mutex which guards all operations with control queue
|
||||
BulkLock MUTEX ?
|
||||
; mutex which guards all operations with bulk queue
|
||||
PeriodicLock MUTEX ?
|
||||
; mutex which guards all operations with periodic queues
|
||||
WaitSpinlock:
|
||||
; spinlock guarding WaitPipeRequest/ReadyPipeHead (but not WaitPipeList)
|
||||
StartWaitFrame dd ?
|
||||
; USB frame number when WaitPipeRequest* was registered.
|
||||
ResettingHub dd ?
|
||||
; Pointer to usb_hub responsible for the currently resetting port, if any.
|
||||
; NULL for the root hub.
|
||||
ResettingPort db ?
|
||||
; Port that is currently resetting, 0-based.
|
||||
ResettingSpeed db ?
|
||||
; Speed of currently resetting device.
|
||||
ResettingStatus db ?
|
||||
; Status of port reset. 0 = no port is resetting, -1 = reset failed,
|
||||
; 1 = reset in progress, 2 = reset recovery in progress.
|
||||
rb 1 ; alignment
|
||||
ResetTime dd ?
|
||||
; Time when reset signalling or reset recovery has been started.
|
||||
ConnectedTime rd 16
|
||||
; Time, in timer ticks, when the port i has signalled the connect event.
|
||||
; Valid only if bit i in NewConnected is set.
|
||||
DevicesByPort rd 16
|
||||
; Pointer to usb_pipe for zero endpoint (which serves as device handle)
|
||||
; for each port.
|
||||
ends
|
||||
|
||||
; Interface-specific data. Several interfaces of one device can operate
|
||||
; independently, each is controlled by some driver and is identified by
|
||||
; some driver-specific data passed as is to the driver.
|
||||
struct usb_interface_data
|
||||
DriverData dd ?
|
||||
; Passed as is to the driver.
|
||||
DriverFunc dd ?
|
||||
; Pointer to USBSRV structure for the driver.
|
||||
ends
|
||||
|
||||
; Device-specific data.
|
||||
struct usb_device_data
|
||||
PipeListLock MUTEX
|
||||
; Lock guarding OpenedPipeList. Must be the first item of the structure,
|
||||
; the code passes pointer to usb_device_data as is to mutex_lock/unlock.
|
||||
OpenedPipeList rd 2
|
||||
; List of all opened pipes for the device.
|
||||
; Used when the device is disconnected, so all pipes should be closed.
|
||||
ClosedPipeList rd 2
|
||||
; List of all closed, but still valid pipes for the device.
|
||||
; A pipe closed with USBClosePipe is just deallocated,
|
||||
; but a pipe closed due to disconnect must remain valid until driver-provided
|
||||
; disconnect handler returns; this list links all such pipes to deallocate them
|
||||
; after disconnect processing.
|
||||
NumPipes dd ?
|
||||
; Number of not-yet-closed pipes.
|
||||
Hub dd ?
|
||||
; NULL if connected to the root hub, pointer to usb_hub otherwise.
|
||||
Port db ?
|
||||
; Port on the hub, zero-based.
|
||||
DeviceDescrSize db ?
|
||||
; Size of device descriptor.
|
||||
NumInterfaces db ?
|
||||
; Number of interfaces.
|
||||
Speed db ?
|
||||
; Device speed, one of USB_SPEED_*.
|
||||
ConfigDataSize dd ?
|
||||
; Total size of data associated with the configuration descriptor
|
||||
; (including the configuration descriptor itself);
|
||||
Interfaces dd ?
|
||||
; Offset from the beginning of this structure to Interfaces field.
|
||||
; Variable-length fields:
|
||||
; DeviceDescriptor:
|
||||
; device descriptor starts here
|
||||
; ConfigDescriptor = DeviceDescriptor + DeviceDescrSize
|
||||
; configuration descriptor with all associated data
|
||||
; Interfaces = ALIGN_UP(ConfigDescriptor + ConfigDataSize, 4)
|
||||
; array of NumInterfaces elements of type usb_interface_data
|
||||
ends
|
||||
|
||||
usb_device_data.DeviceDescriptor = sizeof.usb_device_data
|
||||
|
||||
; Description of controller-specific data and functions.
|
||||
struct usb_hardware_func
|
||||
ID dd ? ; '*HCI'
|
||||
DataSize dd ? ; sizeof(*hci_controller)
|
||||
Init dd ?
|
||||
; Initialize controller-specific part of controller data.
|
||||
; in: eax -> *hci_controller to initialize, [ebp-4] = (bus shl 8) + devfn
|
||||
; out: eax = 0 <=> failed, otherwise eax -> usb_controller
|
||||
ProcessDeferred dd ?
|
||||
; Called regularly from the main loop of USB thread
|
||||
; (either due to timeout from a previous call, or due to explicit wakeup).
|
||||
; in: esi -> usb_controller
|
||||
; out: eax = maximum timeout for next call (-1 = infinity)
|
||||
SetDeviceAddress dd ?
|
||||
; in: esi -> usb_controller, ebx -> usb_pipe, cl = address
|
||||
GetDeviceAddress dd ?
|
||||
; in: esi -> usb_controller, ebx -> usb_pipe
|
||||
; out: eax = address
|
||||
PortDisable dd ?
|
||||
; Disable the given port in the root hub.
|
||||
; in: esi -> usb_controller, ecx = port (zero-based)
|
||||
InitiateReset dd ?
|
||||
; Start reset signalling on the given port.
|
||||
; in: esi -> usb_controller, ecx = port (zero-based)
|
||||
SetEndpointPacketSize dd ?
|
||||
; in: esi -> usb_controller, ebx -> usb_pipe, ecx = packet size
|
||||
AllocPipe dd ?
|
||||
; out: eax = pointer to allocated usb_pipe
|
||||
FreePipe dd ?
|
||||
; void stdcall with one argument = pointer to previously allocated usb_pipe
|
||||
InitPipe dd ?
|
||||
; 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
|
||||
UnlinkPipe dd ?
|
||||
; esi -> usb_controller, ebx -> usb_pipe
|
||||
AllocTD dd ?
|
||||
; out: eax = pointer to allocated usb_gtd
|
||||
FreeTD dd ?
|
||||
; void stdcall with one argument = pointer to previously allocated usb_gtd
|
||||
AllocTransfer dd ?
|
||||
; Allocate and initialize one stage of a transfer.
|
||||
; ebx -> usb_pipe, other parameters are passed through the stack:
|
||||
; buffer,size = data to transfer
|
||||
; flags = same as in usb_open_pipe:
|
||||
; bit 0 = allow short transfer, other bits reserved
|
||||
; td = pointer to the current end-of-queue descriptor
|
||||
; direction =
|
||||
; 0000b for normal transfers,
|
||||
; 1000b for control SETUP transfer,
|
||||
; 1101b for control OUT transfer,
|
||||
; 1110b for control IN transfer
|
||||
; returns eax = pointer to the new end-of-queue descriptor
|
||||
; (not included in the queue itself) or 0 on error
|
||||
InsertTransfer dd ?
|
||||
; Activate previously initialized transfer (maybe with multiple stages).
|
||||
; esi -> usb_controller, ebx -> usb_pipe,
|
||||
; [esp+4] -> first usb_gtd for the transfer,
|
||||
; ecx -> last descriptor for the transfer
|
||||
NewDevice dd ?
|
||||
; Initiate configuration of a new device (create pseudo-pipe describing that
|
||||
; device and call usb_new_device).
|
||||
; esi -> usb_controller, eax = speed (one of USB_SPEED_* constants).
|
||||
ends
|
||||
|
||||
; =============================================================================
|
||||
; =================================== Code ====================================
|
||||
; =============================================================================
|
||||
|
||||
; Initializes one controller, called by usb_init for every controller.
|
||||
; edi -> usb_hardware_func, eax -> PCIDEV structure for the device.
|
||||
proc usb_init_controller
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
; 1. Store in the stack PCI coordinates and save pointer to PCIDEV:
|
||||
; make [ebp-4] = (bus shl 8) + devfn, used by controller-specific Init funcs.
|
||||
push dword [eax+PCIDEV.devfn]
|
||||
push eax
|
||||
; 2. Allocate *hci_controller + usb_controller.
|
||||
mov ebx, [edi+usb_hardware_func.DataSize]
|
||||
add ebx, sizeof.usb_controller
|
||||
stdcall kernel_alloc, ebx
|
||||
test eax, eax
|
||||
jz .nothing
|
||||
; 3. Zero-initialize both structures.
|
||||
push edi eax
|
||||
mov ecx, ebx
|
||||
shr ecx, 2
|
||||
xchg edi, eax
|
||||
xor eax, eax
|
||||
rep stosd
|
||||
; 4. Initialize usb_controller structure,
|
||||
; except data known only to controller-specific code (like NumPorts)
|
||||
; and link fields
|
||||
; (this structure will be inserted to the overall list at step 6).
|
||||
dec eax
|
||||
mov [edi+usb_controller.ExistingAddresses+4-sizeof.usb_controller], eax
|
||||
mov [edi+usb_controller.ExistingAddresses+8-sizeof.usb_controller], eax
|
||||
mov [edi+usb_controller.ExistingAddresses+12-sizeof.usb_controller], eax
|
||||
mov [edi+usb_controller.ResettingPort-sizeof.usb_controller], al ; no resetting port
|
||||
dec eax ; don't allocate zero address
|
||||
mov [edi+usb_controller.ExistingAddresses-sizeof.usb_controller], eax
|
||||
lea ecx, [edi+usb_controller.PeriodicLock-sizeof.usb_controller]
|
||||
call mutex_init
|
||||
add ecx, usb_controller.ControlLock - usb_controller.PeriodicLock
|
||||
call mutex_init
|
||||
add ecx, usb_controller.BulkLock - usb_controller.ControlLock
|
||||
call mutex_init
|
||||
pop eax edi
|
||||
mov [eax+ebx-sizeof.usb_controller+usb_controller.HardwareFunc], edi
|
||||
push eax
|
||||
; 5. Call controller-specific initialization.
|
||||
; If failed, free memory allocated in step 2 and return.
|
||||
call [edi+usb_hardware_func.Init]
|
||||
test eax, eax
|
||||
jz .fail
|
||||
pop ecx
|
||||
; 6. Insert the controller to the global list.
|
||||
xchg eax, ebx
|
||||
mov ecx, usb_controllers_list_mutex
|
||||
call mutex_lock
|
||||
mov edx, usb_controllers_list
|
||||
mov eax, [edx+usb_controller.Prev]
|
||||
mov [ebx+usb_controller.Next], edx
|
||||
mov [ebx+usb_controller.Prev], eax
|
||||
mov [edx+usb_controller.Prev], ebx
|
||||
mov [eax+usb_controller.Next], ebx
|
||||
call mutex_unlock
|
||||
; 7. Wakeup USB thread to call ProcessDeferred.
|
||||
call usb_wakeup
|
||||
.nothing:
|
||||
; 8. Restore pointer to PCIDEV saved in step 1 and return.
|
||||
pop eax
|
||||
leave
|
||||
ret
|
||||
.fail:
|
||||
call kernel_free
|
||||
jmp .nothing
|
||||
endp
|
||||
|
||||
; Helper function, calculates physical address including offset in page.
|
||||
proc get_phys_addr
|
||||
push ecx
|
||||
mov ecx, eax
|
||||
and ecx, 0xFFF
|
||||
call get_pg_addr
|
||||
add eax, ecx
|
||||
pop ecx
|
||||
ret
|
||||
endp
|
||||
|
||||
; Put the given control pipe in the wait list;
|
||||
; called when the pipe structure is changed and a possible hardware cache
|
||||
; needs to be synchronized. When it will be known that the cache is updated,
|
||||
; usb_subscription_done procedure will be called.
|
||||
proc usb_subscribe_control
|
||||
cmp [ebx+usb_pipe.NextWait], -1
|
||||
jnz @f
|
||||
mov eax, [esi+usb_controller.WaitPipeListAsync]
|
||||
mov [ebx+usb_pipe.NextWait], eax
|
||||
mov [esi+usb_controller.WaitPipeListAsync], ebx
|
||||
@@:
|
||||
ret
|
||||
endp
|
||||
|
||||
; Called after synchronization of hardware cache with software changes.
|
||||
; Continues process of device enumeration based on when it was delayed
|
||||
; due to call to usb_subscribe_control.
|
||||
proc usb_subscription_done
|
||||
mov eax, [ebx+usb_pipe.DeviceData]
|
||||
cmp [eax+usb_device_data.DeviceDescrSize], 0
|
||||
jz usb_after_set_address
|
||||
jmp usb_after_set_endpoint_size
|
||||
endp
|
||||
|
||||
; This function is called when a new device has either passed
|
||||
; or failed first stages of configuration, so the next device
|
||||
; can enter configuration process.
|
||||
proc usb_test_pending_port
|
||||
mov [esi+usb_controller.ResettingPort], -1
|
||||
cmp [esi+usb_controller.PendingPorts], 0
|
||||
jz .nothing
|
||||
bsf ecx, [esi+usb_controller.PendingPorts]
|
||||
btr [esi+usb_controller.PendingPorts], ecx
|
||||
mov eax, [esi+usb_controller.HardwareFunc]
|
||||
jmp [eax+usb_hardware_func.InitiateReset]
|
||||
.nothing:
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is regularly called from controller-specific ProcessDeferred,
|
||||
; it checks whether there are disconnected events and if so, process them.
|
||||
proc usb_disconnect_stage2
|
||||
bsf ecx, [esi+usb_controller.NewDisconnected]
|
||||
jz .nothing
|
||||
lock btr [esi+usb_controller.NewDisconnected], ecx
|
||||
btr [esi+usb_controller.PendingPorts], ecx
|
||||
xor ebx, ebx
|
||||
xchg ebx, [esi+usb_controller.DevicesByPort+ecx*4]
|
||||
test ebx, ebx
|
||||
jz usb_disconnect_stage2
|
||||
call usb_device_disconnected
|
||||
jmp usb_disconnect_stage2
|
||||
.nothing:
|
||||
ret
|
||||
endp
|
||||
|
||||
; Initial stage of disconnect processing: called when device is disconnected.
|
||||
proc usb_device_disconnected
|
||||
; Loop over all pipes, close everything, wait until hardware reacts.
|
||||
; The final handling is done in usb_pipe_closed.
|
||||
push ebx
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
call mutex_lock
|
||||
lea eax, [ecx+usb_device_data.OpenedPipeList-usb_pipe.NextSibling]
|
||||
push eax
|
||||
mov ebx, [eax+usb_pipe.NextSibling]
|
||||
.pipe_loop:
|
||||
call usb_close_pipe_nolock
|
||||
mov ebx, [ebx+usb_pipe.NextSibling]
|
||||
cmp ebx, [esp]
|
||||
jnz .pipe_loop
|
||||
pop eax
|
||||
pop ebx
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
call mutex_unlock
|
||||
ret
|
||||
endp
|
||||
|
||||
; Called from controller-specific ProcessDeferred,
|
||||
; processes wait-pipe-done notifications,
|
||||
; returns whether there are more items in wait queues.
|
||||
; in: esi -> usb_controller
|
||||
; out: eax = bitmask of pipe types with non-empty wait queue
|
||||
proc usb_process_wait_lists
|
||||
xor edx, edx
|
||||
push edx
|
||||
call usb_process_one_wait_list
|
||||
jnc @f
|
||||
or byte [esp], 1 shl CONTROL_PIPE
|
||||
@@:
|
||||
push 4
|
||||
pop edx
|
||||
call usb_process_one_wait_list
|
||||
jnc @f
|
||||
or byte [esp], 1 shl INTERRUPT_PIPE
|
||||
@@:
|
||||
xor edx, edx
|
||||
call usb_process_one_wait_list
|
||||
jnc @f
|
||||
or byte [esp], 1 shl CONTROL_PIPE
|
||||
@@:
|
||||
pop eax
|
||||
ret
|
||||
endp
|
||||
|
||||
; Helper procedure for usb_process_wait_lists;
|
||||
; does the same for one wait queue.
|
||||
; in: esi -> usb_controller,
|
||||
; edx=0 for *Async, edx=4 for *Periodic list
|
||||
; out: CF = issue new request
|
||||
proc usb_process_one_wait_list
|
||||
; 1. Check whether there is a pending request. If so, do nothing.
|
||||
mov ebx, [esi+usb_controller.WaitPipeRequestAsync+edx]
|
||||
cmp ebx, [esi+usb_controller.ReadyPipeHeadAsync+edx]
|
||||
clc
|
||||
jnz .nothing
|
||||
; 2. Check whether there are new data. If so, issue a new request.
|
||||
cmp ebx, [esi+usb_controller.WaitPipeListAsync+edx]
|
||||
stc
|
||||
jnz .nothing
|
||||
test ebx, ebx
|
||||
jz .nothing
|
||||
; 3. Clear all lists.
|
||||
xor ecx, ecx
|
||||
mov [esi+usb_controller.WaitPipeListAsync+edx], ecx
|
||||
mov [esi+usb_controller.WaitPipeRequestAsync+edx], ecx
|
||||
mov [esi+usb_controller.ReadyPipeHeadAsync+edx], ecx
|
||||
; 4. Loop over all pipes from the wait list.
|
||||
.pipe_loop:
|
||||
; For every pipe:
|
||||
; 5. Save edx and next pipe in the list.
|
||||
push edx
|
||||
push [ebx+usb_pipe.NextWait]
|
||||
; 6. If USB_FLAG_EXTRA_WAIT is set, reinsert the pipe to the list and continue.
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT
|
||||
jz .process
|
||||
mov eax, [esi+usb_controller.WaitPipeListAsync+edx]
|
||||
mov [ebx+usb_pipe.NextWait], eax
|
||||
mov [esi+usb_controller.WaitPipeListAsync+edx], ebx
|
||||
jmp .continue
|
||||
.process:
|
||||
; 7. Call the handler depending on USB_FLAG_CLOSED.
|
||||
or [ebx+usb_pipe.NextWait], -1
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED
|
||||
jz .nodisconnect
|
||||
call usb_pipe_closed
|
||||
jmp .continue
|
||||
.nodisconnect:
|
||||
call usb_subscription_done
|
||||
.continue:
|
||||
; 8. Restore edx and next pipe saved in step 5 and continue the loop.
|
||||
pop ebx
|
||||
pop edx
|
||||
test ebx, ebx
|
||||
jnz .pipe_loop
|
||||
.check_new_work:
|
||||
; 9. Set CF depending on whether WaitPipeList* is nonzero.
|
||||
cmp [esi+usb_controller.WaitPipeListAsync+edx], 1
|
||||
cmc
|
||||
.nothing:
|
||||
ret
|
||||
endp
|
1237
kernel/trunk/bus/usb/hub.inc
Normal file
1237
kernel/trunk/bus/usb/hub.inc
Normal file
File diff suppressed because it is too large
Load Diff
250
kernel/trunk/bus/usb/init.inc
Normal file
250
kernel/trunk/bus/usb/init.inc
Normal file
@ -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"
|
215
kernel/trunk/bus/usb/memory.inc
Normal file
215
kernel/trunk/bus/usb/memory.inc
Normal file
@ -0,0 +1,215 @@
|
||||
; Memory management for USB structures.
|
||||
; Protocol layer uses the common kernel heap malloc/free.
|
||||
; Hardware layer has special requirements:
|
||||
; * memory blocks should be properly aligned
|
||||
; * memory blocks should not cross page boundary
|
||||
; Hardware layer allocates fixed-size blocks.
|
||||
; Thus, the specific allocator is quite easy to write:
|
||||
; allocate one page, split into blocks, maintain the single-linked
|
||||
; list of all free blocks in each page.
|
||||
|
||||
; Note: size must be a multiple of required alignment.
|
||||
|
||||
; Data for one pool: dd pointer to the first page, MUTEX lock.
|
||||
|
||||
uglobal
|
||||
; Structures in UHCI and OHCI have equal sizes.
|
||||
; Thus, functions and data for allocating/freeing can be shared;
|
||||
; we keep them here rather than in controller-specific files.
|
||||
align 4
|
||||
; Data for UHCI and OHCI endpoints pool.
|
||||
usb1_ep_first_page dd ?
|
||||
usb1_ep_mutex MUTEX
|
||||
; Data for UHCI and OHCI general transfer descriptors pool.
|
||||
usb_gtd_first_page dd ?
|
||||
usb_gtd_mutex MUTEX
|
||||
endg
|
||||
|
||||
; sanity check: structures in UHCI and OHCI should be the same for allocation
|
||||
if (sizeof.ohci_pipe=sizeof.uhci_pipe)&(ohci_pipe.SoftwarePart=uhci_pipe.SoftwarePart)
|
||||
|
||||
; Allocates one endpoint structure for UHCI/OHCI.
|
||||
; Returns pointer to software part (usb_pipe) in eax.
|
||||
proc usb1_allocate_endpoint
|
||||
push ebx
|
||||
mov ebx, usb1_ep_mutex
|
||||
stdcall usb_allocate_common, sizeof.ohci_pipe
|
||||
test eax, eax
|
||||
jz @f
|
||||
add eax, ohci_pipe.SoftwarePart
|
||||
@@:
|
||||
pop ebx
|
||||
ret
|
||||
endp
|
||||
|
||||
; Free one endpoint structure for UHCI/OHCI.
|
||||
; Stdcall with one argument, pointer to software part (usb_pipe).
|
||||
proc usb1_free_endpoint
|
||||
sub dword [esp+4], ohci_pipe.SoftwarePart
|
||||
jmp usb_free_common
|
||||
endp
|
||||
|
||||
else
|
||||
; sanity check continued
|
||||
.err allocate_endpoint/free_endpoint must be different for OHCI and UHCI
|
||||
end if
|
||||
|
||||
; sanity check: structures in UHCI and OHCI should be the same for allocation
|
||||
if (sizeof.ohci_gtd=sizeof.uhci_gtd)&(ohci_gtd.SoftwarePart=uhci_gtd.SoftwarePart)
|
||||
|
||||
; Allocates one general transfer descriptor structure for UHCI/OHCI.
|
||||
; Returns pointer to software part (usb_gtd) in eax.
|
||||
proc usb1_allocate_general_td
|
||||
push ebx
|
||||
mov ebx, usb_gtd_mutex
|
||||
stdcall usb_allocate_common, sizeof.ohci_gtd
|
||||
test eax, eax
|
||||
jz @f
|
||||
add eax, ohci_gtd.SoftwarePart
|
||||
@@:
|
||||
pop ebx
|
||||
ret
|
||||
endp
|
||||
|
||||
; Free one general transfer descriptor structure for UHCI/OHCI.
|
||||
; Stdcall with one argument, pointer to software part (usb_gtd).
|
||||
proc usb1_free_general_td
|
||||
sub dword [esp+4], ohci_gtd.SoftwarePart
|
||||
jmp usb_free_common
|
||||
endp
|
||||
|
||||
else
|
||||
; sanity check continued
|
||||
.err allocate_general_td/free_general_td must be different for OHCI and UHCI
|
||||
end if
|
||||
|
||||
; Allocator for fixed-size blocks: allocate a block.
|
||||
; [ebx-4] = pointer to the first page, ebx = pointer to MUTEX structure.
|
||||
proc usb_allocate_common
|
||||
push edi ; save used register to be stdcall
|
||||
virtual at esp
|
||||
dd ? ; saved edi
|
||||
dd ? ; return address
|
||||
.size dd ?
|
||||
end virtual
|
||||
; 1. Take the lock.
|
||||
mov ecx, ebx
|
||||
call mutex_lock
|
||||
; 2. Find the first allocated page with a free block, if any.
|
||||
; 2a. Initialize for the loop.
|
||||
mov edx, ebx
|
||||
.pageloop:
|
||||
; 2b. Get the next page, keeping the current in eax.
|
||||
mov eax, edx
|
||||
mov edx, [edx-4]
|
||||
; 2c. If there is no next page, we're out of luck; go to 4.
|
||||
test edx, edx
|
||||
jz .newpage
|
||||
add edx, 0x1000
|
||||
@@:
|
||||
; 2d. Get the pointer to the first free block on this page.
|
||||
; If there is no free block, continue to 2b.
|
||||
mov eax, [edx-8]
|
||||
test eax, eax
|
||||
jz .pageloop
|
||||
; 2e. Get the pointer to the next free block.
|
||||
mov ecx, [eax]
|
||||
; 2f. Update the pointer to the first free block from eax to ecx.
|
||||
; Normally [edx-8] still contains eax, if so, atomically set it to ecx
|
||||
; and proceed to 3.
|
||||
; However, the price of simplicity of usb_free_common (in particular, it
|
||||
; doesn't take the lock) is that [edx-8] could (rarely) be changed while
|
||||
; we processed steps 2d+2e. If so, return to 2d and retry.
|
||||
lock cmpxchg [edx-8], ecx
|
||||
jnz @b
|
||||
.return:
|
||||
; 3. Release the lock taken in step 1 and return.
|
||||
push eax
|
||||
mov ecx, ebx
|
||||
call mutex_unlock
|
||||
pop eax
|
||||
pop edi ; restore used register to be stdcall
|
||||
ret 4
|
||||
.newpage:
|
||||
; 4. Allocate a new page.
|
||||
push eax
|
||||
stdcall kernel_alloc, 0x1000
|
||||
pop edx
|
||||
; If failed, say something to the debug board and return zero.
|
||||
test eax, eax
|
||||
jz .nomemory
|
||||
; 5. Add the new page to the tail of list of allocated pages.
|
||||
mov [edx-4], eax
|
||||
; 6. Initialize two service dwords in the end of page:
|
||||
; first free block is (start of page) + (block size)
|
||||
; (we will return first block at (start of page), so consider it allocated),
|
||||
; no next page.
|
||||
mov edx, eax
|
||||
lea edi, [eax+0x1000-8]
|
||||
add edx, [.size]
|
||||
mov [edi], edx
|
||||
and dword [edi+4], 0
|
||||
; 7. All blocks starting from edx are free; join them in a single-linked list.
|
||||
@@:
|
||||
mov ecx, edx
|
||||
add edx, [.size]
|
||||
mov [ecx], edx
|
||||
cmp edx, edi
|
||||
jbe @b
|
||||
sub ecx, [.size]
|
||||
and dword [ecx], 0
|
||||
; 8. Return (start of page).
|
||||
jmp .return
|
||||
.nomemory:
|
||||
dbgstr 'no memory for USB descriptor'
|
||||
xor eax, eax
|
||||
jmp .return
|
||||
endp
|
||||
|
||||
; Allocator for fixed-size blocks: free a block.
|
||||
proc usb_free_common
|
||||
push ecx edx
|
||||
virtual at esp
|
||||
rd 2 ; saved registers
|
||||
dd ? ; return address
|
||||
.block dd ?
|
||||
end virtual
|
||||
; Insert the given block to the head of free blocks in this page.
|
||||
mov ecx, [.block]
|
||||
mov edx, ecx
|
||||
or edx, 0xFFF
|
||||
@@:
|
||||
mov eax, [edx+1-8]
|
||||
mov [ecx], eax
|
||||
lock cmpxchg [edx+1-8], ecx
|
||||
jnz @b
|
||||
pop edx ecx
|
||||
ret 4
|
||||
endp
|
||||
|
||||
; Helper procedure for OHCI: translate physical address in ecx
|
||||
; of some transfer descriptor to linear address.
|
||||
proc usb_td_to_virt
|
||||
; Traverse all pages used for transfer descriptors, looking for the one
|
||||
; with physical address as in ecx.
|
||||
mov eax, [usb_gtd_first_page]
|
||||
@@:
|
||||
test eax, eax
|
||||
jz .zero
|
||||
push eax
|
||||
call get_pg_addr
|
||||
sub eax, ecx
|
||||
jz .found
|
||||
cmp eax, -0x1000
|
||||
ja .found
|
||||
pop eax
|
||||
mov eax, [eax+0x1000-4]
|
||||
jmp @b
|
||||
.found:
|
||||
; When found, combine page address from eax with page offset from ecx.
|
||||
pop eax
|
||||
and ecx, 0xFFF
|
||||
add eax, ecx
|
||||
.zero:
|
||||
ret
|
||||
endp
|
1601
kernel/trunk/bus/usb/ohci.inc
Normal file
1601
kernel/trunk/bus/usb/ohci.inc
Normal file
File diff suppressed because it is too large
Load Diff
813
kernel/trunk/bus/usb/pipe.inc
Normal file
813
kernel/trunk/bus/usb/pipe.inc
Normal file
@ -0,0 +1,813 @@
|
||||
; Functions for USB pipe manipulation: opening/closing, sending data etc.
|
||||
;
|
||||
; =============================================================================
|
||||
; ================================= Constants =================================
|
||||
; =============================================================================
|
||||
; USB pipe types
|
||||
CONTROL_PIPE = 0
|
||||
ISOCHRONOUS_PIPE = 1
|
||||
BULK_PIPE = 2
|
||||
INTERRUPT_PIPE = 3
|
||||
|
||||
; Status codes for transfer callbacks.
|
||||
; Taken from OHCI as most verbose controller in this sense.
|
||||
USB_STATUS_OK = 0 ; no error
|
||||
USB_STATUS_CRC = 1 ; CRC error
|
||||
USB_STATUS_BITSTUFF = 2 ; bit stuffing violation
|
||||
USB_STATUS_TOGGLE = 3 ; data toggle mismatch
|
||||
USB_STATUS_STALL = 4 ; device returned STALL
|
||||
USB_STATUS_NORESPONSE = 5 ; device not responding
|
||||
USB_STATUS_PIDCHECK = 6 ; invalid PID check bits
|
||||
USB_STATUS_WRONGPID = 7 ; unexpected PID value
|
||||
USB_STATUS_OVERRUN = 8 ; too many data from endpoint
|
||||
USB_STATUS_UNDERRUN = 9 ; too few data from endpoint
|
||||
USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer
|
||||
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer
|
||||
USB_STATUS_CLOSED = 16 ; pipe closed
|
||||
; either explicitly with USBClosePipe
|
||||
; or implicitly due to device disconnect
|
||||
|
||||
; flags for usb_pipe.Flags
|
||||
USB_FLAG_CLOSED = 1 ; pipe is closed, no new transfers
|
||||
; pipe is closed, return error instead of submitting any new transfer
|
||||
USB_FLAG_CAN_FREE = 2
|
||||
; pipe is closed via explicit call to USBClosePipe, so it can be freed without
|
||||
; any driver notification; if this flag is not set, then the pipe is closed due
|
||||
; to device disconnect, so it must remain valid until return from disconnect
|
||||
; callback provided by the driver
|
||||
USB_FLAG_EXTRA_WAIT = 4
|
||||
; The pipe was in wait list, while another event occured;
|
||||
; when the first wait will be done, reinsert the pipe to wait list
|
||||
USB_FLAG_CLOSED_BIT = 0 ; USB_FLAG_CLOSED = 1 shl USB_FLAG_CLOSED_BIT
|
||||
|
||||
; =============================================================================
|
||||
; ================================ Structures =================================
|
||||
; =============================================================================
|
||||
|
||||
; Pipe descriptor.
|
||||
; * An USB pipe is described by two structures, for hardware and for software.
|
||||
; * This is the software part. The hardware part is defined in a driver
|
||||
; of the corresponding controller.
|
||||
; * The hardware part is located immediately before usb_pipe,
|
||||
; both are allocated at once by controller-specific code
|
||||
; (it knows the total length, which depends on the hardware part).
|
||||
struct usb_pipe
|
||||
Controller dd ?
|
||||
; Pointer to usb_controller structure corresponding to this pipe.
|
||||
; Must be the first dword after hardware part, see *hci_new_device.
|
||||
;
|
||||
; Every endpoint is included into one of processing lists:
|
||||
; * Bulk list contains all Bulk endpoints.
|
||||
; * Control list contains all Control endpoints.
|
||||
; * Several Periodic lists serve Interrupt endpoints with different interval.
|
||||
; - There are N=2^n "leaf" periodic lists for N ms interval, one is processed
|
||||
; in the frames 0,N,2N,..., another is processed in the frames
|
||||
; 1,1+N,1+2N,... and so on. The hardware starts processing of periodic
|
||||
; endpoints in every frame from the list identified by lower n bits of the
|
||||
; frame number; the addresses of these N lists are written to the
|
||||
; controller data area during the initialization.
|
||||
; - We assume that n=5, N=32 to simplify the code and compact the data.
|
||||
; OHCI works in this way. UHCI and EHCI actually have n=10, N=1024,
|
||||
; but this is an overkill for interrupt endpoints; the large value of N is
|
||||
; useful only for isochronous transfers in UHCI and EHCI. UHCI/EHCI code
|
||||
; initializes "leaf" lists k,k+32,k+64,...,k+(1024-32) to the same value,
|
||||
; giving essentially N=32.
|
||||
; This restriction means that the actual maximum interval of polling any
|
||||
; interrupt endpoint is 32ms, which seems to be a reasonable value.
|
||||
; - Similarly, there are 16 lists for 16-ms interval, 8 lists for 8-ms
|
||||
; interval and so on. Finally, there is one list for 1ms interval. Their
|
||||
; addresses are not directly known to the controller.
|
||||
; - The hardware serves endpoints following a physical link from the hardware
|
||||
; part.
|
||||
; - The hardware links are organized as follows. If the list item is not the
|
||||
; last, it's hardware link points to the next item. The hardware link of
|
||||
; the last item points to the first item of the "next" list.
|
||||
; - The "next" list for k-th and (k+M)-th periodic lists for interval 2M ms
|
||||
; is the k-th periodic list for interval M ms, M >= 1. In this scheme,
|
||||
; if two "previous" lists are served in the frames k,k+2M,k+4M,...
|
||||
; and k+M,k+3M,k+5M,... correspondingly, the "next" list is served in
|
||||
; the frames k,k+M,k+2M,k+3M,k+4M,k+5M,..., which is exactly what we want.
|
||||
; - The links between Periodic, Control, Bulk lists and the processing of
|
||||
; Isochronous endpoints are controller-specific.
|
||||
; * The head of every processing list is a static entry which does not
|
||||
; correspond to any real pipe. It is described by usb_static_ep
|
||||
; structure, not usb_pipe. For OHCI and UHCI, sizeof.usb_static_ep plus
|
||||
; sizeof hardware part is 20h, the total number of lists is
|
||||
; 32+16+8+4+2+1+1+1 = 65, so all these structures fit in one page,
|
||||
; leaving space for other data. This is another reason for 32ms limit.
|
||||
; * Static endpoint descriptors are kept in *hci_controller structure.
|
||||
; * All items in every processing list, including the static head, are
|
||||
; organized in a double-linked list using .NextVirt and .PrevVirt fields.
|
||||
; * [[item.NextVirt].PrevVirt] = [[item.PrevVirt].NextVirt] for all items.
|
||||
NextVirt dd ?
|
||||
; Next endpoint in the processing list.
|
||||
; See also PrevVirt field and the description before NextVirt field.
|
||||
PrevVirt dd ?
|
||||
; Previous endpoint in the processing list.
|
||||
; See also NextVirt field and the description before NextVirt field.
|
||||
;
|
||||
; Every pipe has the associated transfer queue, that is, the double-linked
|
||||
; list of Transfer Descriptors aka TD. For Control, Bulk and Interrupt
|
||||
; endpoints this list consists of usb_gtd structures
|
||||
; (GTD = General Transfer Descriptors), for Isochronous endpoints
|
||||
; this list consists of usb_itd structures, which are not developed yet.
|
||||
; The pipe needs to know only the last TD; the first TD can be
|
||||
; obtained as [[pipe.LastTD].NextVirt].
|
||||
LastTD dd ?
|
||||
; Last TD in the transfer queue.
|
||||
;
|
||||
; All opened pipes corresponding to the same physical device are organized in
|
||||
; the double-linked list using .NextSibling and .PrevSibling fields.
|
||||
; The head of this list is kept in usb_device_data structure (OpenedPipeList).
|
||||
; This list is used when the device is disconnected and all pipes for the
|
||||
; device should be closed.
|
||||
; Also, all pipes closed due to disconnect must remain valid at least until
|
||||
; driver-provided disconnect function returns; all should-be-freed-but-not-now
|
||||
; pipes for one device are organized in another double-linked list with
|
||||
; the head in usb_device_data.ClosedPipeList; this list uses the same link
|
||||
; fields, one pipe can never be in both lists.
|
||||
NextSibling dd ?
|
||||
; Next pipe for the physical device.
|
||||
PrevSibling dd ?
|
||||
; Previous pipe for the physical device.
|
||||
;
|
||||
; When hardware part of pipe is changed, some time is needed before further
|
||||
; actions so that hardware reacts on this change. During that time,
|
||||
; all changed pipes are organized in single-linked list with the head
|
||||
; usb_controller.WaitPipeList* and link field NextWait.
|
||||
; Currently there are two possible reasons to change:
|
||||
; change of address/packet size in initial configuration,
|
||||
; close of the pipe. They are distinguished by USB_FLAG_CLOSED.
|
||||
NextWait dd ?
|
||||
Lock MUTEX
|
||||
; Mutex that guards operations with transfer queue for this pipe.
|
||||
Type db ?
|
||||
; Type of pipe, one of {CONTROL,ISOCHRONOUS,BULK,INTERRUPT}_PIPE.
|
||||
Flags db ?
|
||||
; Combination of flags, USB_FLAG_*.
|
||||
rb 2 ; dword alignment
|
||||
DeviceData dd ?
|
||||
; Pointer to usb_device_data, common for all pipes for one device.
|
||||
ends
|
||||
|
||||
; This structure describes the static head of every list of pipes.
|
||||
struct usb_static_ep
|
||||
; software fields
|
||||
Bandwidth dd ?
|
||||
; valid only for interrupt/isochronous USB1 lists
|
||||
; The offsets of the following two fields must be the same in this structure
|
||||
; and in usb_pipe.
|
||||
NextVirt dd ?
|
||||
PrevVirt dd ?
|
||||
ends
|
||||
|
||||
; This structure represents one transfer descriptor
|
||||
; ('g' stands for "general" as opposed to isochronous usb_itd).
|
||||
; Note that one transfer can have several descriptors:
|
||||
; a control transfer has three stages.
|
||||
; Additionally, every controller has a limit on transfer length with
|
||||
; one descriptor (packet size for UHCI, 1K for OHCI, 4K for EHCI),
|
||||
; large transfers must be split into individual packets according to that limit.
|
||||
struct usb_gtd
|
||||
Callback dd ?
|
||||
; Zero for intermediate descriptors, pointer to callback function
|
||||
; for final descriptor. See the docs for description of the callback.
|
||||
UserData dd ?
|
||||
; Dword which is passed to Callback as is, not used by USB code itself.
|
||||
; Two following fields organize all descriptors for one pipe in
|
||||
; the linked list.
|
||||
NextVirt dd ?
|
||||
PrevVirt dd ?
|
||||
Pipe dd ?
|
||||
; Pointer to the parent usb_pipe.
|
||||
Buffer dd ?
|
||||
; Pointer to data for this descriptor.
|
||||
Length dd ?
|
||||
; Length of data for this descriptor.
|
||||
ends
|
||||
|
||||
; =============================================================================
|
||||
; =================================== Code ====================================
|
||||
; =============================================================================
|
||||
|
||||
USB_STDCALL_VERIFY = 1
|
||||
macro stdcall_verify [arg]
|
||||
{
|
||||
common
|
||||
if USB_STDCALL_VERIFY
|
||||
pushad
|
||||
stdcall arg
|
||||
call verify_regs
|
||||
popad
|
||||
else
|
||||
stdcall arg
|
||||
end if
|
||||
}
|
||||
|
||||
; Initialization of usb_static_ep structure,
|
||||
; called from controller-specific initialization; edi -> usb_static_ep
|
||||
proc usb_init_static_endpoint
|
||||
mov [edi+usb_static_ep.NextVirt], edi
|
||||
mov [edi+usb_static_ep.PrevVirt], edi
|
||||
ret
|
||||
endp
|
||||
|
||||
; Part of API for drivers, see documentation for USBOpenPipe.
|
||||
proc usb_open_pipe stdcall uses ebx esi edi,\
|
||||
config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword
|
||||
locals
|
||||
targetsmask dd ? ; S-Mask for USB2
|
||||
bandwidth dd ?
|
||||
target dd ?
|
||||
endl
|
||||
; 1. Verify type of pipe: it must be one of *_PIPE constants.
|
||||
; Isochronous pipes are not supported yet.
|
||||
mov eax, [type]
|
||||
cmp eax, INTERRUPT_PIPE
|
||||
ja .badtype
|
||||
cmp al, ISOCHRONOUS_PIPE
|
||||
jnz .goodtype
|
||||
.badtype:
|
||||
dbgstr 'unsupported type of USB pipe'
|
||||
jmp .return0
|
||||
.goodtype:
|
||||
; 2. Allocate memory for pipe and transfer queue.
|
||||
; Empty transfer queue consists of one inactive TD.
|
||||
mov ebx, [config_pipe]
|
||||
mov esi, [ebx+usb_pipe.Controller]
|
||||
mov edx, [esi+usb_controller.HardwareFunc]
|
||||
call [edx+usb_hardware_func.AllocPipe]
|
||||
test eax, eax
|
||||
jz .nothing
|
||||
mov edi, eax
|
||||
mov edx, [esi+usb_controller.HardwareFunc]
|
||||
call [edx+usb_hardware_func.AllocTD]
|
||||
test eax, eax
|
||||
jz .free_and_return0
|
||||
; 3. Initialize transfer queue: pointer to transfer descriptor,
|
||||
; pointers in transfer descriptor, queue lock.
|
||||
mov [edi+usb_pipe.LastTD], eax
|
||||
mov [eax+usb_gtd.NextVirt], eax
|
||||
mov [eax+usb_gtd.PrevVirt], eax
|
||||
mov [eax+usb_gtd.Pipe], edi
|
||||
lea ecx, [edi+usb_pipe.Lock]
|
||||
call mutex_init
|
||||
; 4. Initialize software part of pipe structure, except device-related fields.
|
||||
mov al, byte [type]
|
||||
mov [edi+usb_pipe.Type], al
|
||||
xor eax, eax
|
||||
mov [edi+usb_pipe.Flags], al
|
||||
mov [edi+usb_pipe.DeviceData], eax
|
||||
mov [edi+usb_pipe.Controller], esi
|
||||
or [edi+usb_pipe.NextWait], -1
|
||||
; 5. Initialize device-related fields:
|
||||
; for zero endpoint, set .NextSibling = .PrevSibling = this;
|
||||
; for other endpoins, copy device data, take the lock guarding pipe list
|
||||
; for the device and verify that disconnect processing has not yet started
|
||||
; for the device. (Since disconnect processing also takes that lock,
|
||||
; either it has completed or it will not start until we release the lock.)
|
||||
; Note: usb_device_disconnected should not see the new pipe until
|
||||
; initialization is complete, so that lock will be held during next steps
|
||||
; (disconnect processing should either not see it at all, or see fully
|
||||
; initialized pipe).
|
||||
cmp [endpoint], eax
|
||||
jz .zero_endpoint
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
mov [edi+usb_pipe.DeviceData], ecx
|
||||
call mutex_lock
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED
|
||||
jz .common
|
||||
.fail:
|
||||
; If disconnect processing has completed, unlock the mutex, free memory
|
||||
; allocated in step 2 and return zero.
|
||||
call mutex_unlock
|
||||
mov edx, [esi+usb_controller.HardwareFunc]
|
||||
stdcall [edx+usb_hardware_func.FreeTD], [edi+usb_pipe.LastTD]
|
||||
.free_and_return0:
|
||||
mov edx, [esi+usb_controller.HardwareFunc]
|
||||
stdcall [edx+usb_hardware_func.FreePipe], edi
|
||||
.return0:
|
||||
xor eax, eax
|
||||
jmp .nothing
|
||||
.zero_endpoint:
|
||||
mov [edi+usb_pipe.NextSibling], edi
|
||||
mov [edi+usb_pipe.PrevSibling], edi
|
||||
.common:
|
||||
; 6. Initialize hardware part of pipe structure.
|
||||
; 6a. Acquire the corresponding mutex.
|
||||
lea ecx, [esi+usb_controller.ControlLock]
|
||||
cmp [type], BULK_PIPE
|
||||
jb @f ; control pipe
|
||||
lea ecx, [esi+usb_controller.BulkLock]
|
||||
jz @f ; bulk pipe
|
||||
lea ecx, [esi+usb_controller.PeriodicLock]
|
||||
@@:
|
||||
call mutex_lock
|
||||
; 6b. Let the controller-specific code do its job.
|
||||
push ecx
|
||||
mov edx, [esi+usb_controller.HardwareFunc]
|
||||
mov eax, [edi+usb_pipe.LastTD]
|
||||
mov ecx, [config_pipe]
|
||||
call [edx+usb_hardware_func.InitPipe]
|
||||
pop ecx
|
||||
; 6c. Release the mutex.
|
||||
push eax
|
||||
call mutex_unlock
|
||||
pop eax
|
||||
; 6d. If controller-specific code indicates failure,
|
||||
; release the lock taken in step 5, free memory allocated in step 2
|
||||
; and return zero.
|
||||
test eax, eax
|
||||
jz .fail
|
||||
; 7. The pipe is initialized. If this is not the first pipe for the device,
|
||||
; insert it to the tail of pipe list for the device,
|
||||
; increment number of pipes,
|
||||
; release the lock taken at step 5.
|
||||
mov ecx, [edi+usb_pipe.DeviceData]
|
||||
test ecx, ecx
|
||||
jz @f
|
||||
mov eax, [ebx+usb_pipe.PrevSibling]
|
||||
mov [edi+usb_pipe.NextSibling], ebx
|
||||
mov [edi+usb_pipe.PrevSibling], eax
|
||||
mov [ebx+usb_pipe.PrevSibling], edi
|
||||
mov [eax+usb_pipe.NextSibling], edi
|
||||
inc [ecx+usb_device_data.NumPipes]
|
||||
call mutex_unlock
|
||||
@@:
|
||||
; 8. Return pointer to usb_pipe.
|
||||
mov eax, edi
|
||||
.nothing:
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called several times during initial device configuration,
|
||||
; when usb_device_data structure is reallocated.
|
||||
; It (re)initializes all pointers in usb_device_data.
|
||||
; ebx -> usb_pipe
|
||||
proc usb_reinit_pipe_list
|
||||
push eax
|
||||
; 1. (Re)initialize the lock guarding pipe list.
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
call mutex_init
|
||||
; 2. Initialize list of opened pipes: two entries, the head and ebx.
|
||||
add ecx, usb_device_data.OpenedPipeList - usb_pipe.NextSibling
|
||||
mov [ecx+usb_pipe.NextSibling], ebx
|
||||
mov [ecx+usb_pipe.PrevSibling], ebx
|
||||
mov [ebx+usb_pipe.NextSibling], ecx
|
||||
mov [ebx+usb_pipe.PrevSibling], ecx
|
||||
; 3. Initialize list of closed pipes: empty list, only the head is present.
|
||||
add ecx, usb_device_data.ClosedPipeList - usb_device_data.OpenedPipeList
|
||||
mov [ecx+usb_pipe.NextSibling], ecx
|
||||
mov [ecx+usb_pipe.PrevSibling], ecx
|
||||
pop eax
|
||||
ret
|
||||
endp
|
||||
|
||||
; Part of API for drivers, see documentation for USBClosePipe.
|
||||
proc usb_close_pipe
|
||||
push ebx esi ; save used registers to be stdcall
|
||||
virtual at esp
|
||||
rd 2 ; saved registers
|
||||
dd ? ; return address
|
||||
.pipe dd ?
|
||||
end virtual
|
||||
; 1. Lock the pipe list for the device.
|
||||
mov ebx, [.pipe]
|
||||
mov esi, [ebx+usb_pipe.Controller]
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
call mutex_lock
|
||||
; 2. Set the flag "the driver has abandoned this pipe, free it at any time".
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_lock
|
||||
or [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE
|
||||
call mutex_unlock
|
||||
; 3. Call the worker function.
|
||||
call usb_close_pipe_nolock
|
||||
; 4. Unlock the pipe list for the device.
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
call mutex_unlock
|
||||
; 5. Wakeup the USB thread so that it can proceed with releasing that pipe.
|
||||
push edi
|
||||
call usb_wakeup
|
||||
pop edi
|
||||
; 6. Return.
|
||||
pop esi ebx ; restore used registers to be stdcall
|
||||
retn 4
|
||||
endp
|
||||
|
||||
; Worker function for pipe closing. Called by usb_close_pipe API and
|
||||
; from disconnect processing.
|
||||
; The lock guarding pipe list for the device should be held by the caller.
|
||||
; ebx -> usb_pipe, esi -> usb_controller
|
||||
proc usb_close_pipe_nolock
|
||||
; 1. Set the flag "pipe is closed, ignore new transfers".
|
||||
; If it was already set, do nothing.
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_lock
|
||||
bts dword [ebx+usb_pipe.Flags], USB_FLAG_CLOSED_BIT
|
||||
jc .closed
|
||||
call mutex_unlock
|
||||
; 2. Remove the pipe from the list of opened pipes.
|
||||
mov eax, [ebx+usb_pipe.NextSibling]
|
||||
mov edx, [ebx+usb_pipe.PrevSibling]
|
||||
mov [eax+usb_pipe.PrevSibling], edx
|
||||
mov [edx+usb_pipe.NextSibling], eax
|
||||
; 3. Unlink the pipe from hardware structures.
|
||||
; 3a. Acquire the corresponding lock.
|
||||
lea edx, [esi+usb_controller.WaitPipeListAsync]
|
||||
lea ecx, [esi+usb_controller.ControlLock]
|
||||
cmp [ebx+usb_pipe.Type], BULK_PIPE
|
||||
jb @f ; control pipe
|
||||
lea ecx, [esi+usb_controller.BulkLock]
|
||||
jz @f ; bulk pipe
|
||||
add edx, usb_controller.WaitPipeListPeriodic - usb_controller.WaitPipeListAsync
|
||||
lea ecx, [esi+usb_controller.PeriodicLock]
|
||||
@@:
|
||||
push edx
|
||||
call mutex_lock
|
||||
push ecx
|
||||
; 3b. Let the controller-specific code do its job.
|
||||
mov eax, [esi+usb_controller.HardwareFunc]
|
||||
call [eax+usb_hardware_func.UnlinkPipe]
|
||||
; 3c. Release the corresponding lock.
|
||||
pop ecx
|
||||
call mutex_unlock
|
||||
; 4. Put the pipe into wait queue.
|
||||
pop edx
|
||||
cmp [ebx+usb_pipe.NextWait], -1
|
||||
jz .insert_new
|
||||
or [ebx+usb_pipe.Flags], USB_FLAG_EXTRA_WAIT
|
||||
jmp .inserted
|
||||
.insert_new:
|
||||
mov eax, [edx]
|
||||
mov [ebx+usb_pipe.NextWait], eax
|
||||
mov [edx], ebx
|
||||
.inserted:
|
||||
; 5. Return.
|
||||
ret
|
||||
.closed:
|
||||
call mutex_unlock
|
||||
xor eax, eax
|
||||
ret
|
||||
endp
|
||||
|
||||
; This procedure is called when a pipe with USB_FLAG_CLOSED is removed from the
|
||||
; corresponding wait list. It means that the hardware has fully forgot about it.
|
||||
; ebx -> usb_pipe, esi -> usb_controller
|
||||
proc usb_pipe_closed
|
||||
push edi
|
||||
mov edi, [esi+usb_controller.HardwareFunc]
|
||||
; 1. Loop over all transfers, calling the driver with USB_STATUS_CLOSED
|
||||
; and freeing all descriptors.
|
||||
mov edx, [ebx+usb_pipe.LastTD]
|
||||
test edx, edx
|
||||
jz .no_transfer
|
||||
mov edx, [edx+usb_gtd.NextVirt]
|
||||
.transfer_loop:
|
||||
cmp edx, [ebx+usb_pipe.LastTD]
|
||||
jz .transfer_done
|
||||
mov ecx, [edx+usb_gtd.Callback]
|
||||
test ecx, ecx
|
||||
jz .no_callback
|
||||
push edx
|
||||
stdcall_verify ecx, ebx, USB_STATUS_CLOSED, \
|
||||
[edx+usb_gtd.Buffer], 0, [edx+usb_gtd.UserData]
|
||||
pop edx
|
||||
.no_callback:
|
||||
push [edx+usb_gtd.NextVirt]
|
||||
stdcall [edi+usb_hardware_func.FreeTD], edx
|
||||
pop edx
|
||||
jmp .transfer_loop
|
||||
.transfer_done:
|
||||
stdcall [edi+usb_hardware_func.FreeTD], edx
|
||||
.no_transfer:
|
||||
; 2. Decrement number of pipes for the device.
|
||||
; If this pipe is the last pipe, go to 5.
|
||||
mov ecx, [ebx+usb_pipe.DeviceData]
|
||||
call mutex_lock
|
||||
dec [ecx+usb_device_data.NumPipes]
|
||||
jz .last_pipe
|
||||
call mutex_unlock
|
||||
; 3. If the flag "the driver has abandoned this pipe" is set,
|
||||
; free memory and return.
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_CAN_FREE
|
||||
jz .nofree
|
||||
stdcall [edi+usb_hardware_func.FreePipe], ebx
|
||||
pop edi
|
||||
ret
|
||||
; 4. Otherwise, add it to the list of closed pipes and return.
|
||||
.nofree:
|
||||
add ecx, usb_device_data.ClosedPipeList - usb_pipe.NextSibling
|
||||
mov edx, [ecx+usb_pipe.PrevSibling]
|
||||
mov [ebx+usb_pipe.NextSibling], ecx
|
||||
mov [ebx+usb_pipe.PrevSibling], edx
|
||||
mov [ecx+usb_pipe.PrevSibling], ebx
|
||||
mov [edx+usb_pipe.NextSibling], ebx
|
||||
pop edi
|
||||
ret
|
||||
.last_pipe:
|
||||
; That was the last pipe for the device.
|
||||
; 5. Notify device driver(s) about disconnect.
|
||||
call mutex_unlock
|
||||
movzx eax, [ecx+usb_device_data.NumInterfaces]
|
||||
test eax, eax
|
||||
jz .notify_done
|
||||
add ecx, [ecx+usb_device_data.Interfaces]
|
||||
.notify_loop:
|
||||
mov edx, [ecx+usb_interface_data.DriverFunc]
|
||||
test edx, edx
|
||||
jz @f
|
||||
mov edx, [edx+USBSRV.usb_func]
|
||||
cmp [edx+USBFUNC.strucsize], USBFUNC.device_disconnect + 4
|
||||
jb @f
|
||||
mov edx, [edx+USBFUNC.device_disconnect]
|
||||
test edx, edx
|
||||
jz @f
|
||||
push eax ecx
|
||||
stdcall_verify edx, [ecx+usb_interface_data.DriverData]
|
||||
pop ecx eax
|
||||
@@:
|
||||
add ecx, sizeof.usb_interface_data
|
||||
dec eax
|
||||
jnz .notify_loop
|
||||
.notify_done:
|
||||
; 6. Bus address, if assigned, can now be reused.
|
||||
call [edi+usb_hardware_func.GetDeviceAddress]
|
||||
test eax, eax
|
||||
jz @f
|
||||
bts [esi+usb_controller.ExistingAddresses], eax
|
||||
@@:
|
||||
dbgstr 'USB device disconnected'
|
||||
; 7. All drivers have returned from disconnect callback,
|
||||
; so all drivers should not use any device-related pipes.
|
||||
; Free the remaining pipes.
|
||||
mov eax, [ebx+usb_pipe.DeviceData]
|
||||
add eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling
|
||||
push eax
|
||||
mov eax, [eax+usb_pipe.NextSibling]
|
||||
.free_loop:
|
||||
cmp eax, [esp]
|
||||
jz .free_done
|
||||
push [eax+usb_pipe.NextSibling]
|
||||
stdcall [edi+usb_hardware_func.FreePipe], eax
|
||||
pop eax
|
||||
jmp .free_loop
|
||||
.free_done:
|
||||
stdcall [edi+usb_hardware_func.FreePipe], ebx
|
||||
pop eax
|
||||
; 8. Free the usb_device_data structure.
|
||||
sub eax, usb_device_data.ClosedPipeList - usb_pipe.NextSibling
|
||||
call free
|
||||
; 9. Return.
|
||||
.nothing:
|
||||
pop edi
|
||||
ret
|
||||
endp
|
||||
|
||||
; Part of API for drivers, see documentation for USBNormalTransferAsync.
|
||||
proc usb_normal_transfer_async stdcall uses ebx edi,\
|
||||
pipe:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword
|
||||
; 1. Sanity check: callback must be nonzero.
|
||||
; (It is important for other parts of code.)
|
||||
xor eax, eax
|
||||
cmp [callback], eax
|
||||
jz .nothing
|
||||
; 2. Lock the transfer queue.
|
||||
mov ebx, [pipe]
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_lock
|
||||
; 3. If the pipe has already been closed (presumably due to device disconnect),
|
||||
; release the lock taken in step 2 and return zero.
|
||||
xor eax, eax
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED
|
||||
jnz .unlock
|
||||
; 4. Allocate and initialize TDs for the transfer.
|
||||
mov edx, [ebx+usb_pipe.Controller]
|
||||
mov edi, [edx+usb_controller.HardwareFunc]
|
||||
stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], [ebx+usb_pipe.LastTD], 0
|
||||
; If failed, release the lock taken in step 2 and return zero.
|
||||
test eax, eax
|
||||
jz .unlock
|
||||
; 5. Store callback and its parameters in the last descriptor for this transfer.
|
||||
mov ecx, [eax+usb_gtd.PrevVirt]
|
||||
mov edx, [callback]
|
||||
mov [ecx+usb_gtd.Callback], edx
|
||||
mov edx, [calldata]
|
||||
mov [ecx+usb_gtd.UserData], edx
|
||||
mov edx, [buffer]
|
||||
mov [ecx+usb_gtd.Buffer], edx
|
||||
; 6. Advance LastTD pointer and activate transfer.
|
||||
push [ebx+usb_pipe.LastTD]
|
||||
mov [ebx+usb_pipe.LastTD], eax
|
||||
call [edi+usb_hardware_func.InsertTransfer]
|
||||
pop eax
|
||||
; 7. Release the lock taken in step 2 and
|
||||
; return pointer to the first descriptor for the new transfer.
|
||||
.unlock:
|
||||
push eax
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_unlock
|
||||
pop eax
|
||||
.nothing:
|
||||
ret
|
||||
endp
|
||||
|
||||
; Part of API for drivers, see documentation for USBControlTransferAsync.
|
||||
proc usb_control_async stdcall uses ebx edi,\
|
||||
pipe:dword, config:dword, buffer:dword, size:dword, callback:dword, calldata:dword, flags:dword
|
||||
locals
|
||||
last_td dd ?
|
||||
endl
|
||||
; 1. Sanity check: callback must be nonzero.
|
||||
; (It is important for other parts of code.)
|
||||
cmp [callback], 0
|
||||
jz .return0
|
||||
; 2. Lock the transfer queue.
|
||||
mov ebx, [pipe]
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_lock
|
||||
; 3. If the pipe has already been closed (presumably due to device disconnect),
|
||||
; release the lock taken in step 2 and return zero.
|
||||
test [ebx+usb_pipe.Flags], USB_FLAG_CLOSED
|
||||
jnz .unlock_return0
|
||||
; A control transfer contains two or three stages:
|
||||
; Setup stage, optional Data stage, Status stage.
|
||||
; 4. Allocate and initialize TDs for the Setup stage.
|
||||
; Payload is 8 bytes from [config].
|
||||
mov edx, [ebx+usb_pipe.Controller]
|
||||
mov edi, [edx+usb_controller.HardwareFunc]
|
||||
stdcall [edi+usb_hardware_func.AllocTransfer], [config], 8, 0, [ebx+usb_pipe.LastTD], (2 shl 2) + 0
|
||||
; short transfer is an error, direction is DATA0, token is SETUP
|
||||
mov [last_td], eax
|
||||
test eax, eax
|
||||
jz .fail
|
||||
; 5. Allocate and initialize TDs for the Data stage, if [size] is nonzero.
|
||||
; Payload is [size] bytes from [buffer].
|
||||
mov edx, [config]
|
||||
mov ecx, (3 shl 2) + 1 ; DATA1, token is OUT
|
||||
cmp byte [edx], 0
|
||||
jns @f
|
||||
cmp [size], 0
|
||||
jz @f
|
||||
inc ecx ; token is IN
|
||||
@@:
|
||||
cmp [size], 0
|
||||
jz .nodata
|
||||
push ecx
|
||||
stdcall [edi+usb_hardware_func.AllocTransfer], [buffer], [size], [flags], eax, ecx
|
||||
pop ecx
|
||||
test eax, eax
|
||||
jz .fail
|
||||
mov [last_td], eax
|
||||
.nodata:
|
||||
; 6. Allocate and initialize TDs for the Status stage.
|
||||
; No payload.
|
||||
xor ecx, 3 ; IN becomes OUT, OUT becomes IN
|
||||
stdcall [edi+usb_hardware_func.AllocTransfer], 0, 0, 0, eax, ecx
|
||||
test eax, eax
|
||||
jz .fail
|
||||
; 7. Store callback and its parameters in the last descriptor for this transfer.
|
||||
mov ecx, [eax+usb_gtd.PrevVirt]
|
||||
mov edx, [callback]
|
||||
mov [ecx+usb_gtd.Callback], edx
|
||||
mov edx, [calldata]
|
||||
mov [ecx+usb_gtd.UserData], edx
|
||||
mov edx, [buffer]
|
||||
mov [ecx+usb_gtd.Buffer], edx
|
||||
; 8. Advance LastTD pointer and activate transfer.
|
||||
push [ebx+usb_pipe.LastTD]
|
||||
mov [ebx+usb_pipe.LastTD], eax
|
||||
call [edi+usb_hardware_func.InsertTransfer]
|
||||
; 9. Release the lock taken in step 2 and
|
||||
; return pointer to the first descriptor for the new transfer.
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_unlock
|
||||
pop eax
|
||||
ret
|
||||
.fail:
|
||||
mov eax, [last_td]
|
||||
test eax, eax
|
||||
jz .unlock_return0
|
||||
stdcall usb_undo_tds, [ebx+usb_pipe.LastTD]
|
||||
.unlock_return0:
|
||||
lea ecx, [ebx+usb_pipe.Lock]
|
||||
call mutex_unlock
|
||||
.return0:
|
||||
xor eax, eax
|
||||
ret
|
||||
endp
|
||||
|
||||
; Initialize software part of usb_gtd. Called from controller-specific code
|
||||
; somewhere in AllocTransfer with eax -> next (inactive) usb_gtd,
|
||||
; ebx -> usb_pipe, ebp frame from call to AllocTransfer with [.td] ->
|
||||
; current (initializing) usb_gtd.
|
||||
; Returns ecx = [.td].
|
||||
proc usb_init_transfer
|
||||
virtual at ebp-4
|
||||
.Size dd ?
|
||||
rd 2
|
||||
.Buffer dd ?
|
||||
dd ?
|
||||
.Flags dd ?
|
||||
.td dd ?
|
||||
end virtual
|
||||
mov [eax+usb_gtd.Pipe], ebx
|
||||
mov ecx, [.td]
|
||||
mov [eax+usb_gtd.PrevVirt], ecx
|
||||
mov edx, [ecx+usb_gtd.NextVirt]
|
||||
mov [ecx+usb_gtd.NextVirt], eax
|
||||
mov [eax+usb_gtd.NextVirt], edx
|
||||
mov [edx+usb_gtd.PrevVirt], eax
|
||||
mov edx, [.Size]
|
||||
mov [ecx+usb_gtd.Length], edx
|
||||
xor edx, edx
|
||||
mov [ecx+usb_gtd.Callback], edx
|
||||
mov [ecx+usb_gtd.UserData], edx
|
||||
ret
|
||||
endp
|
||||
|
||||
; Free all TDs for the current transfer if something has failed
|
||||
; during initialization (e.g. no memory for the next TD).
|
||||
; Stdcall with one stack argument = first TD for the transfer
|
||||
; and eax = last initialized TD for the transfer.
|
||||
proc usb_undo_tds
|
||||
push [eax+usb_gtd.NextVirt]
|
||||
@@:
|
||||
cmp eax, [esp+8]
|
||||
jz @f
|
||||
push [eax+usb_gtd.PrevVirt]
|
||||
stdcall [edi+usb_hardware_func.FreeTD], eax
|
||||
pop eax
|
||||
jmp @b
|
||||
@@:
|
||||
pop ecx
|
||||
mov [eax+usb_gtd.NextVirt], ecx
|
||||
mov [ecx+usb_gtd.PrevVirt], eax
|
||||
ret 4
|
||||
endp
|
||||
|
||||
; Helper procedure for handling short packets in controller-specific code.
|
||||
; Returns with CF cleared if this is the final packet in some stage:
|
||||
; for control transfers that means one of Data and Status stages,
|
||||
; for other transfers - the final packet in the only stage.
|
||||
proc usb_is_final_packet
|
||||
cmp [ebx+usb_gtd.Callback], 0
|
||||
jnz .nothing
|
||||
mov eax, [ebx+usb_gtd.NextVirt]
|
||||
cmp [eax+usb_gtd.Callback], 0
|
||||
jz .stc
|
||||
mov eax, [ebx+usb_gtd.Pipe]
|
||||
cmp [eax+usb_pipe.Type], CONTROL_PIPE
|
||||
jz .nothing
|
||||
.stc:
|
||||
stc
|
||||
.nothing:
|
||||
ret
|
||||
endp
|
||||
|
||||
; Helper procedure for controller-specific code:
|
||||
; removes one TD from the transfer queue, ebx -> usb_gtd to remove.
|
||||
proc usb_unlink_td
|
||||
mov ecx, [ebx+usb_gtd.Pipe]
|
||||
add ecx, usb_pipe.Lock
|
||||
call mutex_lock
|
||||
mov eax, [ebx+usb_gtd.PrevVirt]
|
||||
mov edx, [ebx+usb_gtd.NextVirt]
|
||||
mov [edx+usb_gtd.PrevVirt], eax
|
||||
mov [eax+usb_gtd.NextVirt], edx
|
||||
call mutex_unlock
|
||||
ret
|
||||
endp
|
||||
|
||||
if USB_STDCALL_VERIFY
|
||||
proc verify_regs
|
||||
virtual at esp
|
||||
dd ? ; return address
|
||||
.edi dd ?
|
||||
.esi dd ?
|
||||
.ebp dd ?
|
||||
.esp dd ?
|
||||
.ebx dd ?
|
||||
.edx dd ?
|
||||
.ecx dd ?
|
||||
.eax dd ?
|
||||
end virtual
|
||||
cmp ebx, [.ebx]
|
||||
jz @f
|
||||
dbgstr 'ERROR!!! ebx changed'
|
||||
@@:
|
||||
cmp esi, [.esi]
|
||||
jz @f
|
||||
dbgstr 'ERROR!!! esi changed'
|
||||
@@:
|
||||
cmp edi, [.edi]
|
||||
jz @f
|
||||
dbgstr 'ERROR!!! edi changed'
|
||||
@@:
|
||||
cmp ebp, [.ebp]
|
||||
jz @f
|
||||
dbgstr 'ERROR!!! ebp changed'
|
||||
@@:
|
||||
ret
|
||||
endp
|
||||
end if
|
926
kernel/trunk/bus/usb/protocol.inc
Normal file
926
kernel/trunk/bus/usb/protocol.inc
Normal file
@ -0,0 +1,926 @@
|
||||
; 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
|
||||
|
||||
; Possible speeds of USB devices
|
||||
USB_SPEED_FS = 0 ; full-speed
|
||||
USB_SPEED_LS = 1 ; low-speed
|
||||
USB_SPEED_HS = 2 ; high-speed
|
||||
|
||||
; Compile-time setting. If set, the code will dump all descriptors as they are
|
||||
; read to the debug board.
|
||||
USB_DUMP_DESCRIPTORS = 1
|
||||
|
||||
; =============================================================================
|
||||
; ================================ 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. 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.
|
||||
; 1a. Allocate a bus address.
|
||||
push ecx
|
||||
call usb_set_address_request
|
||||
pop ecx
|
||||
; 1b. If failed, just return zero.
|
||||
test eax, eax
|
||||
jz .nothing
|
||||
; 1c. 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.
|
||||
push sizeof.usb_device_data + 8
|
||||
pop eax
|
||||
push ecx
|
||||
call malloc
|
||||
pop ecx
|
||||
; 1d. If failed, free the bus address and return zero.
|
||||
test eax, eax
|
||||
jz .nomemory
|
||||
; 1e. 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
|
||||
; 1f. If failed, free the memory, the bus address and return zero.
|
||||
test ebx, ebx
|
||||
jz .freememory
|
||||
; 2. Store pointer to device data in the pipe structure.
|
||||
mov [ebx+usb_pipe.DeviceData], eax
|
||||
; 3. Init device data, using usb_controller.Resetting* variables.
|
||||
mov [eax+usb_device_data.NumPipes], 1
|
||||
mov [eax+usb_device_data.ConfigDataSize], edi
|
||||
mov [eax+usb_device_data.Interfaces], edi
|
||||
movzx ecx, [esi+usb_controller.ResettingPort]
|
||||
; Note: the following write zeroes
|
||||
; usb_device_data.DeviceDescrSize, usb_device_data.NumInterfaces,
|
||||
; usb_device_data.Speed.
|
||||
mov dword [eax+usb_device_data.Port], ecx
|
||||
mov dl, [esi+usb_controller.ResettingSpeed]
|
||||
mov [eax+usb_device_data.Speed], dl
|
||||
mov edx, [esi+usb_controller.ResettingHub]
|
||||
mov [eax+usb_device_data.Hub], edx
|
||||
; 4. 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
|
||||
; 5. Issue SET_ADDRESS control request, using buffer filled in step 1a.
|
||||
; Use the return value from usb_control_async as our return value;
|
||||
; if it is zero, then something has failed.
|
||||
lea eax, [esi+usb_controller.SetAddressBuffer]
|
||||
stdcall usb_control_async, ebx, eax, edi, edi, usb_set_address_callback, edi, edi
|
||||
.nothing:
|
||||
; 6. Return.
|
||||
pop edi ebx ; restore used registers to be stdcall
|
||||
ret
|
||||
; Handlers of failures in steps 1b, 1d, 1f.
|
||||
.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
|
||||
; Load data to registers for further references.
|
||||
mov ebx, [pipe]
|
||||
mov ecx, dword [esi+usb_controller.SetAddressBuffer+2]
|
||||
mov eax, [esi+usb_controller.HardwareFunc]
|
||||
; 1. Check whether the device has accepted new address. If so, proceed to 2.
|
||||
; Otherwise, go to 3.
|
||||
cmp [status], 0
|
||||
jnz .error
|
||||
; 2. Address accepted.
|
||||
; 2a. 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]
|
||||
; 2b. If the port is in non-root hub, clear 'reset in progress' flag.
|
||||
; In any case, proceed to 4.
|
||||
mov eax, [esi+usb_controller.ResettingHub]
|
||||
test eax, eax
|
||||
jz .return
|
||||
and [eax+usb_hub.Actions], not HUB_RESET_IN_PROGRESS
|
||||
.return:
|
||||
; 4. Address configuration done, we can proceed with other ports.
|
||||
; Call the worker function for that.
|
||||
call usb_test_pending_port
|
||||
.nothing:
|
||||
pop ebx ; restore ebx to be stdcall
|
||||
ret
|
||||
.error:
|
||||
; 3. Device error: device not responding, disconnect etc.
|
||||
DEBUGF 1,'K : error %d in SET_ADDRESS, USB device disabled\n',[status]
|
||||
; 3a. The address has not been accepted. Mark it as free.
|
||||
bts dword [esi+usb_controller.ExistingAddresses], ecx
|
||||
; 3b. 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
|
||||
; Note that malloc destroys ebx.
|
||||
push ebx
|
||||
call malloc
|
||||
pop ebx
|
||||
; 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
|
||||
repeat sizeof.usb_device_data / 4
|
||||
movsd
|
||||
end repeat
|
||||
pop edi esi
|
||||
call usb_reinit_pipe_list
|
||||
; 1d. Free the old memory.
|
||||
; Note that free destroys ebx.
|
||||
push ebx
|
||||
call free
|
||||
pop ebx
|
||||
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(DEVICE) 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], dl
|
||||
; 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
|
||||
movzx 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 dl
|
||||
jz .invalid
|
||||
cmp dl, [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
|
508
kernel/trunk/bus/usb/scheduler.inc
Normal file
508
kernel/trunk/bus/usb/scheduler.inc
Normal file
@ -0,0 +1,508 @@
|
||||
; Implementation of periodic transaction scheduler for USB.
|
||||
; Bandwidth dedicated to periodic transactions is limited, so
|
||||
; different pipes should be scheduled as uniformly as possible.
|
||||
|
||||
; USB1 scheduler.
|
||||
; Algorithm is simple:
|
||||
; when adding a pipe, optimize the following quantity:
|
||||
; * for every millisecond, take all bandwidth scheduled to periodic transfers,
|
||||
; * calculate maximum over all milliseconds,
|
||||
; * select a variant which minimizes that maximum;
|
||||
; when removing a pipe, do nothing (except for bookkeeping).
|
||||
|
||||
; sanity check: structures in UHCI and OHCI should be the same
|
||||
if (sizeof.ohci_static_ep=sizeof.uhci_static_ep)&(ohci_static_ep.SoftwarePart=uhci_static_ep.SoftwarePart)&(ohci_static_ep.NextList=uhci_static_ep.NextList)
|
||||
; Select a list for a new pipe.
|
||||
; in: esi -> usb_controller, maxpacket, type, interval can be found in the stack
|
||||
; in: ecx = 2 * maximal interval = total number of periodic lists + 1
|
||||
; in: edx -> {u|o}hci_static_ep for the first list
|
||||
; in: eax -> byte past {u|o}hci_static_ep for the last list in the first group
|
||||
; out: edx -> usb_static_ep for the selected list or zero if failed
|
||||
proc usb1_select_interrupt_list
|
||||
; inherit some variables from usb_open_pipe
|
||||
virtual at ebp-8
|
||||
.bandwidth dd ?
|
||||
.target dd ?
|
||||
dd ?
|
||||
dd ?
|
||||
.config_pipe dd ?
|
||||
.endpoint dd ?
|
||||
.maxpacket dd ?
|
||||
.type dd ?
|
||||
.interval dd ?
|
||||
end virtual
|
||||
push ebx edi ; save used registers to be stdcall
|
||||
push eax ; save eax for checks in step 3
|
||||
; 1. Only intervals 2^k ms can be supported.
|
||||
; The core specification says that the real interval should not be greater
|
||||
; than the interval given by the endpoint descriptor, but can be less.
|
||||
; Determine the actual interval as 2^k ms.
|
||||
mov eax, ecx
|
||||
; 1a. Set [.interval] to 1 if it was zero; leave it as is otherwise
|
||||
cmp [.interval], 1
|
||||
adc [.interval], 0
|
||||
; 1b. Divide ecx by two while it is strictly greater than [.interval].
|
||||
@@:
|
||||
shr ecx, 1
|
||||
cmp [.interval], ecx
|
||||
jb @b
|
||||
; ecx = the actual interval
|
||||
;
|
||||
; For example, let ecx = 8, eax = 64.
|
||||
; The scheduler space is 32 milliseconds,
|
||||
; we need to schedule something every 8 ms;
|
||||
; there are 8 variants: schedule at times 0,8,16,24,
|
||||
; schedule at times 1,9,17,25,..., schedule at times 7,15,23,31.
|
||||
; Now concentrate: there are three nested loops,
|
||||
; * the innermost loop calculates the total periodic bandwidth scheduled
|
||||
; in the given millisecond,
|
||||
; * the intermediate loop calculates the maximum over all milliseconds
|
||||
; in the given variant, that is the quantity we're trying to minimize,
|
||||
; * the outermost loop checks all variants.
|
||||
; 2. Calculate offset between the first list and the first list for the
|
||||
; selected interval, in bytes; save in the stack for step 4.
|
||||
sub eax, ecx
|
||||
sub eax, ecx
|
||||
imul eax, sizeof.ohci_static_ep
|
||||
push eax
|
||||
imul ebx, ecx, sizeof.ohci_static_ep
|
||||
; 3. Select the best variant.
|
||||
; 3a. The outermost loop.
|
||||
; Prepare for the loop: set the current optimal bandwidth to maximum
|
||||
; possible value (so that any variant will pass the first comparison),
|
||||
; calculate delta for the intermediate loop.
|
||||
or [.bandwidth], -1
|
||||
.varloop:
|
||||
; 3b. The intermediate loop.
|
||||
; Prepare for the loop: set the maximum to be calculated to zero,
|
||||
; save counter of the outermost loop.
|
||||
xor edi, edi
|
||||
push edx
|
||||
virtual at esp
|
||||
.cur_variant dd ? ; step 3b
|
||||
.result_delta dd ? ; step 2
|
||||
.group1_limit dd ? ; function prolog
|
||||
end virtual
|
||||
.calc_max_bandwidth:
|
||||
; 3c. The innermost loop. Sum over all lists.
|
||||
xor eax, eax
|
||||
push edx
|
||||
.calc_bandwidth:
|
||||
add eax, [edx+ohci_static_ep.SoftwarePart+usb_static_ep.Bandwidth]
|
||||
mov edx, [edx+ohci_static_ep.NextList]
|
||||
test edx, edx
|
||||
jnz .calc_bandwidth
|
||||
pop edx
|
||||
; 3d. The intermediate loop continued: update maximum.
|
||||
cmp eax, edi
|
||||
jb @f
|
||||
mov edi, eax
|
||||
@@:
|
||||
; 3e. The intermediate loop continued: advance counter.
|
||||
add edx, ebx
|
||||
cmp edx, [.group1_limit]
|
||||
jb .calc_max_bandwidth
|
||||
; 3e. The intermediate loop done: restore counter of the outermost loop.
|
||||
pop edx
|
||||
; 3f. The outermost loop continued: if the current variant is
|
||||
; better (maybe not strictly) then the previous optimum, update
|
||||
; the optimal bandwidth and resulting list.
|
||||
cmp edi, [.bandwidth]
|
||||
ja @f
|
||||
mov [.bandwidth], edi
|
||||
mov [.target], edx
|
||||
@@:
|
||||
; 3g. The outermost loop continued: advance counter.
|
||||
add edx, sizeof.ohci_static_ep
|
||||
dec ecx
|
||||
jnz .varloop
|
||||
; 4. Get the pointer to the best list.
|
||||
pop edx ; restore value from step 2
|
||||
pop eax ; purge stack var from prolog
|
||||
add edx, [.target]
|
||||
; 5. Calculate bandwidth for the new pipe.
|
||||
mov eax, [.maxpacket] ; TODO: calculate real bandwidth
|
||||
and eax, (1 shl 11) - 1
|
||||
; 6. TODO: check that bandwidth for the new pipe plus old bandwidth
|
||||
; still fits to maximum allowed by the core specification.
|
||||
; 7. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return.
|
||||
add edx, ohci_static_ep.SoftwarePart
|
||||
add [edx+usb_static_ep.Bandwidth], eax
|
||||
pop edi ebx ; restore used registers to be stdcall
|
||||
ret
|
||||
endp
|
||||
; sanity check, part 2
|
||||
else
|
||||
.err select_interrupt_list must be different for UHCI and OHCI
|
||||
end if
|
||||
|
||||
; Pipe is removing, update the corresponding lists.
|
||||
; We do not reorder anything, so just update book-keeping variable
|
||||
; in the list header.
|
||||
proc usb1_interrupt_list_unlink
|
||||
virtual at esp
|
||||
dd ? ; return address
|
||||
.maxpacket dd ?
|
||||
.lowspeed db ?
|
||||
.direction db ?
|
||||
rb 2
|
||||
end virtual
|
||||
; find list header
|
||||
mov edx, ebx
|
||||
@@:
|
||||
mov edx, [edx+usb_pipe.NextVirt]
|
||||
cmp [edx+usb_pipe.Controller], esi
|
||||
jnz @b
|
||||
; subtract pipe bandwidth
|
||||
; TODO: calculate real bandwidth
|
||||
mov eax, [.maxpacket]
|
||||
and eax, (1 shl 11) - 1
|
||||
sub [edx+usb_static_ep.Bandwidth], eax
|
||||
ret 8
|
||||
endp
|
||||
|
||||
; USB2 scheduler.
|
||||
; There are two parts: high-speed pipes and split-transaction pipes.
|
||||
; Split-transaction scheduler is currently a stub.
|
||||
; High-speed scheduler uses the same algorithm as USB1 scheduler:
|
||||
; when adding a pipe, optimize the following quantity:
|
||||
; * for every microframe, take all bandwidth scheduled to periodic transfers,
|
||||
; * calculate maximum over all microframe,
|
||||
; * select a variant which minimizes that maximum;
|
||||
; when removing a pipe, do nothing (except for bookkeeping).
|
||||
; in: esi -> usb_controller
|
||||
; out: edx -> usb_static_ep, eax = S-Mask
|
||||
proc ehci_select_hs_interrupt_list
|
||||
; inherit some variables from usb_open_pipe
|
||||
virtual at ebp-12
|
||||
.targetsmask dd ?
|
||||
.bandwidth dd ?
|
||||
.target dd ?
|
||||
dd ?
|
||||
dd ?
|
||||
.config_pipe dd ?
|
||||
.endpoint dd ?
|
||||
.maxpacket dd ?
|
||||
.type dd ?
|
||||
.interval dd ?
|
||||
end virtual
|
||||
; prolog, initialize local vars
|
||||
or [.bandwidth], -1
|
||||
or [.target], -1
|
||||
or [.targetsmask], -1
|
||||
push ebx edi ; save used registers to be stdcall
|
||||
; 1. In EHCI, every list describes one millisecond = 8 microframes.
|
||||
; Thus, there are two significantly different branches:
|
||||
; for pipes with interval >= 8 microframes, advance to 2,
|
||||
; for pipes which should be planned in every frame (one or more microframes),
|
||||
; go to 9.
|
||||
; Note: the actual interval for high-speed devices is 2^([.interval]-1),
|
||||
; (the core specification forbids [.interval] == 0)
|
||||
mov ecx, [.interval]
|
||||
dec ecx
|
||||
cmp ecx, 3
|
||||
jb .every_frame
|
||||
; 2. Determine the actual interval in milliseconds.
|
||||
sub ecx, 3
|
||||
cmp ecx, 5 ; maximum 32ms
|
||||
jbe @f
|
||||
push 5
|
||||
pop ecx
|
||||
@@:
|
||||
; There are four nested loops,
|
||||
; * Loop #4 (the innermost one) calculates the total periodic bandwidth
|
||||
; scheduled in the given microframe of the given millisecond.
|
||||
; * Loop #3 calculates the maximum over all milliseconds
|
||||
; in the given variant, that is the quantity we're trying to minimize.
|
||||
; * Loops #1 and #2 check all variants;
|
||||
; loop #1 is responsible for the target millisecond,
|
||||
; loop #2 is responsible for the microframe within millisecond.
|
||||
; 3. Prepare for loops.
|
||||
; ebx = number of iterations of loop #1
|
||||
; [esp] = delta of counter for loop #3, in bytes
|
||||
; [esp+4] = delta between the first group and the target group, in bytes
|
||||
push 1
|
||||
pop ebx
|
||||
push sizeof.ehci_static_ep
|
||||
pop edx
|
||||
shl ebx, cl
|
||||
shl edx, cl
|
||||
mov eax, 64*sizeof.ehci_static_ep
|
||||
sub eax, edx
|
||||
sub eax, edx
|
||||
push eax
|
||||
push edx
|
||||
; 4. Select the best variant.
|
||||
; 4a. Loop #1: initialize counter = pointer to ehci_static_ep for
|
||||
; the target millisecond in the first group.
|
||||
lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller]
|
||||
.varloop0:
|
||||
; 4b. Loop #2: initialize counter = microframe within the target millisecond.
|
||||
xor ecx, ecx
|
||||
.varloop:
|
||||
; 4c. Loop #3: save counter of loop #1,
|
||||
; initialize counter with the value of loop #1 counter,
|
||||
; initialize maximal bandwidth = zero.
|
||||
xor edi, edi
|
||||
push edx
|
||||
virtual at esp
|
||||
.saved_counter1 dd ? ; step 4c
|
||||
.loop3_delta dd ? ; step 3
|
||||
.target_delta dd ? ; step 3
|
||||
end virtual
|
||||
.calc_max_bandwidth:
|
||||
; 4d. Loop #4: initialize counter with the value of loop #3 counter,
|
||||
; initialize total bandwidth = zero.
|
||||
xor eax, eax
|
||||
push edx
|
||||
.calc_bandwidth:
|
||||
; 4e. Loop #4: add the bandwidth from the current list
|
||||
; and advance to the next list, while there is one.
|
||||
add ax, [edx+ehci_static_ep.Bandwidths+ecx*2]
|
||||
mov edx, [edx+ehci_static_ep.NextList]
|
||||
test edx, edx
|
||||
jnz .calc_bandwidth
|
||||
; 4f. Loop #4 end: restore counter of loop #3.
|
||||
pop edx
|
||||
; 4g. Loop #3: update maximal bandwidth.
|
||||
cmp eax, edi
|
||||
jb @f
|
||||
mov edi, eax
|
||||
@@:
|
||||
; 4h. Loop #3: advance the counter and repeat while within the first group.
|
||||
lea eax, [esi+ehci_controller.IntEDs+32*sizeof.ehci_static_ep-sizeof.ehci_controller]
|
||||
add edx, [.loop3_delta]
|
||||
cmp edx, eax
|
||||
jb .calc_max_bandwidth
|
||||
; 4i. Loop #3 end: restore counter of loop #1.
|
||||
pop edx
|
||||
; 4j. Loop #2: if the current variant is better (maybe not strictly)
|
||||
; then the previous optimum, update the optimal bandwidth and the target.
|
||||
cmp edi, [.bandwidth]
|
||||
ja @f
|
||||
mov [.bandwidth], edi
|
||||
mov [.target], edx
|
||||
push 1
|
||||
pop eax
|
||||
shl eax, cl
|
||||
mov [.targetsmask], eax
|
||||
@@:
|
||||
; 4k. Loop #2: continue 8 times for every microframe.
|
||||
inc ecx
|
||||
cmp ecx, 8
|
||||
jb .varloop
|
||||
; 4l. Loop #1: advance counter and repeat ebx times,
|
||||
; ebx was calculated in step 3.
|
||||
add edx, sizeof.ehci_static_ep
|
||||
dec ebx
|
||||
jnz .varloop0
|
||||
; 5. Get the pointer to the best list.
|
||||
pop edx ; restore value from step 3
|
||||
pop edx ; get delta calculated in step 3
|
||||
add edx, [.target]
|
||||
; 6. Calculate bandwidth for the new pipe.
|
||||
; TODO1: calculate real bandwidth
|
||||
mov eax, [.maxpacket]
|
||||
mov ecx, eax
|
||||
and eax, (1 shl 11) - 1
|
||||
shr ecx, 11
|
||||
inc ecx
|
||||
and ecx, 3
|
||||
imul eax, ecx
|
||||
; 7. TODO2: check that bandwidth for the new pipe plus old bandwidth
|
||||
; still fits to maximum allowed by the core specification
|
||||
; current [.bandwidth] + new bandwidth <= limit;
|
||||
; USB2 specification allows maximum 60000*80% bit times for periodic microframe
|
||||
; 8. Convert {o|u}hci_static_ep to usb_static_ep, update bandwidth and return.
|
||||
mov ecx, [.targetsmask]
|
||||
add [edx+ehci_static_ep.Bandwidths+ecx*2], ax
|
||||
add edx, ehci_static_ep.SoftwarePart
|
||||
push 1
|
||||
pop eax
|
||||
shl eax, cl
|
||||
pop edi ebx ; restore used registers to be stdcall
|
||||
ret
|
||||
.every_frame:
|
||||
; The pipe should be scheduled every frame in two or more microframes.
|
||||
; 9. Calculate maximal bandwidth for every microframe: three nested loops.
|
||||
; 9a. The outermost loop: ebx = microframe to calculate.
|
||||
xor ebx, ebx
|
||||
.calc_all_bandwidths:
|
||||
; 9b. The intermediate loop:
|
||||
; edx = pointer to ehci_static_ep in the first group, [esp] = counter,
|
||||
; edi = maximal bandwidth
|
||||
lea edx, [esi+ehci_controller.IntEDs-sizeof.ehci_controller]
|
||||
xor edi, edi
|
||||
push 32
|
||||
.calc_max_bandwidth2:
|
||||
; 9c. The innermost loop: calculate bandwidth for the given microframe
|
||||
; in the given frame.
|
||||
xor eax, eax
|
||||
push edx
|
||||
.calc_bandwidth2:
|
||||
add ax, [edx+ehci_static_ep.Bandwidths+ebx*2]
|
||||
mov edx, [edx+ehci_static_ep.NextList]
|
||||
test edx, edx
|
||||
jnz .calc_bandwidth2
|
||||
pop edx
|
||||
; 9d. The intermediate loop continued: update maximal bandwidth.
|
||||
cmp eax, edi
|
||||
jb @f
|
||||
mov edi, eax
|
||||
@@:
|
||||
add edx, sizeof.ehci_static_ep
|
||||
dec dword [esp]
|
||||
jnz .calc_max_bandwidth2
|
||||
pop eax
|
||||
; 9e. Push the calculated maximal bandwidth and continue the outermost loop.
|
||||
push edi
|
||||
inc ebx
|
||||
cmp ebx, 8
|
||||
jb .calc_all_bandwidths
|
||||
virtual at esp
|
||||
.bandwidth7 dd ?
|
||||
.bandwidth6 dd ?
|
||||
.bandwidth5 dd ?
|
||||
.bandwidth4 dd ?
|
||||
.bandwidth3 dd ?
|
||||
.bandwidth2 dd ?
|
||||
.bandwidth1 dd ?
|
||||
.bandwidth0 dd ?
|
||||
end virtual
|
||||
; 10. Select the best variant.
|
||||
; edx = S-Mask = bitmask of scheduled microframes
|
||||
push 0x11
|
||||
pop edx
|
||||
cmp ecx, 1
|
||||
ja @f
|
||||
mov dl, 0x55
|
||||
jz @f
|
||||
mov dl, 0xFF
|
||||
@@:
|
||||
; try all variants edx, edx shl 1, edx shl 2, ...
|
||||
; until they fit in the lower byte (8 microframes per frame)
|
||||
.select_best_mframe:
|
||||
xor edi, edi
|
||||
mov ecx, edx
|
||||
mov eax, esp
|
||||
.calc_mframe:
|
||||
add cl, cl
|
||||
jnc @f
|
||||
cmp edi, [eax]
|
||||
jae @f
|
||||
mov edi, [eax]
|
||||
@@:
|
||||
add eax, 4
|
||||
test cl, cl
|
||||
jnz .calc_mframe
|
||||
cmp [.bandwidth], edi
|
||||
jb @f
|
||||
mov [.bandwidth], edi
|
||||
mov [.targetsmask], edx
|
||||
@@:
|
||||
add dl, dl
|
||||
jnc .select_best_mframe
|
||||
; 11. Restore stack after step 9.
|
||||
add esp, 8*4
|
||||
; 12. Get the pointer to the target list (responsible for every microframe).
|
||||
lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+62*sizeof.ehci_static_ep-sizeof.ehci_controller]
|
||||
; 13. TODO1: calculate real bandwidth.
|
||||
mov eax, [.maxpacket]
|
||||
mov ecx, eax
|
||||
and eax, (1 shl 11) - 1
|
||||
shr ecx, 11
|
||||
inc ecx
|
||||
and ecx, 3
|
||||
imul eax, ecx
|
||||
; 14. TODO2: check that current [.bandwidth] + new bandwidth <= limit;
|
||||
; USB2 specification allows maximum 60000*80% bit times for periodic microframe.
|
||||
; Update bandwidths including the new pipe.
|
||||
mov ecx, [.targetsmask]
|
||||
lea edi, [edx+ehci_static_ep.Bandwidths-ehci_static_ep.SoftwarePart]
|
||||
.update_bandwidths:
|
||||
shr ecx, 1
|
||||
jnc @f
|
||||
add [edi], ax
|
||||
@@:
|
||||
add edi, 2
|
||||
test ecx, ecx
|
||||
jnz .update_bandwidths
|
||||
; 15. Return target list and target S-Mask.
|
||||
mov eax, [.targetsmask]
|
||||
pop edi ebx ; restore used registers to be stdcall
|
||||
ret
|
||||
endp
|
||||
|
||||
; Pipe is removing, update the corresponding lists.
|
||||
; We do not reorder anything, so just update book-keeping variable
|
||||
; in the list header.
|
||||
proc ehci_hs_interrupt_list_unlink
|
||||
; get target list
|
||||
mov edx, [ebx+ehci_pipe.BaseList-ehci_pipe.SoftwarePart]
|
||||
; TODO: calculate real bandwidth
|
||||
movzx eax, word [ebx+ehci_pipe.Token-ehci_pipe.SoftwarePart+2]
|
||||
mov ecx, [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart]
|
||||
and eax, (1 shl 11) - 1
|
||||
shr ecx, 30
|
||||
imul eax, ecx
|
||||
movzx ecx, byte [ebx+ehci_pipe.Flags-ehci_pipe.SoftwarePart]
|
||||
add edx, ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart
|
||||
; update bandwidth
|
||||
.dec_bandwidth:
|
||||
shr ecx, 1
|
||||
jnc @f
|
||||
sub [edx], ax
|
||||
@@:
|
||||
add edx, 2
|
||||
test ecx, ecx
|
||||
jnz .dec_bandwidth
|
||||
; return
|
||||
ret
|
||||
endp
|
||||
|
||||
uglobal
|
||||
ehci_last_fs_alloc dd ?
|
||||
endg
|
||||
|
||||
; This needs to be rewritten. Seriously.
|
||||
; It schedules everything to the first microframe of some frame,
|
||||
; frame is spinned out of thin air.
|
||||
; This works while you have one keyboard and one mouse...
|
||||
; maybe even ten keyboards and ten mice... but give any serious stress,
|
||||
; and this would break.
|
||||
proc ehci_select_fs_interrupt_list
|
||||
virtual at ebp-12
|
||||
.targetsmask dd ?
|
||||
.bandwidth dd ?
|
||||
.target dd ?
|
||||
dd ?
|
||||
dd ?
|
||||
.config_pipe dd ?
|
||||
.endpoint dd ?
|
||||
.maxpacket dd ?
|
||||
.type dd ?
|
||||
.interval dd ?
|
||||
end virtual
|
||||
cmp [.interval], 1
|
||||
adc [.interval], 0
|
||||
mov ecx, 64
|
||||
mov eax, ecx
|
||||
@@:
|
||||
shr ecx, 1
|
||||
cmp [.interval], ecx
|
||||
jb @b
|
||||
sub eax, ecx
|
||||
sub eax, ecx
|
||||
dec ecx
|
||||
and ecx, [ehci_last_fs_alloc]
|
||||
inc [ehci_last_fs_alloc]
|
||||
add eax, ecx
|
||||
imul eax, sizeof.ehci_static_ep
|
||||
lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+eax-sizeof.ehci_controller]
|
||||
mov ax, 1C01h
|
||||
ret
|
||||
endp
|
||||
|
||||
proc ehci_fs_interrupt_list_unlink
|
||||
ret
|
||||
endp
|
1817
kernel/trunk/bus/usb/uhci.inc
Normal file
1817
kernel/trunk/bus/usb/uhci.inc
Normal file
File diff suppressed because it is too large
Load Diff
@ -634,6 +634,17 @@ struct SRV
|
||||
srv_proc_ex dd ? ;+0x2C ;kernel mode service handler
|
||||
ends
|
||||
|
||||
struct USBSRV
|
||||
srv SRV
|
||||
usb_func dd ?
|
||||
ends
|
||||
|
||||
struct USBFUNC
|
||||
strucsize dd ?
|
||||
add_device dd ?
|
||||
device_disconnect dd ?
|
||||
ends
|
||||
|
||||
DRV_ENTRY equ 1
|
||||
DRV_EXIT equ -1
|
||||
|
||||
|
@ -142,7 +142,11 @@ proc srv_handler stdcall, ioctl:dword
|
||||
cmp [edi+SRV.size], sizeof.SRV
|
||||
jne .fail
|
||||
|
||||
stdcall [edi+SRV.srv_proc], esi
|
||||
; stdcall [edi+SRV.srv_proc], esi
|
||||
mov eax, [edi+SRV.srv_proc]
|
||||
test eax, eax
|
||||
jz .fail
|
||||
stdcall eax, esi
|
||||
ret
|
||||
.fail:
|
||||
xor eax, eax
|
||||
@ -174,7 +178,11 @@ srv_handlerEx:
|
||||
cmp [eax+SRV.size], sizeof.SRV
|
||||
jne .fail
|
||||
|
||||
stdcall [eax+SRV.srv_proc], ecx
|
||||
; stdcall [eax+SRV.srv_proc], ecx
|
||||
mov eax, [eax+SRV.srv_proc]
|
||||
test eax, eax
|
||||
jz .fail
|
||||
stdcall eax, ecx
|
||||
ret
|
||||
.fail:
|
||||
or eax, -1
|
||||
@ -213,8 +221,30 @@ proc get_service stdcall, sz_name:dword
|
||||
ret
|
||||
endp
|
||||
|
||||
align 4
|
||||
proc reg_service stdcall, name:dword, handler:dword
|
||||
reg_service:
|
||||
xor eax, eax
|
||||
mov ecx, [esp+8]
|
||||
jecxz .nothing
|
||||
push sizeof.SRV
|
||||
push ecx
|
||||
pushd [esp+12]
|
||||
call reg_service_ex
|
||||
.nothing:
|
||||
ret 8
|
||||
|
||||
reg_usb_driver:
|
||||
push sizeof.USBSRV
|
||||
pushd [esp+12]
|
||||
pushd [esp+12]
|
||||
call reg_service_ex
|
||||
test eax, eax
|
||||
jz .nothing
|
||||
mov ecx, [esp+12]
|
||||
mov [eax+USBSRV.usb_func], ecx
|
||||
.nothing:
|
||||
ret 12
|
||||
|
||||
proc reg_service_ex stdcall, name:dword, handler:dword, srvsize:dword
|
||||
|
||||
push ebx
|
||||
|
||||
@ -223,10 +253,10 @@ proc reg_service stdcall, name:dword, handler:dword
|
||||
cmp [name], eax
|
||||
je .fail
|
||||
|
||||
cmp [handler], eax
|
||||
je .fail
|
||||
; cmp [handler], eax
|
||||
; je .fail
|
||||
|
||||
mov eax, sizeof.SRV
|
||||
mov eax, [srvsize]
|
||||
call malloc
|
||||
test eax, eax
|
||||
jz .fail
|
||||
|
@ -550,7 +550,7 @@ proc destroy_app_space stdcall, pg_dir:dword, dlls_list:dword
|
||||
|
||||
xor edx, edx
|
||||
push edx
|
||||
mov eax, 0x2
|
||||
mov eax, 0x1
|
||||
mov ebx, [pg_dir]
|
||||
.loop:
|
||||
;eax = current slot of process
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
; Detect all BIOS hard drives.
|
||||
; diamond, 2008
|
||||
; Do not include USB mass storages. CleverMouse, 2013
|
||||
|
||||
xor cx, cx
|
||||
mov es, cx
|
||||
@ -24,21 +25,40 @@ bdds:
|
||||
test ah, ah
|
||||
jz bddc
|
||||
inc cx
|
||||
; We are going to call int 13h/func 48h, Extended get drive parameters.
|
||||
; The latest version of the EDD specification is 3.0.
|
||||
; There are two slightly incompatible variants for version 3.0;
|
||||
; original one from Phoenix in 1998, see e.g.
|
||||
; http://www.t10.org/t13/technical/d98120r0.pdf, and T13 draft,
|
||||
; http://www.t13.org/documents/UploadedDocuments/docs2004/d1572r3-EDD3.pdf
|
||||
; T13 draft addresses more possible buses, so it gives additional 8 bytes
|
||||
; for device path.
|
||||
; Most BIOSes follow Phoenix, but T13 version is also known to be used
|
||||
; (e.g. systems based on AMD Geode).
|
||||
; Fortunately, there is an in/out length field, so
|
||||
; it is easy to tell what variant was selected by the BIOS:
|
||||
; Phoenix-3.0 has 42h bytes, T13-3.0 has 4Ah bytes.
|
||||
; Note that 2.0 has 1Eh bytes, 1.1 has 1Ah bytes; both variants of 3.0 have
|
||||
; the same structure for first 1Eh bytes, compatible with previous versions.
|
||||
; Note also that difference between Phoenix-3.0 and T13-3.0 starts near the
|
||||
; end of the structure, so the current code doesn't even need to distinguish.
|
||||
; It needs, however, give at least 4Ah bytes as input and expect that BIOS
|
||||
; could return 42h bytes as output while still giving all the information.
|
||||
mov ah, 48h
|
||||
push ds
|
||||
push es
|
||||
pop ds
|
||||
mov si, 0xA000
|
||||
mov word [si], 1Eh
|
||||
mov word [si], 4Ah
|
||||
mov ah, 48h
|
||||
int 13h
|
||||
pop ds
|
||||
jc bddc2
|
||||
inc byte [es:0x907F]
|
||||
cmp word [es:si], 1Eh
|
||||
jb bddl
|
||||
jb .noide
|
||||
cmp word [es:si+1Ah], 0xFFFF
|
||||
jz bddl
|
||||
jz .noide
|
||||
inc byte [es:0x907F]
|
||||
mov al, dl
|
||||
stosb
|
||||
push ds
|
||||
@ -61,7 +81,15 @@ bdds:
|
||||
stosw
|
||||
pop ds
|
||||
jmp bddc2
|
||||
bddl:
|
||||
.noide:
|
||||
cmp word [es:si], 42h
|
||||
jb .nousb
|
||||
cmp word [es:si+28h], 'US'
|
||||
jnz .nousb
|
||||
cmp byte [es:si+2Ah], 'B'
|
||||
jz bddc2
|
||||
.nousb:
|
||||
inc byte [es:0x907F]
|
||||
mov al, dl
|
||||
stosb
|
||||
xor ax, ax
|
||||
|
183
kernel/trunk/docs/usbapi.txt
Normal file
183
kernel/trunk/docs/usbapi.txt
Normal file
@ -0,0 +1,183 @@
|
||||
When the kernel detects a connected USB device, it configures the device in
|
||||
terms of USB protocol - SET_ADDRESS + SET_CONFIGURATION, the first
|
||||
configuration is always selected. The kernel also reads device descriptor to
|
||||
print some information, reads and parses configuration descriptor. For every
|
||||
interface the kernel looks for class code of this interface and loads the
|
||||
corresponding COFF driver. Currently the correspondence is hardcoded into
|
||||
the kernel code and looks as follows: 3 = usbhid.obj, 8 = usbstor.obj,
|
||||
9 is handled by the kernel itself, other = usbother.obj.
|
||||
|
||||
The driver must be standard driver in COFF format, exporting procedure
|
||||
named "START" and a variable named "version". Loader calls "START" procedure
|
||||
as stdcall with one parameter DRV_ENTRY = 1; if initialization is successful,
|
||||
the "START" procedure is also called by shutdown code with one parameter
|
||||
DRV_EXIT = -1.
|
||||
|
||||
The driver must register itself as a USB driver in "START" procedure.
|
||||
This is done by call to exported function RegUSBDriver and passing the returned
|
||||
value as result of "START" procedure.
|
||||
|
||||
void* __stdcall RegUSBDriver(
|
||||
const char* name,
|
||||
void* handler,
|
||||
const USBFUNC* usbfunc
|
||||
);
|
||||
|
||||
The parameter 'name' should match the name of driver, "usbhid" for usbhid.obj.
|
||||
The parameter 'handler' is optional; if it is non-NULL, it should point to
|
||||
the standard handler for IOCTL interface as in non-USB drivers.
|
||||
The parameter 'usbfunc' is a pointer to the following structure:
|
||||
|
||||
struc USBFUNC
|
||||
{
|
||||
.strucsize dd ? ; size of the structure, including this field
|
||||
.add_device dd ? ; pointer to AddDevice function in the driver
|
||||
; required
|
||||
.device_disconnect dd ? ; pointer to DeviceDisconnected function in the driver
|
||||
; optional, may be NULL
|
||||
; other functions may be added in the future
|
||||
}
|
||||
|
||||
The driver should implement the function
|
||||
|
||||
void* __stdcall AddDevice(
|
||||
void* pipe0,
|
||||
void* configdescr,
|
||||
void* interfacedescr
|
||||
);
|
||||
|
||||
The parameter 'controlpipe' is a handle of the control pipe for endpoint zero
|
||||
of the device. It can be used as the argument of USBControlTransferAsync.
|
||||
The parameter 'configdescr' points to USB configuration descriptor
|
||||
and all associated data, as returned by GET_DESCRIPTOR request.
|
||||
The total length of all associated data is contained in the configuration
|
||||
descriptor.
|
||||
The parameter 'interfacedescr' points to USB interface descriptor corresponding
|
||||
to the interface which is initializing. This is a pointer inside data
|
||||
associated with the configuration descriptor.
|
||||
Note that one device can implement many interfaces, so AddDevice may be
|
||||
called several times with the same 'configdescr' and different 'interfacedescr'.
|
||||
The returned value NULL means that the initialization has failed.
|
||||
Any other value means that configuration was successful; the kernel does not
|
||||
try to interpret the value. It can be, for example, pointer to the internal
|
||||
data allocated with Kmalloc, or index in some internal table. Remember that
|
||||
Kmalloc() is NOT stdcall, it destroys ebx.
|
||||
|
||||
The driver can implement the function
|
||||
|
||||
void __stdcall DeviceDisconnected(
|
||||
void* devicedata
|
||||
);
|
||||
|
||||
If this function is implemented, the kernel calls it when the device is
|
||||
disconnected, passing the returned value of AddDevice as 'devicedata'.
|
||||
|
||||
The driver can use the following functions exported by the kernel.
|
||||
|
||||
void* __stdcall USBOpenPipe(
|
||||
void* pipe0,
|
||||
int endpoint,
|
||||
int maxpacketsize,
|
||||
int type,
|
||||
int interval
|
||||
);
|
||||
|
||||
The parameter 'pipe0' is a handle of the pipe for endpoint zero for
|
||||
the device, as passed to AddDevice. It is used to identify the device.
|
||||
The parameter 'endpoint' is endpoint number as defined by USB. Lower
|
||||
4 bits form the number itself, bit 7 - highest bit of low byte -
|
||||
is 0/1 for OUT/IN endpoints, other bits should be zero.
|
||||
The parameter 'maxpacketsize' sets the maximum packet size for this pipe.
|
||||
The parameter 'type' selects the type of the endpoint as defined by USB:
|
||||
0 = control, 1 = isochronous (not supported yet), 2 = bulk, 3 = interrupt.
|
||||
The parameter 'interval' is ignored for control and bulk endpoints.
|
||||
For interrupt endpoints, it sets the polling interval in milliseconds.
|
||||
The function returns a handle to the pipe or NULL on failure.
|
||||
|
||||
void* __stdcall USBNormalTransferAsync(
|
||||
void* pipe,
|
||||
void* buffer,
|
||||
int size,
|
||||
void* callback,
|
||||
void* calldata,
|
||||
int flags
|
||||
);
|
||||
void* __stdcall USBControlTransferAsync(
|
||||
void* pipe,
|
||||
void* config,
|
||||
void* buffer,
|
||||
int size,
|
||||
void* callback,
|
||||
void* calldata,
|
||||
int flags
|
||||
);
|
||||
|
||||
The first function inserts a bulk or interrupt transfer to the transfer queue
|
||||
for given pipe. Type and direction of transfer are fixed for bulk and interrupt
|
||||
endpoints and are set in USBOpenPipe. The second function inserts a control
|
||||
transfer to the transfer queue for given pipe. Direction of a control transfer
|
||||
is concluded from 'config' packet, bit 7 of byte 0 is set for IN transfers
|
||||
and cleared for OUT transfers. These function return immediately; when data
|
||||
are transferred, the callback function will be called.
|
||||
|
||||
The parameter 'pipe' is a handle returned by USBOpenPipe.
|
||||
The parameter 'config' of USBControlTransferAsync points to 8-byte
|
||||
configuration packet as defined by USB.
|
||||
The parameter 'buffer' is a pointer to buffer. For IN transfers, it will be
|
||||
filled with the data. For OUT transfers, it should contain data to be
|
||||
transferred. It can be NULL for an empty transfer or if no additional data are
|
||||
required for a control transfer.
|
||||
The parameter 'size' is size of data to transfer. It can be 0 for an empty
|
||||
transfer or if no additional data are required for a control transfer.
|
||||
The parameter 'callback' is a pointer to a function which will be called
|
||||
when the transfer will be done.
|
||||
The parameter 'calldata' will be passed as is to the callback function.
|
||||
For example, it can be NULL, it can be a pointer to device data or it can be
|
||||
a pointer to data used to pass additional parameters between caller and
|
||||
callback. The transfer-specific data can also be associated with 'buffer',
|
||||
preceding (negative offsets from 'buffer') or following (offsets more than
|
||||
or equal to 'size') the buffer itself.
|
||||
The parameter 'flags' is the bitmask.
|
||||
The bit 0 is ignored for OUT transfers, for IN transfers it controls whether
|
||||
the device can transfer less data than 'size' bytes. If the bit is 0, a small
|
||||
transfer is an error; if the bit is 1, a small transfer is OK.
|
||||
All other bits are reserved and should be zero.
|
||||
The returned value is NULL if an error occured and non-NULL if the transfer
|
||||
was successfully queued. If an error will occur later, the callback function
|
||||
will be notified.
|
||||
|
||||
void __stdcall CallbackFunction(
|
||||
void* pipe,
|
||||
int status,
|
||||
void* buffer,
|
||||
int length,
|
||||
void* calldata
|
||||
);
|
||||
|
||||
The parameters 'pipe', 'buffer', 'calldata' are the same as for the
|
||||
corresponding USB*TransferAsync.
|
||||
The parameter 'length' is the number of bytes transferred. For
|
||||
control transfers, this includes 8 bytes from SETUP stage, so
|
||||
0 means that SETUP stage failed and 'size'+8 means full transfer.
|
||||
The parameter 'status' is nonzero if an error occured.
|
||||
USB_STATUS_OK = 0 ; no error
|
||||
USB_STATUS_CRC = 1 ; CRC error
|
||||
USB_STATUS_BITSTUFF = 2 ; bit stuffing violation
|
||||
USB_STATUS_TOGGLE = 3 ; data toggle mismatch
|
||||
USB_STATUS_STALL = 4 ; device returned STALL
|
||||
USB_STATUS_NORESPONSE = 5 ; device not responding
|
||||
USB_STATUS_PIDCHECK = 6 ; invalid PID check bits
|
||||
USB_STATUS_WRONGPID = 7 ; unexpected PID value
|
||||
USB_STATUS_OVERRUN = 8 ; too many data from endpoint
|
||||
USB_STATUS_UNDERRUN = 9 ; too few data from endpoint
|
||||
USB_STATUS_BUFOVERRUN = 12 ; overflow of internal controller buffer
|
||||
; possible only for isochronous transfers
|
||||
USB_STATUS_BUFUNDERRUN = 13 ; underflow of internal controller buffer
|
||||
; possible only for isochronous transfers
|
||||
USB_STATUS_DISCONNECTED = 16 ; device disconnected
|
||||
|
||||
If several transfers are queued for the same pipe, their callback functions
|
||||
are called in the same order as they were queued.
|
||||
When the device is disconnected, all callback functions are called
|
||||
with USB_STATUS_DISCONNECTED. The call to DeviceDisconnected() occurs after
|
||||
all callbacks.
|
439
kernel/trunk/drivers/fdo.inc
Normal file
439
kernel/trunk/drivers/fdo.inc
Normal file
@ -0,0 +1,439 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ;;
|
||||
;; Copyright (C) KolibriOS team 2004-2007. All rights reserved. ;;
|
||||
;; Distributed under terms of the GNU General Public License ;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
|
||||
;
|
||||
; Formatted Debug Output (FDO)
|
||||
; Copyright (c) 2005-2006, mike.dld
|
||||
; Created: 2005-01-29, Changed: 2006-11-10
|
||||
;
|
||||
; For questions and bug reports, mail to mike.dld@gmail.com
|
||||
;
|
||||
; Available format specifiers are: %s, %d, %u, %x (with partial width support)
|
||||
;
|
||||
|
||||
; to be defined:
|
||||
; __DEBUG__ equ 1
|
||||
; __DEBUG_LEVEL__ equ 5
|
||||
|
||||
macro debug_func name {
|
||||
if used name
|
||||
name@of@func equ name
|
||||
}
|
||||
|
||||
macro debug_beginf {
|
||||
align 4
|
||||
name@of@func:
|
||||
}
|
||||
|
||||
debug_endf fix end if
|
||||
|
||||
macro DEBUGS _sign,[_str] {
|
||||
common
|
||||
local tp
|
||||
tp equ 0
|
||||
match _arg:_num,_str \{
|
||||
DEBUGS_N _sign,_num,_arg
|
||||
tp equ 1
|
||||
\}
|
||||
match =0 _arg,tp _str \{
|
||||
DEBUGS_N _sign,,_arg
|
||||
\}
|
||||
}
|
||||
|
||||
macro DEBUGS_N _sign,_num,[_str] {
|
||||
common
|
||||
pushf
|
||||
pushad
|
||||
local ..str,..label,is_str
|
||||
is_str = 0
|
||||
forward
|
||||
if _str eqtype ''
|
||||
is_str = 1
|
||||
end if
|
||||
common
|
||||
if is_str = 1
|
||||
jmp ..label
|
||||
..str db _str,0
|
||||
..label:
|
||||
add esp, 4*8+4
|
||||
mov edx, ..str
|
||||
sub esp, 4*8+4
|
||||
else
|
||||
mov edx, _str
|
||||
end if
|
||||
if ~_num eq
|
||||
if _num eqtype eax
|
||||
if _num in <eax,ebx,ecx,edx,edi,ebp,esp>
|
||||
mov esi, _num
|
||||
else if ~_num eq esi
|
||||
movzx esi, _num
|
||||
end if
|
||||
else if _num eqtype 0
|
||||
mov esi, _num
|
||||
else
|
||||
local tp
|
||||
tp equ 0
|
||||
match [_arg],_num \{
|
||||
mov esi, dword[_arg]
|
||||
tp equ 1
|
||||
\}
|
||||
match =0 =dword[_arg],tp _num \{
|
||||
mov esi, dword[_arg]
|
||||
tp equ 1
|
||||
\}
|
||||
match =0 =word[_arg],tp _num \{
|
||||
movzx esi, word[_arg]
|
||||
tp equ 1
|
||||
\}
|
||||
match =0 =byte[_arg],tp _num \{
|
||||
movzx esi, byte[_arg]
|
||||
tp equ 1
|
||||
\}
|
||||
match =0,tp \{
|
||||
'Error: specified string width is incorrect'
|
||||
\}
|
||||
end if
|
||||
else
|
||||
mov esi, 0x7FFFFFFF
|
||||
end if
|
||||
call fdo_debug_outstr
|
||||
popad
|
||||
popf
|
||||
}
|
||||
|
||||
macro DEBUGD _sign,_dec {
|
||||
local tp
|
||||
tp equ 0
|
||||
match _arg:_num,_dec \{
|
||||
DEBUGD_N _sign,_num,_arg
|
||||
tp equ 1
|
||||
\}
|
||||
match =0 _arg,tp _dec \{
|
||||
DEBUGD_N _sign,,_arg
|
||||
\}
|
||||
}
|
||||
|
||||
macro DEBUGD_N _sign,_num,_dec {
|
||||
pushf
|
||||
pushad
|
||||
if (~_num eq)
|
||||
if (_dec eqtype eax | _dec eqtype 0)
|
||||
'Error: precision allowed only for in-memory variables'
|
||||
end if
|
||||
if (~_num in <1,2,4>)
|
||||
if _sign
|
||||
'Error: 1, 2 and 4 are only allowed for precision in %d'
|
||||
else
|
||||
'Error: 1, 2 and 4 are only allowed for precision in %u'
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
if _dec eqtype eax
|
||||
if _dec in <ebx,ecx,edx,esi,edi,ebp,esp>
|
||||
mov eax, _dec
|
||||
else if ~_dec eq eax
|
||||
if _sign = 1
|
||||
movsx eax, _dec
|
||||
else
|
||||
movzx eax, _dec
|
||||
end if
|
||||
end if
|
||||
else if _dec eqtype 0
|
||||
mov eax, _dec
|
||||
else
|
||||
add esp, 4*8+4
|
||||
if _num eq
|
||||
mov eax, dword _dec
|
||||
else if _num = 1
|
||||
if _sign = 1
|
||||
movsx eax, byte _dec
|
||||
else
|
||||
movzx eax, byte _dec
|
||||
end if
|
||||
else if _num = 2
|
||||
if _sign = 1
|
||||
movsx eax, word _dec
|
||||
else
|
||||
movzx eax, word _dec
|
||||
end if
|
||||
else
|
||||
mov eax, dword _dec
|
||||
end if
|
||||
sub esp, 4*8+4
|
||||
end if
|
||||
mov cl, _sign
|
||||
call fdo_debug_outdec
|
||||
popad
|
||||
popf
|
||||
}
|
||||
|
||||
macro DEBUGH _sign,_hex {
|
||||
local tp
|
||||
tp equ 0
|
||||
match _arg:_num,_hex \{
|
||||
DEBUGH_N _sign,_num,_arg
|
||||
tp equ 1
|
||||
\}
|
||||
match =0 _arg,tp _hex \{
|
||||
DEBUGH_N _sign,,_arg
|
||||
\}
|
||||
}
|
||||
|
||||
macro DEBUGH_N _sign,_num,_hex {
|
||||
pushf
|
||||
pushad
|
||||
if (~_num eq) & (~_num in <1,2,3,4,5,6,7,8>)
|
||||
'Error: 1..8 are only allowed for precision in %x'
|
||||
end if
|
||||
if _hex eqtype eax
|
||||
if _hex in <eax,ebx,ecx,edx,esi,edi,ebp,esp>
|
||||
if ~_hex eq eax
|
||||
mov eax, _hex
|
||||
end if
|
||||
mov edx, 8
|
||||
else if _hex in <ax,bx,cx,dx,si,di,bp,sp>
|
||||
if ~_hex eq ax
|
||||
movzx eax, _hex
|
||||
end if
|
||||
if (_num eq)
|
||||
mov edx, 4
|
||||
end if
|
||||
else if _hex in <al,ah,bl,bh,cl,ch,dl,dh>
|
||||
if ~_hex eq al
|
||||
movzx eax, _hex
|
||||
end if
|
||||
if (_num eq)
|
||||
mov edx, 2
|
||||
end if
|
||||
end if
|
||||
else if _hex eqtype 0
|
||||
mov eax, _hex
|
||||
else
|
||||
add esp, 4*8+4
|
||||
mov eax, dword _hex
|
||||
sub esp, 4*8+4
|
||||
end if
|
||||
if ~_num eq
|
||||
mov edx, _num
|
||||
else
|
||||
if ~_hex eqtype eax
|
||||
mov edx, 8
|
||||
end if
|
||||
end if
|
||||
call fdo_debug_outhex
|
||||
popad
|
||||
popf
|
||||
}
|
||||
|
||||
;-----------------------------------------------------------------------------
|
||||
|
||||
debug_func fdo_debug_outchar
|
||||
debug_beginf
|
||||
pushad
|
||||
movzx ebx, al
|
||||
mov eax, 1
|
||||
; mov ecx,sys_msg_board
|
||||
; call ecx ; sys_msg_board
|
||||
stdcall SysMsgBoardChar
|
||||
popad
|
||||
ret
|
||||
debug_endf
|
||||
|
||||
debug_func fdo_debug_outstr
|
||||
debug_beginf
|
||||
mov eax, 1
|
||||
.l1:
|
||||
dec esi
|
||||
js .l2
|
||||
movzx ebx, byte[edx]
|
||||
or bl, bl
|
||||
jz .l2
|
||||
; mov ecx,sys_msg_board
|
||||
; call ecx ; sys_msg_board
|
||||
stdcall SysMsgBoardChar
|
||||
inc edx
|
||||
jmp .l1
|
||||
.l2:
|
||||
ret
|
||||
debug_endf
|
||||
|
||||
debug_func fdo_debug_outdec
|
||||
debug_beginf
|
||||
or cl, cl
|
||||
jz @f
|
||||
or eax, eax
|
||||
jns @f
|
||||
neg eax
|
||||
push eax
|
||||
mov al, '-'
|
||||
call fdo_debug_outchar
|
||||
pop eax
|
||||
@@:
|
||||
push 10
|
||||
pop ecx
|
||||
push -'0'
|
||||
.l1:
|
||||
xor edx, edx
|
||||
div ecx
|
||||
push edx
|
||||
test eax, eax
|
||||
jnz .l1
|
||||
.l2:
|
||||
pop eax
|
||||
add al, '0'
|
||||
jz .l3
|
||||
call fdo_debug_outchar
|
||||
jmp .l2
|
||||
.l3:
|
||||
ret
|
||||
debug_endf
|
||||
|
||||
debug_func fdo_debug_outhex
|
||||
__fdo_hexdigits db '0123456789ABCDEF'
|
||||
debug_beginf
|
||||
mov cl, dl
|
||||
neg cl
|
||||
add cl, 8
|
||||
shl cl, 2
|
||||
rol eax, cl
|
||||
.l1:
|
||||
rol eax, 4
|
||||
push eax
|
||||
and eax, 0x0000000F
|
||||
mov al, [__fdo_hexdigits+eax]
|
||||
call fdo_debug_outchar
|
||||
pop eax
|
||||
dec edx
|
||||
jnz .l1
|
||||
ret
|
||||
debug_endf
|
||||
|
||||
;-----------------------------------------------------------------------------
|
||||
|
||||
macro DEBUGF _level,_format,[_arg] {
|
||||
common
|
||||
if __DEBUG__ = 1 & _level >= __DEBUG_LEVEL__
|
||||
local ..f1,f2,a1,a2,c1,c2,c3,..lbl
|
||||
_debug_str_ equ __debug_str_ # a1
|
||||
a1 = 0
|
||||
c2 = 0
|
||||
c3 = 0
|
||||
f2 = 0
|
||||
repeat ..lbl-..f1
|
||||
virtual at 0
|
||||
db _format,0,0
|
||||
load c1 word from %-1
|
||||
end virtual
|
||||
if c1 = '%s'
|
||||
virtual at 0
|
||||
db _format,0,0
|
||||
store word 0 at %-1
|
||||
load c1 from f2-c2
|
||||
end virtual
|
||||
if c1 <> 0
|
||||
DEBUGS 0,_debug_str_+f2-c2
|
||||
end if
|
||||
c2 = c2 + 1
|
||||
f2 = %+1
|
||||
DEBUGF_HELPER S,a1,0,_arg
|
||||
else if c1 = '%x'
|
||||
virtual at 0
|
||||
db _format,0,0
|
||||
store word 0 at %-1
|
||||
load c1 from f2-c2
|
||||
end virtual
|
||||
if c1 <> 0
|
||||
DEBUGS 0,_debug_str_+f2-c2
|
||||
end if
|
||||
c2 = c2 + 1
|
||||
f2 = %+1
|
||||
DEBUGF_HELPER H,a1,0,_arg
|
||||
else if c1 = '%d' | c1 = '%u'
|
||||
local c4
|
||||
if c1 = '%d'
|
||||
c4 = 1
|
||||
else
|
||||
c4 = 0
|
||||
end if
|
||||
virtual at 0
|
||||
db _format,0,0
|
||||
store word 0 at %-1
|
||||
load c1 from f2-c2
|
||||
end virtual
|
||||
if c1 <> 0
|
||||
DEBUGS 0,_debug_str_+f2-c2
|
||||
end if
|
||||
c2 = c2 + 1
|
||||
f2 = %+1
|
||||
DEBUGF_HELPER D,a1,c4,_arg
|
||||
else if c1 = '\n'
|
||||
c3 = c3 + 1
|
||||
end if
|
||||
end repeat
|
||||
virtual at 0
|
||||
db _format,0,0
|
||||
load c1 from f2-c2
|
||||
end virtual
|
||||
if (c1<>0)&(f2<>..lbl-..f1-1)
|
||||
DEBUGS 0,_debug_str_+f2-c2
|
||||
end if
|
||||
virtual at 0
|
||||
..f1 db _format,0
|
||||
..lbl:
|
||||
__debug_strings equ __debug_strings,_debug_str_,<_format>,..lbl-..f1-1-c2-c3
|
||||
end virtual
|
||||
end if
|
||||
}
|
||||
|
||||
macro __include_debug_strings dummy,[_id,_fmt,_len] {
|
||||
common
|
||||
local c1,a1,a2
|
||||
forward
|
||||
if defined _len & ~_len eq
|
||||
_id:
|
||||
a1 = 0
|
||||
a2 = 0
|
||||
repeat _len
|
||||
virtual at 0
|
||||
db _fmt,0,0
|
||||
load c1 word from %+a2-1
|
||||
end virtual
|
||||
if (c1='%s')|(c1='%x')|(c1='%d')|(c1='%u')
|
||||
db 0
|
||||
a2 = a2 + 1
|
||||
else if (c1='\n')
|
||||
dw $0A0D
|
||||
a1 = a1 + 1
|
||||
a2 = a2 + 1
|
||||
else
|
||||
db c1 and 0x0FF
|
||||
end if
|
||||
end repeat
|
||||
db 0
|
||||
end if
|
||||
}
|
||||
|
||||
macro DEBUGF_HELPER _letter,_num,_sign,[_arg] {
|
||||
common
|
||||
local num
|
||||
num = 0
|
||||
forward
|
||||
if num = _num
|
||||
DEBUG#_letter _sign,_arg
|
||||
end if
|
||||
num = num+1
|
||||
common
|
||||
_num = _num+1
|
||||
}
|
||||
|
||||
macro include_debug_strings {
|
||||
if __DEBUG__ = 1
|
||||
match dbg_str,__debug_strings \{
|
||||
__include_debug_strings dbg_str
|
||||
\}
|
||||
end if
|
||||
}
|
@ -96,7 +96,15 @@ kernel_export \
|
||||
LFBAddress,\
|
||||
GetDisplay,\
|
||||
SetScreen,\
|
||||
\
|
||||
RegUSBDriver,\
|
||||
USBOpenPipe,\
|
||||
USBNormalTransferAsync,\
|
||||
USBControlTransferAsync,\
|
||||
\
|
||||
DiskAdd,\
|
||||
DiskMediaChanged,\
|
||||
DiskDel
|
||||
DiskDel,\
|
||||
\
|
||||
TimerHS,\
|
||||
CancelTimerHS
|
||||
|
696
kernel/trunk/drivers/usbhid.asm
Normal file
696
kernel/trunk/drivers/usbhid.asm
Normal file
@ -0,0 +1,696 @@
|
||||
; standard driver stuff
|
||||
format MS COFF
|
||||
|
||||
DEBUG = 1
|
||||
|
||||
; this is for DEBUGF macro from 'fdo.inc'
|
||||
__DEBUG__ = 1
|
||||
__DEBUG_LEVEL__ = 1
|
||||
|
||||
include 'proc32.inc'
|
||||
include 'imports.inc'
|
||||
include 'fdo.inc'
|
||||
|
||||
public START
|
||||
public version
|
||||
|
||||
; USB constants
|
||||
DEVICE_DESCR_TYPE = 1
|
||||
CONFIG_DESCR_TYPE = 2
|
||||
STRING_DESCR_TYPE = 3
|
||||
INTERFACE_DESCR_TYPE = 4
|
||||
ENDPOINT_DESCR_TYPE = 5
|
||||
DEVICE_QUALIFIER_DESCR_TYPE = 6
|
||||
|
||||
CONTROL_PIPE = 0
|
||||
ISOCHRONOUS_PIPE = 1
|
||||
BULK_PIPE = 2
|
||||
INTERRUPT_PIPE = 3
|
||||
|
||||
; USB structures
|
||||
virtual at 0
|
||||
config_descr:
|
||||
.bLength db ?
|
||||
.bDescriptorType db ?
|
||||
.wTotalLength dw ?
|
||||
.bNumInterfaces db ?
|
||||
.bConfigurationValue db ?
|
||||
.iConfiguration db ?
|
||||
.bmAttributes db ?
|
||||
.bMaxPower db ?
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
virtual at 0
|
||||
interface_descr:
|
||||
.bLength db ?
|
||||
.bDescriptorType db ?
|
||||
.bInterfaceNumber db ?
|
||||
.bAlternateSetting db ?
|
||||
.bNumEndpoints db ?
|
||||
.bInterfaceClass db ?
|
||||
.bInterfaceSubClass db ?
|
||||
.bInterfaceProtocol db ?
|
||||
.iInterface db ?
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
virtual at 0
|
||||
endpoint_descr:
|
||||
.bLength db ?
|
||||
.bDescriptorType db ?
|
||||
.bEndpointAddress db ?
|
||||
.bmAttributes db ?
|
||||
.wMaxPacketSize dw ?
|
||||
.bInterval db ?
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
; Driver data for all devices
|
||||
virtual at 0
|
||||
device_data:
|
||||
.type dd ? ; 1 = keyboard, 2 = mouse
|
||||
.intpipe dd ? ; interrupt pipe handle
|
||||
.packetsize dd ?
|
||||
.packet rb 8 ; packet with data from device
|
||||
.control rb 8 ; control packet to device
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
; Driver data for mouse
|
||||
virtual at device_data.sizeof
|
||||
mouse_data:
|
||||
; no additional data
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
; Driver data for keyboard
|
||||
virtual at device_data.sizeof
|
||||
keyboard_data:
|
||||
.handle dd ? ; keyboard handle from RegKeyboard
|
||||
.configpipe dd ? ; config pipe handle
|
||||
.prevpacket rb 8 ; previous packet with data from device
|
||||
.timer dd ? ; auto-repeat timer handle
|
||||
.repeatkey db ? ; auto-repeat key code
|
||||
.ledstate db ? ; state of LEDs
|
||||
align 4
|
||||
.sizeof:
|
||||
end virtual
|
||||
|
||||
section '.flat' code readable align 16
|
||||
; The start procedure.
|
||||
START:
|
||||
; 1. Test whether the procedure is called with the argument DRV_ENTRY.
|
||||
; If not, return 0.
|
||||
xor eax, eax ; initialize return value
|
||||
cmp dword [esp+4], 1 ; compare the argument
|
||||
jnz .nothing
|
||||
; 2. Register self as a USB driver.
|
||||
; The name is my_driver = 'usbhid'; IOCTL interface is not supported;
|
||||
; usb_functions is an offset of a structure with callback functions.
|
||||
stdcall RegUSBDriver, my_driver, eax, usb_functions
|
||||
; 3. Return the returned value of RegUSBDriver.
|
||||
.nothing:
|
||||
ret 4
|
||||
|
||||
; This procedure is called when new HID device is detected.
|
||||
; It initializes the device.
|
||||
AddDevice:
|
||||
; Arguments are addressed through esp. In this point of the function,
|
||||
; [esp+4] = a handle of the config pipe, [esp+8] points to config_descr
|
||||
; structure, [esp+12] points to interface_descr structure.
|
||||
; 1. Check device type. Currently only mice and keyboards with
|
||||
; boot protocol are supported.
|
||||
; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and
|
||||
; bInterfaceProtocol are subsequent in interface_descr, just one
|
||||
; memory reference is used for both.
|
||||
mov edx, [esp+12]
|
||||
push ebx ; save used register to be stdcall
|
||||
mov cx, word [edx+interface_descr.bInterfaceSubClass]
|
||||
; 1b. For boot protocol, subclass must be 1 and protocol must be either 1 for
|
||||
; a keyboard or 2 for a mouse. Check.
|
||||
cmp cx, 0x0101
|
||||
jz .keyboard
|
||||
cmp cx, 0x0201
|
||||
jz .mouse
|
||||
; 1c. If the device is neither a keyboard nor a mouse, print a message and
|
||||
; go to 6c.
|
||||
DEBUGF 1,'K : unknown HID device\n'
|
||||
jmp .nothing
|
||||
; 1d. If the device is a keyboard or a mouse, print a message and continue
|
||||
; configuring.
|
||||
.keyboard:
|
||||
DEBUGF 1,'K : USB keyboard detected\n'
|
||||
push keyboard_data.sizeof
|
||||
jmp .common
|
||||
.mouse:
|
||||
DEBUGF 1,'K : USB mouse detected\n'
|
||||
push mouse_data.sizeof
|
||||
.common:
|
||||
; 2. Allocate memory for device data.
|
||||
pop eax ; get size of device data
|
||||
; 2a. Call the kernel, saving and restoring register edx.
|
||||
push edx
|
||||
call Kmalloc
|
||||
pop edx
|
||||
; 2b. Check result. If failed, say a message and go to 6c.
|
||||
test eax, eax
|
||||
jnz @f
|
||||
DEBUGF 1,'K : no memory\n'
|
||||
jmp .nothing
|
||||
@@:
|
||||
xchg eax, ebx
|
||||
; HID devices use one IN interrupt endpoint for polling the device
|
||||
; and an optional OUT interrupt endpoint. We do not use the later,
|
||||
; but must locate the first. Look for the IN interrupt endpoint.
|
||||
; 3. Get the upper bound of all descriptors' data.
|
||||
mov eax, [esp+8+4] ; configuration descriptor
|
||||
movzx ecx, [eax+config_descr.wTotalLength]
|
||||
add eax, ecx
|
||||
; 4. Loop over all descriptors until
|
||||
; either end-of-data reached - this is fail
|
||||
; or interface descriptor found - this is fail, all further data
|
||||
; correspond to that interface
|
||||
; or endpoint descriptor found.
|
||||
; 4a. Loop start: eax points to the interface descriptor.
|
||||
.lookep:
|
||||
; 4b. Get next descriptor.
|
||||
movzx ecx, byte [edx] ; the first byte of all descriptors is length
|
||||
add edx, ecx
|
||||
; 4c. Check that at least two bytes are readable. The opposite is an error.
|
||||
inc edx
|
||||
cmp edx, eax
|
||||
jae .errorep
|
||||
dec edx
|
||||
; 4d. Check that this descriptor is not interface descriptor. The opposite is
|
||||
; an error.
|
||||
cmp byte [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE
|
||||
jz .errorep
|
||||
; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue
|
||||
; the loop.
|
||||
cmp byte [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE
|
||||
jnz .lookep
|
||||
; 5. Check that the descriptor contains all required data and all data are
|
||||
; readable. If so, proceed to 7.
|
||||
cmp byte [edx+endpoint_descr.bLength], endpoint_descr.sizeof
|
||||
jb .errorep
|
||||
sub eax, endpoint_descr.sizeof
|
||||
cmp edx, eax
|
||||
jbe @f
|
||||
; 6. An error occured during processing endpoint descriptor.
|
||||
.errorep:
|
||||
; 6a. Print a message.
|
||||
DEBUGF 1,'K : error: invalid endpoint descriptor\n'
|
||||
; 6b. Free memory allocated for device data.
|
||||
.free:
|
||||
xchg eax, ebx
|
||||
call Kfree
|
||||
.nothing:
|
||||
; 6c. Return an error.
|
||||
xor eax, eax
|
||||
pop ebx
|
||||
ret 12
|
||||
@@:
|
||||
; 7. Check that the endpoint is IN interrupt endpoint. If not, go to 6.
|
||||
test [edx+endpoint_descr.bEndpointAddress], 80h
|
||||
jz .errorep
|
||||
mov cl, [edx+endpoint_descr.bmAttributes]
|
||||
and cl, 3
|
||||
cmp cl, INTERRUPT_PIPE
|
||||
jnz .errorep
|
||||
; 8. Open pipe for the endpoint.
|
||||
; 8a. Load parameters from the descriptor.
|
||||
movzx ecx, [edx+endpoint_descr.bEndpointAddress]
|
||||
movzx eax, [edx+endpoint_descr.bInterval]
|
||||
movzx edx, [edx+endpoint_descr.wMaxPacketSize]
|
||||
; 8b. Call the kernel, saving and restoring edx.
|
||||
push edx
|
||||
stdcall USBOpenPipe, [esp+4+24], ecx, edx, INTERRUPT_PIPE, eax
|
||||
pop edx
|
||||
; 8c. Check result. If failed, go to 6b.
|
||||
test eax, eax
|
||||
jz .free
|
||||
; We use 12 bytes for device type, interrupt pipe and interrupt packet size,
|
||||
; 8 bytes for a packet and 8 bytes for previous packet, used by a keyboard.
|
||||
; 9. Initialize device data.
|
||||
mov [ebx+device_data.intpipe], eax
|
||||
push 8
|
||||
pop ecx
|
||||
cmp edx, ecx
|
||||
jb @f
|
||||
mov edx, ecx
|
||||
@@:
|
||||
xor eax, eax
|
||||
mov [ebx+device_data.packetsize], edx
|
||||
mov dword [ebx+device_data.packet], eax
|
||||
mov dword [ebx+device_data.packet+4], eax
|
||||
mov edx, [esp+12+4] ; interface descriptor
|
||||
movzx ecx, [edx+interface_descr.bInterfaceProtocol]
|
||||
mov [ebx+device_data.type], ecx
|
||||
cmp ecx, 1
|
||||
jnz @f
|
||||
mov [ebx+keyboard_data.handle], eax
|
||||
mov [ebx+keyboard_data.timer], eax
|
||||
mov [ebx+keyboard_data.repeatkey], al
|
||||
mov dword [ebx+keyboard_data.prevpacket], eax
|
||||
mov dword [ebx+keyboard_data.prevpacket+4], eax
|
||||
mov eax, [esp+4+4]
|
||||
mov [ebx+keyboard_data.configpipe], eax
|
||||
@@:
|
||||
; 10. Send the control packet SET_PROTOCOL(Boot Protocol) to the interface.
|
||||
lea eax, [ebx+device_data.control]
|
||||
mov dword [eax], 21h + (0Bh shl 8) + (0 shl 16) ; class request to interface + SET_PROTOCOL + Boot protocol
|
||||
and dword [eax+4], 0
|
||||
mov dl, [edx+interface_descr.bInterfaceNumber]
|
||||
mov [eax+4], dl
|
||||
; Callback function is mouse_configured for mice and keyboard_configured1 for keyboards.
|
||||
mov edx, keyboard_configured1
|
||||
cmp ecx, 1
|
||||
jz @f
|
||||
mov edx, mouse_configured
|
||||
@@:
|
||||
stdcall USBControlTransferAsync, [esp+4+28], eax, 0, 0, edx, ebx, 0
|
||||
; 11. Return with pointer to device data as returned value.
|
||||
xchg eax, ebx
|
||||
pop ebx
|
||||
ret 12
|
||||
|
||||
; This function is called when SET_PROTOCOL command for keyboard is done,
|
||||
; either successful or unsuccessful.
|
||||
keyboard_configured1:
|
||||
xor edx, edx
|
||||
; 1. Check the status of the transfer.
|
||||
; If the transfer was failed, go to the common error handler.
|
||||
cmp dword [esp+8], edx ; status is zero?
|
||||
jnz keyboard_data_ready.error
|
||||
; 2. Send the control packet SET_IDLE(infinity). HID auto-repeat is not useful.
|
||||
mov eax, [esp+20]
|
||||
push edx ; flags for USBControlTransferAsync
|
||||
push eax ; userdata for USBControlTransferAsync
|
||||
add eax, device_data.control
|
||||
mov dword [eax], 21h + (0Ah shl 8) + (0 shl 24) ; class request to interface + SET_IDLE + no autorepeat
|
||||
stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \
|
||||
eax, edx, edx, keyboard_configured2; , <userdata>, <flags>
|
||||
; 3. Return.
|
||||
ret 20
|
||||
|
||||
; This function is called when SET_IDLE command for keyboard is done,
|
||||
; either successful or unsuccessful.
|
||||
keyboard_configured2:
|
||||
; Check the status of the transfer and go to the corresponding label
|
||||
; in the main handler.
|
||||
cmp dword [esp+8], 0
|
||||
jnz keyboard_data_ready.error
|
||||
mov edx, [esp+20]
|
||||
push edx
|
||||
stdcall RegKeyboard, usbkbd_functions, edx
|
||||
pop edx
|
||||
mov [edx+keyboard_data.handle], eax
|
||||
jmp keyboard_data_ready.next
|
||||
|
||||
; This function is called when another interrupt packet arrives,
|
||||
; processed either successfully or unsuccessfully.
|
||||
; It should parse the packet and initiate another transfer with
|
||||
; the same callback function.
|
||||
keyboard_data_ready:
|
||||
; 1. Check the status of the transfer.
|
||||
mov eax, [esp+8]
|
||||
test eax, eax
|
||||
jnz .error
|
||||
; Parse the packet, comparing with the previous packet.
|
||||
; For boot protocol, USB keyboard packet consists of the first byte
|
||||
; with status keys that are currently pressed. The second byte should
|
||||
; be ignored, and other 5 bytes denote keys that are currently pressed.
|
||||
push esi ebx ; save used registers to be stdcall
|
||||
; 2. Process control keys.
|
||||
; 2a. Initialize before loop for control keys. edx = mask for control bits
|
||||
; that were changed.
|
||||
mov ebx, [esp+20+8]
|
||||
movzx edx, byte [ebx+device_data.packet] ; get state of control keys
|
||||
xor dl, byte [ebx+keyboard_data.prevpacket] ; compare with previous state
|
||||
; 2b. If state of control keys has not changed, advance to 3.
|
||||
jz .nocontrol
|
||||
; 2c. Otherwise, loop over control keys; esi = bit number.
|
||||
xor esi, esi
|
||||
.controlloop:
|
||||
; 2d. Skip bits that have not changed.
|
||||
bt edx, esi
|
||||
jnc .controlnext
|
||||
push edx ; save register which is possibly modified by API
|
||||
; The state of the current control key has changed.
|
||||
; 2e. For extended control keys, send the prefix 0xE0.
|
||||
mov al, [control_keys+esi]
|
||||
test al, al
|
||||
jns @f
|
||||
push eax
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop eax
|
||||
and al, 0x7F
|
||||
@@:
|
||||
; 2f. If the current state of the control key is "pressed", send normal
|
||||
; scancode. Otherwise, the key is released, so set the high bit in scancode.
|
||||
movzx ecx, al
|
||||
bt dword [ebx+device_data.packet], esi
|
||||
jc @f
|
||||
or cl, 0x80
|
||||
@@:
|
||||
call SetKeyboardData
|
||||
pop edx ; restore register which was possibly modified by API
|
||||
.controlnext:
|
||||
; 2g. We have 8 control keys.
|
||||
inc esi
|
||||
cmp esi, 8
|
||||
jb .controlloop
|
||||
.nocontrol:
|
||||
; 3. Initialize before loop for normal keys. esi = index.
|
||||
push 2
|
||||
pop esi
|
||||
.normalloop:
|
||||
; 4. Process one key which was pressed in the previous packet.
|
||||
; 4a. Get the next pressed key from the previous packet.
|
||||
movzx eax, byte [ebx+esi+keyboard_data.prevpacket]
|
||||
; 4b. Ignore special codes.
|
||||
cmp al, 3
|
||||
jbe .normalnext1
|
||||
; 4c. Ignore keys that are still pressed in the current packet.
|
||||
lea ecx, [ebx+device_data.packet]
|
||||
call haskey
|
||||
jz .normalnext1
|
||||
; 4d. Say warning about keys with strange codes.
|
||||
cmp eax, normal_keys_number
|
||||
jae .badkey1
|
||||
movzx ecx, [normal_keys+eax]
|
||||
jecxz .badkey1
|
||||
; 4e. For extended keys, send the prefix 0xE0.
|
||||
push ecx ; save keycode
|
||||
test cl, cl
|
||||
jns @f
|
||||
push ecx
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop ecx
|
||||
@@:
|
||||
; 4f. Send the release event.
|
||||
or cl, 0x80
|
||||
call SetKeyboardData
|
||||
; 4g. If this key is autorepeating, stop the timer.
|
||||
pop ecx ; restore keycode
|
||||
cmp cl, [ebx+keyboard_data.repeatkey]
|
||||
jnz .normalnext1
|
||||
mov eax, [ebx+keyboard_data.timer]
|
||||
test eax, eax
|
||||
jz .normalnext1
|
||||
stdcall CancelTimerHS, eax
|
||||
and [ebx+keyboard_data.timer], 0
|
||||
jmp .normalnext1
|
||||
.badkey1:
|
||||
DEBUGF 1,'K : unknown keycode: %x\n',al
|
||||
.normalnext1:
|
||||
; 5. Process one key which is pressed in the current packet.
|
||||
; 5a. Get the next pressed key from the current packet.
|
||||
movzx eax, byte [ebx+esi+device_data.packet]
|
||||
; 5b. Ignore special codes.
|
||||
cmp al, 3
|
||||
jbe .normalnext2
|
||||
; 5c. Ignore keys that were already pressed in the previous packet.
|
||||
lea ecx, [ebx+keyboard_data.prevpacket]
|
||||
call haskey
|
||||
jz .normalnext2
|
||||
; 5d. Say warning about keys with strange codes.
|
||||
cmp eax, normal_keys_number
|
||||
jae .badkey2
|
||||
movzx ecx, [normal_keys+eax]
|
||||
jecxz .badkey2
|
||||
; 5e. For extended keys, send the prefix 0xE0.
|
||||
push ecx ; save keycode
|
||||
test cl, cl
|
||||
jns @f
|
||||
push ecx
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop ecx
|
||||
@@:
|
||||
; 5f. Send the press event.
|
||||
and cl, not 0x80
|
||||
call SetKeyboardData
|
||||
; 5g. Stop the current auto-repeat timer, if present.
|
||||
mov eax, [ebx+keyboard_data.timer]
|
||||
test eax, eax
|
||||
jz @f
|
||||
stdcall CancelTimerHS, eax
|
||||
@@:
|
||||
; 5h. Start the auto-repeat timer.
|
||||
pop ecx ; restore keycode
|
||||
mov [ebx+keyboard_data.repeatkey], cl
|
||||
stdcall TimerHS, 25, 5, autorepeat_timer, ebx
|
||||
mov [ebx+keyboard_data.timer], eax
|
||||
jmp .normalnext2
|
||||
.badkey2:
|
||||
DEBUGF 1,'K : unknown keycode: %x\n',al
|
||||
.normalnext2:
|
||||
; 6. Advance to next key.
|
||||
inc esi
|
||||
cmp esi, 8
|
||||
jb .normalloop
|
||||
; 7. Save the packet data for future reference.
|
||||
mov eax, dword [ebx+device_data.packet]
|
||||
mov dword [ebx+keyboard_data.prevpacket], eax
|
||||
mov eax, dword [ebx+device_data.packet+4]
|
||||
mov dword [ebx+keyboard_data.prevpacket+4], eax
|
||||
pop ebx esi ; restore registers to be stdcall
|
||||
.next:
|
||||
; 8. Initiate transfer on the interrupt pipe.
|
||||
mov eax, [esp+20]
|
||||
push 1 ; flags for USBNormalTransferAsync
|
||||
push eax ; userdata for USBNormalTransferAsync
|
||||
add eax, device_data.packet
|
||||
stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \
|
||||
eax, dword [eax+device_data.packetsize-device_data.packet], \
|
||||
keyboard_data_ready;, <userdata>, <flags>
|
||||
; 9. Return.
|
||||
.nothing:
|
||||
ret 20
|
||||
.error:
|
||||
; An error has occured.
|
||||
; 10. If an error is caused by the disconnect, do nothing, it is handled
|
||||
; in DeviceDisconnected. Otherwise, say a message.
|
||||
cmp eax, 16
|
||||
jz @f
|
||||
push esi
|
||||
mov esi, errormsgkbd
|
||||
call SysMsgBoardStr
|
||||
pop esi
|
||||
@@:
|
||||
ret 20
|
||||
|
||||
; Auxiliary procedure for keyboard_data_ready.
|
||||
haskey:
|
||||
push 2
|
||||
pop edx
|
||||
@@:
|
||||
cmp byte [ecx+edx], al
|
||||
jz @f
|
||||
inc edx
|
||||
cmp edx, 7
|
||||
jbe @b
|
||||
@@:
|
||||
ret
|
||||
|
||||
; Timer function for auto-repeat.
|
||||
autorepeat_timer:
|
||||
mov eax, [esp+4]
|
||||
movzx ecx, [eax+keyboard_data.repeatkey]
|
||||
test cl, cl
|
||||
jns @f
|
||||
push ecx
|
||||
mov ecx, 0xE0
|
||||
call SetKeyboardData
|
||||
pop ecx
|
||||
and cl, not 0x80
|
||||
@@:
|
||||
call SetKeyboardData
|
||||
ret 4
|
||||
|
||||
; This function is called to update LED state on the keyboard.
|
||||
SetKeyboardLights:
|
||||
mov eax, [esp+4]
|
||||
add eax, device_data.control
|
||||
mov dword [eax], 21h + (9 shl 8) + (2 shl 24)
|
||||
; class request to interface + SET_REPORT + Output zero report
|
||||
mov byte [eax+6], 1
|
||||
mov edx, [esp+8]
|
||||
shr dl, 1
|
||||
jnc @f
|
||||
or dl, 4
|
||||
@@:
|
||||
lea ecx, [eax+keyboard_data.ledstate-device_data.control]
|
||||
mov [ecx], dl
|
||||
stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \
|
||||
eax, ecx, 1, keyboard_data_ready.nothing, 0, 0
|
||||
ret 8
|
||||
|
||||
; This function is called when it is safe to free keyboard data.
|
||||
CloseKeyboard:
|
||||
mov eax, [esp+4]
|
||||
push ebx
|
||||
call Kfree
|
||||
pop ebx
|
||||
ret 4
|
||||
|
||||
; This function is called when SET_PROTOCOL command for mouse is done,
|
||||
; either successful or unsuccessful.
|
||||
mouse_configured:
|
||||
; Check the status of the transfer and go to the corresponding label
|
||||
; in the main handler.
|
||||
cmp dword [esp+8], 0
|
||||
jnz mouse_data_ready.error
|
||||
mov eax, [esp+20]
|
||||
add eax, device_data.packet
|
||||
jmp mouse_data_ready.next
|
||||
|
||||
; This function is called when another interrupt packet arrives,
|
||||
; processed either successfully or unsuccessfully.
|
||||
; It should parse the packet and initiate another transfer with
|
||||
; the same callback function.
|
||||
mouse_data_ready:
|
||||
; 1. Check the status of the transfer.
|
||||
mov eax, [esp+8]
|
||||
test eax, eax
|
||||
jnz .error
|
||||
mov edx, [esp+16]
|
||||
; 2. Parse the packet.
|
||||
; For boot protocol, USB mouse packet consists of at least 3 bytes.
|
||||
; The first byte is state of mouse buttons, the next two bytes are
|
||||
; x and y movements.
|
||||
; Normal mice do not distinguish between boot protocol and report protocol;
|
||||
; in this case, scroll data are also present. Advanced mice, however,
|
||||
; support two different protocols, boot protocol is used for compatibility
|
||||
; and does not contain extended buttons or scroll data.
|
||||
mov eax, [esp+12] ; buffer
|
||||
push eax
|
||||
xor ecx, ecx
|
||||
cmp edx, 4
|
||||
jbe @f
|
||||
movsx ecx, byte [eax+4]
|
||||
@@:
|
||||
push ecx
|
||||
xor ecx, ecx
|
||||
cmp edx, 3
|
||||
jbe @f
|
||||
movsx ecx, byte [eax+3]
|
||||
neg ecx
|
||||
@@:
|
||||
push ecx
|
||||
xor ecx, ecx
|
||||
cmp edx, 2
|
||||
jbe @f
|
||||
movsx ecx, byte [eax+2]
|
||||
neg ecx
|
||||
@@:
|
||||
push ecx
|
||||
movsx ecx, byte [eax+1]
|
||||
push ecx
|
||||
movzx ecx, byte [eax]
|
||||
push ecx
|
||||
call SetMouseData
|
||||
pop eax
|
||||
.next:
|
||||
; 3. Initiate transfer on the interrupt pipe.
|
||||
stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \
|
||||
eax, dword [eax+device_data.packetsize-device_data.packet], mouse_data_ready, eax, 1
|
||||
; 4. Return.
|
||||
ret 20
|
||||
.error:
|
||||
; An error has occured.
|
||||
; 5. If an error is caused by the disconnect, do nothing, it is handled
|
||||
; in DeviceDisconnected. Otherwise, say a message.
|
||||
cmp eax, 16
|
||||
jz @f
|
||||
push esi
|
||||
mov esi, errormsgmouse
|
||||
call SysMsgBoardStr
|
||||
pop esi
|
||||
@@:
|
||||
ret 20
|
||||
|
||||
; This function is called when the device is disconnected.
|
||||
DeviceDisconnected:
|
||||
push ebx ; save used register to be stdcall
|
||||
; 1. Say a message. Use different messages for keyboards and mice.
|
||||
mov ebx, [esp+4+4]
|
||||
push esi
|
||||
mov esi, disconnectmsgk
|
||||
cmp byte [ebx+device_data.type], 1
|
||||
jz @f
|
||||
mov esi, disconnectmsgm
|
||||
@@:
|
||||
stdcall SysMsgBoardStr
|
||||
pop esi
|
||||
; 2. If device is keyboard, then we must unregister it as a keyboard and
|
||||
; possibly stop the auto-repeat timer.
|
||||
cmp byte [ebx+device_data.type], 1
|
||||
jnz .nokbd
|
||||
mov eax, [ebx+keyboard_data.timer]
|
||||
test eax, eax
|
||||
jz @f
|
||||
stdcall CancelTimerHS, eax
|
||||
@@:
|
||||
mov ecx, [ebx+keyboard_data.handle]
|
||||
jecxz .nokbd
|
||||
stdcall DelKeyboard, ecx
|
||||
; If keyboard is registered, then we should free data in CloseKeyboard, not here.
|
||||
jmp .nothing
|
||||
.nokbd:
|
||||
; 3. Free the device data.
|
||||
xchg eax, ebx
|
||||
call Kfree
|
||||
; 4. Return.
|
||||
.nothing:
|
||||
pop ebx ; restore used register to be stdcall
|
||||
ret 4 ; purge one dword argument to be stdcall
|
||||
|
||||
; strings
|
||||
my_driver db 'usbhid',0
|
||||
errormsgmouse db 'K : USB transfer error, disabling mouse',10,0
|
||||
errormsgkbd db 'K : USB transfer error, disabling keyboard',10,0
|
||||
disconnectmsgm db 'K : USB mouse disconnected',10,0
|
||||
disconnectmsgk db 'K : USB keyboard disconnected',10,0
|
||||
|
||||
; data for keyboard: correspondence between HID usage keys and PS/2 scancodes.
|
||||
EX = 80h
|
||||
label control_keys byte
|
||||
db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX
|
||||
label normal_keys byte
|
||||
db 00h, 00h, 00h, 00h, 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h ; 0x
|
||||
db 32h, 31h, 18h, 19h, 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h ; 1x
|
||||
db 04h, 05h, 06h, 07h, 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah ; 2x
|
||||
db 1Bh, 2Bh, 2Bh, 27h, 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h ; 3x
|
||||
db 41h, 42h, 43h, 44h, 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX ; 4x
|
||||
db 4Bh+EX,50h+EX,48h+EX,45h,35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h,51h,4Bh,4Ch,4Dh,47h ; 5x
|
||||
db 48h, 49h, 52h, 53h, 56h,5Dh+EX,5Eh+EX,59h,64h,65h,66h, 67h, 68h, 69h, 6Ah, 6Bh ; 6x
|
||||
db 6Ch, 6Dh, 6Eh, 76h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h ; 7x
|
||||
db 00h, 00h, 00h, 00h, 00h, 7Eh, 00h, 73h, 70h, 7Dh, 79h, 7Bh, 5Ch, 00h, 00h, 00h ; 8x
|
||||
db 0F2h,0F1h,78h, 77h, 76h
|
||||
normal_keys_number = $ - normal_keys
|
||||
|
||||
; Exported variable: kernel API version.
|
||||
align 4
|
||||
version dd 50005h
|
||||
; Structure with callback functions.
|
||||
usb_functions:
|
||||
dd 12
|
||||
dd AddDevice
|
||||
dd DeviceDisconnected
|
||||
|
||||
; Structure with callback functions for keyboards.
|
||||
usbkbd_functions:
|
||||
dd 12
|
||||
dd CloseKeyboard
|
||||
dd SetKeyboardLights
|
||||
|
||||
; for DEBUGF macro
|
||||
include_debug_strings
|
||||
|
||||
; for uninitialized data
|
||||
section '.data' data readable writable align 16
|
1609
kernel/trunk/drivers/usbstor.asm
Normal file
1609
kernel/trunk/drivers/usbstor.asm
Normal file
File diff suppressed because it is too large
Load Diff
@ -805,6 +805,8 @@ end if
|
||||
|
||||
stdcall load_driver, szVidintel
|
||||
|
||||
call usb_init
|
||||
|
||||
; SET PRELIMINARY WINDOW STACK AND POSITIONS
|
||||
|
||||
mov esi, boot_windefs
|
||||
|
@ -220,6 +220,9 @@ include "gui/skincode.inc"
|
||||
|
||||
include "bus/pci/pci32.inc"
|
||||
|
||||
; USB functions
|
||||
include "bus/usb/init.inc"
|
||||
|
||||
; Floppy drive controller
|
||||
|
||||
include "blkdev/fdc.inc"
|
||||
|
Loading…
Reference in New Issue
Block a user