forked from KolibriOS/kolibrios
USB split transaction scheduler
git-svn-id: svn://kolibrios.org@3826 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
parent
49a2302c63
commit
835120b53f
@ -919,11 +919,11 @@ end virtual
|
|||||||
; This could fail if the requested bandwidth is not available;
|
; This could fail if the requested bandwidth is not available;
|
||||||
; if so, return an error.
|
; if so, return an error.
|
||||||
test word [edi+ehci_pipe.Flags-sizeof.ehci_pipe+2], 3FFFh
|
test word [edi+ehci_pipe.Flags-sizeof.ehci_pipe+2], 3FFFh
|
||||||
jnz .interrupt_fs
|
jnz .interrupt_tt
|
||||||
call ehci_select_hs_interrupt_list
|
call ehci_select_hs_interrupt_list
|
||||||
jmp .interrupt_common
|
jmp .interrupt_common
|
||||||
.interrupt_fs:
|
.interrupt_tt:
|
||||||
call ehci_select_fs_interrupt_list
|
call ehci_select_tt_interrupt_list
|
||||||
.interrupt_common:
|
.interrupt_common:
|
||||||
test edx, edx
|
test edx, edx
|
||||||
jz .return0
|
jz .return0
|
||||||
@ -1310,16 +1310,17 @@ proc ehci_new_device
|
|||||||
; ehci_init_pipe assumes that the parent pipe is a control pipe.
|
; ehci_init_pipe assumes that the parent pipe is a control pipe.
|
||||||
movzx ecx, [esi+usb_controller.ResettingPort]
|
movzx ecx, [esi+usb_controller.ResettingPort]
|
||||||
mov edx, [esi+usb_controller.ResettingHub]
|
mov edx, [esi+usb_controller.ResettingHub]
|
||||||
|
; If the parent hub is high-speed, it is TT for the device.
|
||||||
|
; Otherwise, the parent hub itself is behind TT, and the device
|
||||||
|
; has the same TT hub+port as the parent hub.
|
||||||
push eax
|
push eax
|
||||||
.find_hs_hub:
|
|
||||||
mov eax, [edx+usb_hub.ConfigPipe]
|
mov eax, [edx+usb_hub.ConfigPipe]
|
||||||
mov eax, [eax+usb_pipe.DeviceData]
|
mov eax, [eax+usb_pipe.DeviceData]
|
||||||
cmp [eax+usb_device_data.Speed], USB_SPEED_HS
|
cmp [eax+usb_device_data.Speed], USB_SPEED_HS
|
||||||
jz .found_hs_hub
|
jz @f
|
||||||
movzx ecx, [eax+usb_device_data.Port]
|
movzx ecx, [eax+usb_device_data.TTPort]
|
||||||
mov edx, [eax+usb_device_data.Hub]
|
mov edx, [eax+usb_device_data.TTHub]
|
||||||
jmp .find_hs_hub
|
@@:
|
||||||
.found_hs_hub:
|
|
||||||
mov edx, [edx+usb_hub.ConfigPipe]
|
mov edx, [edx+usb_hub.ConfigPipe]
|
||||||
inc ecx
|
inc ecx
|
||||||
mov edx, [edx+ehci_pipe.Token-sizeof.ehci_pipe]
|
mov edx, [edx+ehci_pipe.Token-sizeof.ehci_pipe]
|
||||||
|
@ -133,17 +133,22 @@ NumPipes dd ?
|
|||||||
; Number of not-yet-closed pipes.
|
; Number of not-yet-closed pipes.
|
||||||
Hub dd ?
|
Hub dd ?
|
||||||
; NULL if connected to the root hub, pointer to usb_hub otherwise.
|
; NULL if connected to the root hub, pointer to usb_hub otherwise.
|
||||||
|
TTHub dd ?
|
||||||
|
; Pointer to usb_hub for (the) hub with Transaction Translator for the device,
|
||||||
|
; NULL if the device operates in the same speed as the controller.
|
||||||
Port db ?
|
Port db ?
|
||||||
; Port on the hub, zero-based.
|
; Port on the hub, zero-based.
|
||||||
|
TTPort db ?
|
||||||
|
; Port on the TTHub, zero-based.
|
||||||
DeviceDescrSize db ?
|
DeviceDescrSize db ?
|
||||||
; Size of device descriptor.
|
; Size of device descriptor.
|
||||||
NumInterfaces db ?
|
|
||||||
; Number of interfaces.
|
|
||||||
Speed db ?
|
Speed db ?
|
||||||
; Device speed, one of USB_SPEED_*.
|
; Device speed, one of USB_SPEED_*.
|
||||||
|
NumInterfaces dd ?
|
||||||
|
; Number of interfaces.
|
||||||
ConfigDataSize dd ?
|
ConfigDataSize dd ?
|
||||||
; Total size of data associated with the configuration descriptor
|
; Total size of data associated with the configuration descriptor
|
||||||
; (including the configuration descriptor itself);
|
; (including the configuration descriptor itself).
|
||||||
Interfaces dd ?
|
Interfaces dd ?
|
||||||
; Offset from the beginning of this structure to Interfaces field.
|
; Offset from the beginning of this structure to Interfaces field.
|
||||||
; Variable-length fields:
|
; Variable-length fields:
|
||||||
|
@ -216,6 +216,7 @@ endp
|
|||||||
proc usb_open_pipe stdcall uses ebx esi edi,\
|
proc usb_open_pipe stdcall uses ebx esi edi,\
|
||||||
config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword
|
config_pipe:dword, endpoint:dword, maxpacket:dword, type:dword, interval:dword
|
||||||
locals
|
locals
|
||||||
|
tt_vars rd (ehci_select_tt_interrupt_list.local_vars_size + 3) / 4
|
||||||
targetsmask dd ? ; S-Mask for USB2
|
targetsmask dd ? ; S-Mask for USB2
|
||||||
bandwidth dd ?
|
bandwidth dd ?
|
||||||
target dd ?
|
target dd ?
|
||||||
@ -509,7 +510,7 @@ proc usb_pipe_closed
|
|||||||
; That was the last pipe for the device.
|
; That was the last pipe for the device.
|
||||||
; 5. Notify device driver(s) about disconnect.
|
; 5. Notify device driver(s) about disconnect.
|
||||||
call mutex_unlock
|
call mutex_unlock
|
||||||
movzx eax, [ecx+usb_device_data.NumInterfaces]
|
mov eax, [ecx+usb_device_data.NumInterfaces]
|
||||||
test eax, eax
|
test eax, eax
|
||||||
jz .notify_done
|
jz .notify_done
|
||||||
add ecx, [ecx+usb_device_data.Interfaces]
|
add ecx, [ecx+usb_device_data.Interfaces]
|
||||||
|
@ -239,16 +239,39 @@ proc usb_new_device
|
|||||||
; 2. Store pointer to device data in the pipe structure.
|
; 2. Store pointer to device data in the pipe structure.
|
||||||
mov [ebx+usb_pipe.DeviceData], eax
|
mov [ebx+usb_pipe.DeviceData], eax
|
||||||
; 3. Init device data, using usb_controller.Resetting* variables.
|
; 3. Init device data, using usb_controller.Resetting* variables.
|
||||||
|
mov [eax+usb_device_data.TTHub], edi
|
||||||
|
mov [eax+usb_device_data.TTPort], 0
|
||||||
|
mov [eax+usb_device_data.NumInterfaces], edi
|
||||||
|
mov [eax+usb_device_data.DeviceDescrSize], 0
|
||||||
|
mov dl, [esi+usb_controller.ResettingSpeed]
|
||||||
|
mov [eax+usb_device_data.Speed], dl
|
||||||
mov [eax+usb_device_data.NumPipes], 1
|
mov [eax+usb_device_data.NumPipes], 1
|
||||||
|
push ebx
|
||||||
|
cmp dl, USB_SPEED_HS
|
||||||
|
jz .nott
|
||||||
|
mov ebx, [esi+usb_controller.ResettingHub]
|
||||||
|
test ebx, ebx
|
||||||
|
jz .nott
|
||||||
|
mov cl, [esi+usb_controller.ResettingPort]
|
||||||
|
mov edx, [ebx+usb_hub.ConfigPipe]
|
||||||
|
mov edx, [edx+usb_pipe.DeviceData]
|
||||||
|
cmp [edx+usb_device_data.TTHub], 0
|
||||||
|
jz @f
|
||||||
|
mov cl, [edx+usb_device_data.TTPort]
|
||||||
|
mov ebx, [edx+usb_device_data.TTHub]
|
||||||
|
jmp .has_tt
|
||||||
|
@@:
|
||||||
|
cmp [edx+usb_device_data.Speed], USB_SPEED_HS
|
||||||
|
jnz .nott
|
||||||
|
.has_tt:
|
||||||
|
mov [eax+usb_device_data.TTHub], ebx
|
||||||
|
mov [eax+usb_device_data.TTPort], cl
|
||||||
|
.nott:
|
||||||
|
pop ebx
|
||||||
mov [eax+usb_device_data.ConfigDataSize], edi
|
mov [eax+usb_device_data.ConfigDataSize], edi
|
||||||
mov [eax+usb_device_data.Interfaces], edi
|
mov [eax+usb_device_data.Interfaces], edi
|
||||||
movzx ecx, [esi+usb_controller.ResettingPort]
|
movzx ecx, [esi+usb_controller.ResettingPort]
|
||||||
; Note: the following write zeroes
|
mov [eax+usb_device_data.Port], cl
|
||||||
; 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 edx, [esi+usb_controller.ResettingHub]
|
||||||
mov [eax+usb_device_data.Hub], edx
|
mov [eax+usb_device_data.Hub], edx
|
||||||
; 4. Store pointer to the config pipe in the hub data.
|
; 4. Store pointer to the config pipe in the hub data.
|
||||||
@ -470,9 +493,8 @@ proc usb_after_set_endpoint_size
|
|||||||
mov [ebx+usb_pipe.DeviceData], eax
|
mov [ebx+usb_pipe.DeviceData], eax
|
||||||
mov edi, eax
|
mov edi, eax
|
||||||
mov eax, esi
|
mov eax, esi
|
||||||
repeat sizeof.usb_device_data / 4
|
mov ecx, sizeof.usb_device_data / 4
|
||||||
movsd
|
rep movsd
|
||||||
end repeat
|
|
||||||
pop edi esi
|
pop edi esi
|
||||||
call usb_reinit_pipe_list
|
call usb_reinit_pipe_list
|
||||||
; 1d. Free the old memory.
|
; 1d. Free the old memory.
|
||||||
@ -736,7 +758,7 @@ endl
|
|||||||
jmp .nothing
|
jmp .nothing
|
||||||
@@:
|
@@:
|
||||||
; 3. Store the number of interfaces in device data structure.
|
; 3. Store the number of interfaces in device data structure.
|
||||||
mov [ebx+usb_device_data.NumInterfaces], dl
|
mov [ebx+usb_device_data.NumInterfaces], edx
|
||||||
; 4. If there is only one interface (which happens quite often),
|
; 4. If there is only one interface (which happens quite often),
|
||||||
; the memory allocated in usb_know_length_callback is sufficient.
|
; the memory allocated in usb_know_length_callback is sufficient.
|
||||||
; Otherwise (which also happens quite often), reallocate device data.
|
; Otherwise (which also happens quite often), reallocate device data.
|
||||||
@ -775,7 +797,7 @@ endl
|
|||||||
mov edi, [ebx+usb_device_data.Interfaces]
|
mov edi, [ebx+usb_device_data.Interfaces]
|
||||||
add edi, ebx
|
add edi, ebx
|
||||||
mov [InterfacesData], edi
|
mov [InterfacesData], edi
|
||||||
movzx ecx, [ebx+usb_device_data.NumInterfaces]
|
mov ecx, [ebx+usb_device_data.NumInterfaces]
|
||||||
if sizeof.usb_interface_data <> 8
|
if sizeof.usb_interface_data <> 8
|
||||||
You have changed sizeof.usb_interface_data? Modify this place too.
|
You have changed sizeof.usb_interface_data? Modify this place too.
|
||||||
end if
|
end if
|
||||||
@ -837,9 +859,8 @@ end if
|
|||||||
@@:
|
@@:
|
||||||
; 7f. Check that the new interface does not overflow allocated table.
|
; 7f. Check that the new interface does not overflow allocated table.
|
||||||
mov edx, [NumInterfaces]
|
mov edx, [NumInterfaces]
|
||||||
inc dl
|
inc edx
|
||||||
jz .invalid
|
cmp edx, [ebx+usb_device_data.NumInterfaces]
|
||||||
cmp dl, [ebx+usb_device_data.NumInterfaces]
|
|
||||||
ja .invalid
|
ja .invalid
|
||||||
; 7g. We have found a new interface. Advance bookkeeping vars.
|
; 7g. We have found a new interface. Advance bookkeeping vars.
|
||||||
mov [NumInterfaces], edx
|
mov [NumInterfaces], edx
|
||||||
|
@ -231,12 +231,15 @@ endp
|
|||||||
|
|
||||||
; USB2 scheduler.
|
; USB2 scheduler.
|
||||||
; There are two parts: high-speed pipes and split-transaction pipes.
|
; 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:
|
; High-speed scheduler uses the same algorithm as USB1 scheduler:
|
||||||
; when adding a pipe, optimize the following quantity:
|
; when adding a pipe, optimize the following quantity:
|
||||||
; * for every microframe, take all bandwidth scheduled to periodic transfers,
|
; * for every microframe, take all bandwidth scheduled to periodic transfers,
|
||||||
; * calculate maximum over all microframe,
|
; * calculate maximum over all microframes,
|
||||||
; * select a variant which minimizes that maximum;
|
; * select a variant which minimizes that maximum;
|
||||||
|
; * if there are several such variants,
|
||||||
|
; prefer those that are closer to end of frame
|
||||||
|
; to minimize collisions with split transactions;
|
||||||
; when removing a pipe, do nothing (except for bookkeeping).
|
; when removing a pipe, do nothing (except for bookkeeping).
|
||||||
; in: esi -> usb_controller
|
; in: esi -> usb_controller
|
||||||
; out: edx -> usb_static_ep, eax = S-Mask
|
; out: edx -> usb_static_ep, eax = S-Mask
|
||||||
@ -345,11 +348,13 @@ end virtual
|
|||||||
; then the previous optimum, update the optimal bandwidth and the target.
|
; then the previous optimum, update the optimal bandwidth and the target.
|
||||||
cmp edi, [.bandwidth]
|
cmp edi, [.bandwidth]
|
||||||
ja @f
|
ja @f
|
||||||
|
jb .update
|
||||||
|
cmp ecx, [.targetsmask]
|
||||||
|
jb @f
|
||||||
|
.update:
|
||||||
mov [.bandwidth], edi
|
mov [.bandwidth], edi
|
||||||
mov [.target], edx
|
mov [.target], edx
|
||||||
movi eax, 1
|
mov [.targetsmask], ecx
|
||||||
shl eax, cl
|
|
||||||
mov [.targetsmask], eax
|
|
||||||
@@:
|
@@:
|
||||||
; 4k. Loop #2: continue 8 times for every microframe.
|
; 4k. Loop #2: continue 8 times for every microframe.
|
||||||
inc ecx
|
inc ecx
|
||||||
@ -451,7 +456,7 @@ end virtual
|
|||||||
mov dl, 0xFF
|
mov dl, 0xFF
|
||||||
@@:
|
@@:
|
||||||
; try all variants edx, edx shl 1, edx shl 2, ...
|
; try all variants edx, edx shl 1, edx shl 2, ...
|
||||||
; until they fit in the lower byte (8 microframes per frame)
|
; while they fit in the lower byte (8 microframes per frame)
|
||||||
.select_best_mframe:
|
.select_best_mframe:
|
||||||
xor edi, edi
|
xor edi, edi
|
||||||
mov ecx, edx
|
mov ecx, edx
|
||||||
@ -512,8 +517,6 @@ endp
|
|||||||
; We do not reorder anything, so just update book-keeping variable
|
; We do not reorder anything, so just update book-keeping variable
|
||||||
; in the list header.
|
; in the list header.
|
||||||
proc ehci_hs_interrupt_list_unlink
|
proc ehci_hs_interrupt_list_unlink
|
||||||
; get target list
|
|
||||||
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe]
|
|
||||||
movzx eax, word [ebx+ehci_pipe.Token-sizeof.ehci_pipe+2]
|
movzx eax, word [ebx+ehci_pipe.Token-sizeof.ehci_pipe+2]
|
||||||
; calculate bandwidth
|
; calculate bandwidth
|
||||||
call calc_hs_bandwidth
|
call calc_hs_bandwidth
|
||||||
@ -521,12 +524,13 @@ proc ehci_hs_interrupt_list_unlink
|
|||||||
shr ecx, 30
|
shr ecx, 30
|
||||||
imul eax, ecx
|
imul eax, ecx
|
||||||
movzx ecx, byte [ebx+ehci_pipe.Flags-sizeof.ehci_pipe]
|
movzx ecx, byte [ebx+ehci_pipe.Flags-sizeof.ehci_pipe]
|
||||||
add edx, ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart
|
; get target list
|
||||||
|
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe]
|
||||||
; update bandwidth
|
; update bandwidth
|
||||||
.dec_bandwidth:
|
.dec_bandwidth:
|
||||||
shr ecx, 1
|
shr ecx, 1
|
||||||
jnc @f
|
jnc @f
|
||||||
sub [edx], ax
|
sub word [edx+ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart], ax
|
||||||
@@:
|
@@:
|
||||||
add edx, 2
|
add edx, 2
|
||||||
test ecx, ecx
|
test ecx, ecx
|
||||||
@ -555,18 +559,278 @@ proc calc_hs_bandwidth
|
|||||||
ret
|
ret
|
||||||
endp
|
endp
|
||||||
|
|
||||||
uglobal
|
; Split-transaction scheduler (aka TT scheduler, TT stands for Transaction
|
||||||
ehci_last_fs_alloc dd ?
|
; Translator, section 11.14 of the core spec) needs to schedule three event
|
||||||
endg
|
; types on two buses: Start-Split and Complete-Split on HS bus and normal
|
||||||
|
; transaction on FS/LS bus.
|
||||||
|
; Assume that FS/LS bus is more restricted and more important to be scheduled
|
||||||
|
; uniformly, so select the variant which minimizes maximal used bandwidth
|
||||||
|
; on FS/LS bus and does not overflow HS bus.
|
||||||
|
; If there are several such variants, prefer variants which is closest to
|
||||||
|
; start of frame, and within the same microframe consider HS bandwidth
|
||||||
|
; utilization as a last criteria.
|
||||||
|
|
||||||
|
; The procedure ehci_select_tt_interrupt_list has been splitted into several
|
||||||
|
; macro, each representing a logical step of the procedure,
|
||||||
|
; to simplify understanding what is going on. Consider all the following macro
|
||||||
|
; as logical parts of one procedure, they are meaningless outside the context.
|
||||||
|
|
||||||
|
; Given a frame, calculate bandwidth occupied by already opened pipes
|
||||||
|
; in every microframe.
|
||||||
|
; Look for both HS and FS/LS buses: there are 16 words of information,
|
||||||
|
; 8 for HS bus, 8 for FS/LS bus, for every microframe.
|
||||||
|
; Since we count already opened pipes, the total bandwidth in every microframe
|
||||||
|
; is less than 60000 bits (and even 60000*80% bits), otherwise the scheduler
|
||||||
|
; would not allow to open those pipes.
|
||||||
|
; edi -> first list for the frame
|
||||||
|
macro tt_calc_bandwidth_in_frame
|
||||||
|
{
|
||||||
|
local .lists, .pipes, .pipes_done, .carry
|
||||||
|
; 1. Zero everything.
|
||||||
|
xor eax, eax
|
||||||
|
mov edx, edi
|
||||||
|
repeat 4
|
||||||
|
mov dword [.budget+(%-1)*4], eax
|
||||||
|
end repeat
|
||||||
|
repeat 4
|
||||||
|
mov dword [.hs_bandwidth+(%-1)*4], eax
|
||||||
|
end repeat
|
||||||
|
mov [.total_budget], ax
|
||||||
|
; Loop over all lists for the given frame.
|
||||||
|
.lists:
|
||||||
|
; 2. Total HS bandwidth for all pipes in one list is kept inside list header,
|
||||||
|
; add it. Note that overflow is impossible, so we may add entire dwords.
|
||||||
|
mov ebx, [edx+ehci_static_ep.SoftwarePart+usb_static_ep.NextVirt]
|
||||||
|
repeat 4
|
||||||
|
mov eax, dword [edx+ehci_static_ep.Bandwidths+(%-1)*4]
|
||||||
|
add dword [.hs_bandwidth+(%-1)*4], eax
|
||||||
|
end repeat
|
||||||
|
; Loop over all pipes in the given list.
|
||||||
|
add edx, ehci_static_ep.SoftwarePart
|
||||||
|
.pipes:
|
||||||
|
cmp ebx, edx
|
||||||
|
jz .pipes_done
|
||||||
|
; 3. For every pipe in every list for the given frame:
|
||||||
|
; 3a. Check whether the pipe resides on the same FS/LS bus as the new pipe.
|
||||||
|
; If not, skip this pipe.
|
||||||
|
mov eax, [ebx+usb_pipe.DeviceData]
|
||||||
|
mov eax, [eax+usb_device_data.TTHub]
|
||||||
|
cmp eax, [.tthub]
|
||||||
|
jnz @f
|
||||||
|
; 3b. Calculate FS/LS budget for the opened pipe.
|
||||||
|
; Note that eax = TTHub after 3a.
|
||||||
|
call tt_calc_budget
|
||||||
|
; 3c. Update total budget: add the value from 3b
|
||||||
|
; to the budget of the first microframe scheduled for this pipe.
|
||||||
|
bsf ecx, [ebx+ehci_pipe.Flags-sizeof.ehci_pipe]
|
||||||
|
add [.budget+ecx*2], ax
|
||||||
|
@@:
|
||||||
|
mov ebx, [ebx+usb_pipe.NextVirt]
|
||||||
|
jmp .pipes
|
||||||
|
.pipes_done:
|
||||||
|
mov edx, [edx+ehci_static_ep.NextList-ehci_static_ep.SoftwarePart]
|
||||||
|
test edx, edx
|
||||||
|
jnz .lists
|
||||||
|
; 4. If the budget for some microframe is exceeded, carry it to the following
|
||||||
|
; microframe(s). The actual size of one microframe is 187.5 raw bytes;
|
||||||
|
; the core spec says that 188 bytes should be scheduled in every microframe.
|
||||||
|
xor eax, eax
|
||||||
|
xor ecx, ecx
|
||||||
|
.carry:
|
||||||
|
xor edx, edx
|
||||||
|
add ax, [.budget+ecx*2]
|
||||||
|
cmp ax, 188
|
||||||
|
jbe @f
|
||||||
|
mov dx, ax
|
||||||
|
mov ax, 188
|
||||||
|
sub dx, ax
|
||||||
|
@@:
|
||||||
|
mov [.budget+ecx*2], ax
|
||||||
|
add [.total_budget], ax
|
||||||
|
mov ax, dx
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 8
|
||||||
|
jb .carry
|
||||||
|
}
|
||||||
|
|
||||||
|
; Checks whether the new pipe fits in the existing FS budget
|
||||||
|
; starting from the given microframe. If not, mark the microframe
|
||||||
|
; as impossible for scheduling.
|
||||||
|
; in: ecx = microframe
|
||||||
|
macro tt_exclude_microframe_if_no_budget
|
||||||
|
{
|
||||||
|
local .loop, .good, .bad
|
||||||
|
; 1. If the new budget plus the current budget does not exceed 188 bytes,
|
||||||
|
; the variant is possible.
|
||||||
|
mov ax, [.budget+ecx*2]
|
||||||
|
mov edx, ecx
|
||||||
|
add ax, [.new_budget]
|
||||||
|
sub ax, 188
|
||||||
|
jbe .good
|
||||||
|
; 2. Otherwise,
|
||||||
|
; a) nothing should be scheduled in some following microframes,
|
||||||
|
; b) after adding the new budget everything should fit in first 6 microframes,
|
||||||
|
; this guarantees that even in the worst case 90% limit is satisfied.
|
||||||
|
.loop:
|
||||||
|
cmp edx, 5
|
||||||
|
jae .bad
|
||||||
|
cmp [.budget+(edx+1)*2], 0
|
||||||
|
jnz .bad
|
||||||
|
inc edx
|
||||||
|
sub ax, 188
|
||||||
|
ja .loop
|
||||||
|
.bad:
|
||||||
|
btr [.possible_microframes], ecx
|
||||||
|
.good:
|
||||||
|
}
|
||||||
|
|
||||||
|
; Calculate data corresponding to the particular scheduling variant for the new pipe.
|
||||||
|
; Data describe the current scheduling state collected over all frames touched
|
||||||
|
; by the given variant: maximal HS bandwidth, maximal FS/LS budget,
|
||||||
|
; which microframes fit in the current FS/LS budget for all frames.
|
||||||
|
macro tt_calc_statistics_for_one_variant
|
||||||
|
{
|
||||||
|
local .frames, .microframes
|
||||||
|
; 1. Initialize: zero maximal bandwidth,
|
||||||
|
; first 6 microframes are possible for scheduling.
|
||||||
|
xor eax, eax
|
||||||
|
repeat 4
|
||||||
|
mov dword [.max_hs_bandwidth+(%-1)*4], eax
|
||||||
|
end repeat
|
||||||
|
mov [.max_fs_bandwidth], ax
|
||||||
|
mov [.possible_microframes], 0x3F
|
||||||
|
; Loop over all frames starting with [.variant] advancing by [.variant_delta].
|
||||||
|
mov edi, [.variant]
|
||||||
|
.frames:
|
||||||
|
; 2. Calculate statistics for one frame.
|
||||||
|
tt_calc_bandwidth_in_frame
|
||||||
|
; 3. Update maximal FS budget.
|
||||||
|
mov ax, [.total_budget]
|
||||||
|
cmp ax, [.max_fs_bandwidth]
|
||||||
|
jb @f
|
||||||
|
mov [.max_fs_bandwidth], ax
|
||||||
|
@@:
|
||||||
|
; 4. For every microframe, update maximal HS bandwidth
|
||||||
|
; and check whether the microframe is allowed for scheduling.
|
||||||
|
xor ecx, ecx
|
||||||
|
.microframes:
|
||||||
|
mov ax, [.hs_bandwidth+ecx*2]
|
||||||
|
cmp ax, [.max_hs_bandwidth+ecx*2]
|
||||||
|
jb @f
|
||||||
|
mov [.max_hs_bandwidth+ecx*2], ax
|
||||||
|
@@:
|
||||||
|
tt_exclude_microframe_if_no_budget
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 8
|
||||||
|
jb .microframes
|
||||||
|
; Stop loop when outside of first descriptor group.
|
||||||
|
lea eax, [esi+ehci_controller.IntEDs+32*sizeof.ehci_static_ep-sizeof.ehci_controller]
|
||||||
|
add edi, [.variant_delta]
|
||||||
|
cmp edi, eax
|
||||||
|
jb .frames
|
||||||
|
}
|
||||||
|
|
||||||
|
struct usb_split_info
|
||||||
|
microframe_mask dd ? ; lower byte is S-mask, second byte is C-mask
|
||||||
|
ssplit_bandwidth dd ?
|
||||||
|
csplit_bandwidth dd ?
|
||||||
|
ends
|
||||||
|
|
||||||
|
; Check whether the current variant and the current microframe are allowed
|
||||||
|
; for scheduling. If so, check whether they are better than the previously
|
||||||
|
; selected variant+microframe, if any. If so, update the previously selected
|
||||||
|
; variant+microframe to current ones.
|
||||||
|
; ecx = microframe, [.variant] = variant
|
||||||
|
macro tt_check_variant_microframe
|
||||||
|
{
|
||||||
|
local .nothing, .update, .ssplit, .csplit, .csplit_done
|
||||||
|
; 1. If the current microframe does not fit in existing FS budget, do nothing.
|
||||||
|
bt [.possible_microframes], ecx
|
||||||
|
jnc .nothing
|
||||||
|
; 2. Calculate maximal HS bandwidth over all affected microframes.
|
||||||
|
; 2a. Start-split phase: one or more microframes starting with ecx,
|
||||||
|
; coded in lower byte of .info.microframe_mask.
|
||||||
|
xor ebx, ebx
|
||||||
|
xor edx, edx
|
||||||
|
.ssplit:
|
||||||
|
lea eax, [ecx+edx]
|
||||||
|
movzx eax, [.max_hs_bandwidth+eax*2]
|
||||||
|
add eax, [.info.ssplit_bandwidth]
|
||||||
|
cmp ebx, eax
|
||||||
|
ja @f
|
||||||
|
mov ebx, eax
|
||||||
|
@@:
|
||||||
|
inc edx
|
||||||
|
bt [.info.microframe_mask], edx
|
||||||
|
jc .ssplit
|
||||||
|
; 2b. Complete-split phase: zero or more microframes starting with
|
||||||
|
; ecx+(last start-split microframe)+2,
|
||||||
|
; coded in second byte of .info.microframe_mask.
|
||||||
|
add edx, 8
|
||||||
|
.csplit:
|
||||||
|
inc edx
|
||||||
|
bt [.info.microframe_mask], edx
|
||||||
|
jnc .csplit_done
|
||||||
|
lea eax, [ecx+edx]
|
||||||
|
cmp eax, 8
|
||||||
|
jae .csplit_done
|
||||||
|
movzx eax, [.max_hs_bandwidth+(eax-8)*2]
|
||||||
|
add eax, [.info.csplit_bandwidth]
|
||||||
|
cmp ebx, eax
|
||||||
|
ja .csplit
|
||||||
|
mov ebx, eax
|
||||||
|
jmp .csplit
|
||||||
|
.csplit_done:
|
||||||
|
; 3. Check that current HS bandwidth + new bandwidth <= limit;
|
||||||
|
; USB2 specification allows maximum 60000*80% bit times for periodic microframe.
|
||||||
|
cmp ebx, 48000
|
||||||
|
ja .nothing
|
||||||
|
; 4. This variant is possible for scheduling.
|
||||||
|
; Check whether it is better than the currently selected one.
|
||||||
|
; 4a. The primary criteria: FS/LS bandwidth.
|
||||||
|
mov ax, [.max_fs_bandwidth]
|
||||||
|
cmp ax, [.best_fs_bandwidth]
|
||||||
|
ja .nothing
|
||||||
|
jb .update
|
||||||
|
; 4b. The secondary criteria: prefer microframes which are closer to start of frame.
|
||||||
|
cmp ecx, [.targetsmask]
|
||||||
|
ja .nothing
|
||||||
|
jb .update
|
||||||
|
; 4c. The last criteria: HS bandwidth.
|
||||||
|
cmp ebx, [.bandwidth]
|
||||||
|
ja .nothing
|
||||||
|
.update:
|
||||||
|
; 5. This variant is better than the previously selected.
|
||||||
|
; Update the best variant with current data.
|
||||||
|
mov [.best_fs_bandwidth], ax
|
||||||
|
mov [.bandwidth], ebx
|
||||||
|
mov [.targetsmask], ecx
|
||||||
|
mov eax, [.variant]
|
||||||
|
mov [.target], eax
|
||||||
|
.nothing:
|
||||||
|
}
|
||||||
|
|
||||||
|
; TT scheduler: add new pipe.
|
||||||
|
; in: esi -> usb_controller, edi -> usb_pipe
|
||||||
|
; out: edx -> usb_static_ep, eax = S-Mask
|
||||||
|
proc ehci_select_tt_interrupt_list
|
||||||
|
virtual at ebp-12-.local_vars_size
|
||||||
|
.local_vars_start:
|
||||||
|
.info usb_split_info
|
||||||
|
.new_budget dw ?
|
||||||
|
.total_budget dw ?
|
||||||
|
.possible_microframes dd ?
|
||||||
|
.tthub dd ?
|
||||||
|
.budget rw 8
|
||||||
|
.hs_bandwidth rw 8
|
||||||
|
.max_hs_bandwidth rw 8
|
||||||
|
.max_fs_bandwidth dw ?
|
||||||
|
.best_fs_bandwidth dw ?
|
||||||
|
.variant dd ?
|
||||||
|
.variant_delta dd ?
|
||||||
|
.target_delta dd ?
|
||||||
|
.local_vars_size = $ - .local_vars_start
|
||||||
|
|
||||||
; 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 ?
|
.targetsmask dd ?
|
||||||
.bandwidth dd ?
|
.bandwidth dd ?
|
||||||
.target dd ?
|
.target dd ?
|
||||||
@ -578,26 +842,246 @@ virtual at ebp-12
|
|||||||
.type dd ?
|
.type dd ?
|
||||||
.interval dd ?
|
.interval dd ?
|
||||||
end virtual
|
end virtual
|
||||||
|
mov eax, [edi+ehci_pipe.Token-sizeof.ehci_pipe]
|
||||||
|
shr eax, 16
|
||||||
|
and eax, (1 shl 11) - 1
|
||||||
|
push ebx edi
|
||||||
|
; 1. Compute the real interval. FS/LS devices encode the interval as
|
||||||
|
; number of milliseconds. Use the maximal power of two that is not greater than
|
||||||
|
; the given interval and EHCI scheduling area = 32 frames.
|
||||||
cmp [.interval], 1
|
cmp [.interval], 1
|
||||||
adc [.interval], 0
|
adc [.interval], 0
|
||||||
mov ecx, 64
|
mov ecx, 64
|
||||||
mov eax, ecx
|
mov eax, 64 * sizeof.ehci_static_ep
|
||||||
@@:
|
@@:
|
||||||
shr ecx, 1
|
shr ecx, 1
|
||||||
cmp [.interval], ecx
|
cmp [.interval], ecx
|
||||||
jb @b
|
jb @b
|
||||||
|
mov [.interval], ecx
|
||||||
|
; 2. Compute variables for further calculations.
|
||||||
|
; 2a. [.variant_delta] is delta between two lists from the first group
|
||||||
|
; that correspond to the same variant.
|
||||||
|
imul ecx, sizeof.ehci_static_ep
|
||||||
|
mov [.variant_delta], ecx
|
||||||
|
; 2b. [.target_delta] is delta between the final answer from the group
|
||||||
|
; corresponding to [.interval] and the item from the first group.
|
||||||
sub eax, ecx
|
sub eax, ecx
|
||||||
sub eax, ecx
|
sub eax, ecx
|
||||||
dec ecx
|
mov [.target_delta], eax
|
||||||
and ecx, [ehci_last_fs_alloc]
|
; 2c. [.variant] is the first list from the first group that corresponds
|
||||||
inc [ehci_last_fs_alloc]
|
; to the current variant.
|
||||||
add eax, ecx
|
lea eax, [esi+ehci_controller.IntEDs-sizeof.ehci_controller]
|
||||||
imul eax, sizeof.ehci_static_ep
|
mov [.variant], eax
|
||||||
lea edx, [esi+ehci_controller.IntEDs.SoftwarePart+eax-sizeof.ehci_controller]
|
; 2d. [.tthub] identifies TT hub for new pipe, [.new_budget] is FS budget
|
||||||
mov ax, 1C01h
|
; for new pipe.
|
||||||
|
mov eax, [edi+usb_pipe.DeviceData]
|
||||||
|
mov eax, [eax+usb_device_data.TTHub]
|
||||||
|
mov ebx, edi
|
||||||
|
mov [.tthub], eax
|
||||||
|
call tt_calc_budget
|
||||||
|
mov [.new_budget], ax
|
||||||
|
; 2e. [.usb_split_info] describes bandwidth used by new pipe on HS bus.
|
||||||
|
lea edi, [.info]
|
||||||
|
call tt_fill_split_info
|
||||||
|
test eax, eax
|
||||||
|
jz .no_bandwidth
|
||||||
|
; 2f. There is no best variant yet, put maximal possible values,
|
||||||
|
; so any variant would be better than the "current".
|
||||||
|
or [.best_fs_bandwidth], -1
|
||||||
|
or [.target], -1
|
||||||
|
or [.bandwidth], -1
|
||||||
|
or [.targetsmask], -1
|
||||||
|
; 3. Loop over all variants, for every variant decide whether it is acceptable,
|
||||||
|
; select the best variant from all acceptable variants.
|
||||||
|
.check_variants:
|
||||||
|
tt_calc_statistics_for_one_variant
|
||||||
|
xor ecx, ecx
|
||||||
|
.check_microframes:
|
||||||
|
tt_check_variant_microframe
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 6
|
||||||
|
jb .check_microframes
|
||||||
|
add [.variant], sizeof.ehci_static_ep
|
||||||
|
dec [.interval]
|
||||||
|
jnz .check_variants
|
||||||
|
; 4. If there is no acceptable variants, return error.
|
||||||
|
mov ecx, [.targetsmask]
|
||||||
|
mov edx, [.target]
|
||||||
|
cmp ecx, -1
|
||||||
|
jz .no_bandwidth
|
||||||
|
; 5. Calculate the answer: edx -> selected list, eax = S-Mask and C-Mask.
|
||||||
|
mov eax, [.info.microframe_mask]
|
||||||
|
add edx, [.target_delta]
|
||||||
|
shl eax, cl
|
||||||
|
and eax, 0xFFFF
|
||||||
|
; 6. Update HS bandwidths in the selected list.
|
||||||
|
xor ecx, ecx
|
||||||
|
mov ebx, [.info.ssplit_bandwidth]
|
||||||
|
.update_ssplit:
|
||||||
|
bt eax, ecx
|
||||||
|
jnc @f
|
||||||
|
add [edx+ehci_static_ep.Bandwidths+ecx*2], bx
|
||||||
|
@@:
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 8
|
||||||
|
jb .update_ssplit
|
||||||
|
mov ebx, [.info.csplit_bandwidth]
|
||||||
|
.update_csplit:
|
||||||
|
bt eax, ecx
|
||||||
|
jnc @f
|
||||||
|
add [edx+ehci_static_ep.Bandwidths+(ecx-8)*2], bx
|
||||||
|
@@:
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 16
|
||||||
|
jb .update_csplit
|
||||||
|
; 7. Return.
|
||||||
|
add edx, ehci_static_ep.SoftwarePart
|
||||||
|
pop edi ebx
|
||||||
|
ret
|
||||||
|
.no_bandwidth:
|
||||||
|
dbgstr 'Periodic bandwidth limit reached'
|
||||||
|
xor eax, eax
|
||||||
|
xor edx, edx
|
||||||
|
pop edi ebx
|
||||||
ret
|
ret
|
||||||
endp
|
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_fs_interrupt_list_unlink
|
proc ehci_fs_interrupt_list_unlink
|
||||||
|
; calculate bandwidth
|
||||||
|
push edi
|
||||||
|
sub esp, sizeof.usb_split_info
|
||||||
|
mov edi, esp
|
||||||
|
call tt_fill_split_info
|
||||||
|
; get target list
|
||||||
|
mov edx, [ebx+ehci_pipe.BaseList-sizeof.ehci_pipe]
|
||||||
|
; update bandwidth for Start-Split
|
||||||
|
mov eax, [edi+usb_split_info.ssplit_bandwidth]
|
||||||
|
xor ecx, ecx
|
||||||
|
.dec_bandwidth_1:
|
||||||
|
bt [ebx+ehci_pipe.Flags-sizeof.ehci_pipe], ecx
|
||||||
|
jnc @f
|
||||||
|
sub word [edx+ecx*2+ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart], ax
|
||||||
|
@@:
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 8
|
||||||
|
jb .dec_bandwidth_1
|
||||||
|
; update bandwidth for Complete-Split
|
||||||
|
mov eax, [edi+usb_split_info.csplit_bandwidth]
|
||||||
|
.dec_bandwidth_2:
|
||||||
|
bt [ebx+ehci_pipe.Flags-sizeof.ehci_pipe], ecx
|
||||||
|
jnc @f
|
||||||
|
sub word [edx+(ecx-8)*2+ehci_static_ep.Bandwidths - ehci_static_ep.SoftwarePart], ax
|
||||||
|
@@:
|
||||||
|
inc ecx
|
||||||
|
cmp ecx, 16
|
||||||
|
jb .dec_bandwidth_2
|
||||||
|
add esp, sizeof.usb_split_info
|
||||||
|
pop edi
|
||||||
|
ret
|
||||||
|
endp
|
||||||
|
|
||||||
|
; Helper procedure for ehci_select_tt_interrupt_list.
|
||||||
|
; Calculates "best-case budget" according to the core spec,
|
||||||
|
; that is, number of bytes (not bits) corresponding to "optimistic" transaction
|
||||||
|
; time, including inter-packet delays/bus turn-around time,
|
||||||
|
; but without bit stuffing and timers drift.
|
||||||
|
; One extra TT-specific delay is added: TT think time from the hub descriptor.
|
||||||
|
; Similar to calc_usb1_bandwidth with corresponding changes.
|
||||||
|
; eax -> usb_hub with TT, ebx -> usb_pipe
|
||||||
|
proc tt_calc_budget
|
||||||
|
movzx ecx, [eax+usb_hub.HubCharacteristics]
|
||||||
|
shr ecx, 5
|
||||||
|
and ecx, 3 ; 1+ecx = TT think time in FS-bytes
|
||||||
|
mov eax, [ebx+ehci_pipe.Token-sizeof.ehci_pipe]
|
||||||
|
shr eax, 16
|
||||||
|
and eax, (1 shl 11) - 1 ; get data length
|
||||||
|
bt [ebx+ehci_pipe.Token-sizeof.ehci_pipe], 12
|
||||||
|
jc .low_speed
|
||||||
|
; Full-speed interrupt IN/OUT:
|
||||||
|
; 33 bits for Token packet (8 for SYNC, 24 for token+address, 3 for EOP),
|
||||||
|
; 18 bits for bus turn-around, 11 bits for SYNC+EOP in Data packet,
|
||||||
|
; 2 bits for inter-packet delay, 19 bits for Handshake packet,
|
||||||
|
; 2 bits for another inter-packet delay. 85 bits total, pad to 11 bytes.
|
||||||
|
lea eax, [eax+11+ecx+1]
|
||||||
|
; 1 byte is minimal TT think time in addition to ecx.
|
||||||
|
ret
|
||||||
|
.low_speed:
|
||||||
|
; Low-speed interrupt IN/OUT:
|
||||||
|
; multiply by 8 for LS -> FS,
|
||||||
|
; add 85 bytes as in full-speed interrupt and extra 5 bytes for two PRE packets
|
||||||
|
; and two hub delays.
|
||||||
|
; 1 byte is minimal TT think time in addition to ecx.
|
||||||
|
lea eax, [eax*8+90+ecx+1]
|
||||||
|
ret
|
||||||
|
endp
|
||||||
|
|
||||||
|
; Helper procedure for TT scheduler.
|
||||||
|
; Calculates Start-Split/Complete-Split masks and HS bandwidths.
|
||||||
|
; ebx -> usb_pipe, edi -> usb_split_info
|
||||||
|
proc tt_fill_split_info
|
||||||
|
; Interrupt endpoints.
|
||||||
|
; The core spec says in 5.7.3 "Interrupt Transfer Packet Size Constraints" that:
|
||||||
|
; The maximum allowable interrupt data payload size is 64 bytes or less for full-speed.
|
||||||
|
; Low-speed devices are limited to eight bytes or less maximum data payload size.
|
||||||
|
; This is important for scheduling, it guarantees that in any case transaction fits
|
||||||
|
; in two microframes (usually one, two if transaction has started too late in the first
|
||||||
|
; microframe), so check it.
|
||||||
|
mov eax, [ebx+ehci_pipe.Token-sizeof.ehci_pipe]
|
||||||
|
mov ecx, 8
|
||||||
|
bt eax, 12
|
||||||
|
jc @f
|
||||||
|
mov ecx, 64
|
||||||
|
@@:
|
||||||
|
shr eax, 16
|
||||||
|
and eax, (1 shl 11) - 1 ; get data length
|
||||||
|
cmp eax, ecx
|
||||||
|
ja .error
|
||||||
|
add eax, 3 ; add 3 bytes for other fields in data packet, PID+CRC16
|
||||||
|
; Multiply by 8 for bytes -> bits and then by 7/6 to accomodate bit stuffing;
|
||||||
|
; total 28/3 = 9+1/3
|
||||||
|
mov edx, 55555556h
|
||||||
|
lea ecx, [eax*9]
|
||||||
|
mul edx
|
||||||
|
; One start-split, three complete-splits (unless the last is too far,
|
||||||
|
; but this is handled by the caller).
|
||||||
|
mov eax, [ebx+usb_pipe.LastTD]
|
||||||
|
mov [edi+usb_split_info.microframe_mask], 0x1C01
|
||||||
|
; Structure and HS bandwidth of packets depends on the direction.
|
||||||
|
bt [eax+ehci_gtd.Token-sizeof.ehci_gtd], 8
|
||||||
|
jc .interrupt_in
|
||||||
|
.interrupt_out:
|
||||||
|
; Start-Split phase:
|
||||||
|
; 77 bits for SPLIT packet (32 for SYNC, 8 for EOP, 32 for data, 5 for bit stuffing),
|
||||||
|
; 88 bits for inter-packet delay, 68 bits for Token packet,
|
||||||
|
; 88 bits for inter-packet delay, 40 bits for SYNC+EOP in Data packet,
|
||||||
|
; 88 bits for last inter-packet delay, total 449 bits.
|
||||||
|
lea eax, [edx+ecx+449]
|
||||||
|
mov [edi+usb_split_info.ssplit_bandwidth], eax
|
||||||
|
; Complete-Split phase:
|
||||||
|
; 77 bits for SPLIT packet,
|
||||||
|
; 88 bits for inter-packet delay, 68 bits for Token packet,
|
||||||
|
; 736 bits for bus turn-around, 49 bits for Handshake packet,
|
||||||
|
; 8 bits for inter-packet delay, total 1026 bits.
|
||||||
|
mov [edi+usb_split_info.csplit_bandwidth], 1026
|
||||||
|
ret
|
||||||
|
.interrupt_in:
|
||||||
|
; Start-Split phase:
|
||||||
|
; 77 bits for SPLIT packet, 88 bits for inter-packet delay,
|
||||||
|
; 68 bits for Token packet, 88 bits for another inter-packet delay,
|
||||||
|
; total 321 bits.
|
||||||
|
mov [edi+usb_split_info.ssplit_bandwidth], 321
|
||||||
|
; Complete-Split phase:
|
||||||
|
; 77 bits for SPLIT packet, 88 bits for inter-packet delay,
|
||||||
|
; 68 bits for Token packet, 736 bits for bus turn-around,
|
||||||
|
; 40 bits for SYNC+EOP in Data packet, 8 bits for inter-packet delay,
|
||||||
|
; total 1017 bits.
|
||||||
|
lea eax, [edx+ecx+1017]
|
||||||
|
mov [edi+usb_split_info.csplit_bandwidth], eax
|
||||||
|
ret
|
||||||
|
.error:
|
||||||
|
xor eax, eax
|
||||||
ret
|
ret
|
||||||
endp
|
endp
|
||||||
|
Loading…
Reference in New Issue
Block a user