USB support

git-svn-id: svn://kolibrios.org@3520 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
CleverMouse 2013-05-17 23:53:28 +00:00
parent bcdfe175d7
commit c1284fc3b6
25 changed files with 13119 additions and 349 deletions

View File

@ -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 \

View File

@ -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 \

View File

@ -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 \

View File

@ -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 \

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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"

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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.

View 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
}

View File

@ -96,7 +96,15 @@ kernel_export \
LFBAddress,\
GetDisplay,\
SetScreen,\
\
RegUSBDriver,\
USBOpenPipe,\
USBNormalTransferAsync,\
USBControlTransferAsync,\
\
DiskAdd,\
DiskMediaChanged,\
DiskDel
DiskDel,\
\
TimerHS,\
CancelTimerHS

View 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

File diff suppressed because it is too large Load Diff

View File

@ -805,6 +805,8 @@ end if
stdcall load_driver, szVidintel
call usb_init
; SET PRELIMINARY WINDOW STACK AND POSITIONS
mov esi, boot_windefs

View File

@ -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"