USB split transaction scheduler

git-svn-id: svn://kolibrios.org@3826 a494cfbc-eb01-0410-851d-a64ba20cac60
This commit is contained in:
CleverMouse 2013-07-17 19:45:07 +00:00
parent 49a2302c63
commit 835120b53f
5 changed files with 568 additions and 56 deletions

View File

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

View File

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

View File

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

View File

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

View File

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