; =============================================================================
;
; Litos - Alarm
;
; =============================================================================
; Alarms are stored in 256-level list of alarms with 13 ms difference of levels
; (i.e. 2^17 * 100-ns). Levels are designed as doubly linked lists with hashed
; index, using 256-bit mask indicating which level of lists is not empty. Time
; counting is realized only as shifting level pointer which points to the base
; level of the lists. Last level has range from 3.342 second to infinity.
; =============================================================================
CODE_SECTION 32
; ------------- Alarm list constants
ALARMLIST_SHIFT EQU 17 ; bits of one alarm list level
ALARMLIST_MAX EQU 256 ; number of levels of alarm list
ALARMLIST_DIF EQU 1<<ALARMLIST_SHIFT ; alarm list difference (2^17=13 ms)
; ------------- Macro - initialized ALARM structure (%1=function, %2=user data)
%macro ALARMTIMER 2
LISTHEAD ; link to list (empty = expired)
dd 0,0 ; system time of alarm expiration
dd 0,0 ; repeat interval and current level
dd %1 ; callback function
dd %2 ; user data
%endmacro
; -----------------------------------------------------------------------------
; Lock/unlock alarm list
; -----------------------------------------------------------------------------
; NOTES: Use macro ALARMLOCK to lock, ALARMUNLOCK to unlock.
; -----------------------------------------------------------------------------
; ------------- Alarm lock function
%ifdef SMP
LOCK_LockFnc AlarmLock,AlarmListLock ; declare alarm lock
%endif
; ------------- Macro - call alarm lock function
%macro ALARMLOCK 0
%ifdef SMP
call AlarmLock ; call alarm lock function
%endif
%endmacro
; ------------- Macro - alarm unlock
%macro ALARMUNLOCK 0
%ifdef SMP
LOCK_Unlock AlarmListLock ; unlock alarm
%endif
%endmacro
; -----------------------------------------------------------------------------
; Initialize alarm list
; -----------------------------------------------------------------------------
; ------------- Initialize alarm list
AlarmListInit: mov ebx,AlarmListList ; EBX <- first list of alarms
mov ecx,ALARMLIST_MAX ; ECX <- number of list of alarms
AlarmListInit2: call ListInit ; initialize list of alarms
add ebx,byte LIST_size ; EBX <- next list of alarms
loop AlarmListInit2 ; initialize next list of alarms
; ------------- Re-Initialize alam list start time
AlarmReInit: call GetSystemTime ; get system time
mov [AlarmListStart],eax ; alarm list start time LOW
mov [AlarmListStart+4],edx ; alarm list start time HIGH
ret
; -----------------------------------------------------------------------------
; Initialize alarm entry
; -----------------------------------------------------------------------------
; INPUT: EBX = alarm structure (or 0 on error)
; ECX = user data
; EDX = callback function (INPUT: EBX = alarm structure
; ECX = user data
; EDI:ESI = system time)
; DESTROYS: EAX..EBP
; NOTES: Can enable interrupts.
; It can safely delete alarm entry.
; -----------------------------------------------------------------------------
AlarmInit: push eax ; push EAX
jmp short AlarmCreate2 ; initialize alarm entry
; -----------------------------------------------------------------------------
; Create new alarm
; -----------------------------------------------------------------------------
; INPUT: ECX = user data
; EDX = callback function (INPUT: EBX = alarm structure
; ECX = user data
; EDI:ESI = system time)
; DESTROYS: EAX..EBP
; NOTES: Can enable interrupts.
; It can safely delete alarm entry.
; OUTPUT: CY = error (EBX = 0)
; EBX = alarm structure (or 0 on error)
; NOTES: If alarm is created with other method (static) then initialize link.
; -----------------------------------------------------------------------------
; ------------- Push registers
AlarmCreate: push eax ; push EAX
; ------------- Create alarm structure
mov eax,ALARM_size ; EAX <- size of alarm structure
call SysMemAlloc ; allocate alarm structure
xchg eax,ebx ; EBX <- address of alarm structure
jc short AlarmCreate8 ; memory error (EBX = 0)
; ------------- Fill up entries (and clear error flag NC)
AlarmCreate2: LISTINIT ebx ; initialize link to list
xor eax,eax ; EAX <- 0
mov [ebx+ALARM_Expire],eax ; expiration time LOW
mov [ebx+ALARM_Expire+4],eax ; expiration time HIGH
mov [ebx+ALARM_Repeat],eax ; repeat interval LOW
mov [ebx+ALARM_Repeat+4],eax ; repeat interval HIGH + level
mov [ebx+ALARM_Func],edx ; callback function
mov [ebx+ALARM_Data],ecx ; user data
; ------------- Pop registers
AlarmCreate8: pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Delete alarm
; -----------------------------------------------------------------------------
; INPUT: EBX = alarm structure
; OUTPUT: EBX = NULL
; NOTES: If alarm is running it stops it first.
; -----------------------------------------------------------------------------
; ------------- Stop alarm if it is running
AlarmDelete: call AlarmStop ; stop alarm
; ------------- Delete alarm structure
xchg eax,ebx ; EAX <- alarm structure
call SysMemFree ; free alarm structure
xchg eax,ebx ; EBX <- NULL
ret
; -----------------------------------------------------------------------------
; Set alarm at given absolute date and time
; -----------------------------------------------------------------------------
; INPUT: EDX:EAX = alarm absolute date and time (in 100-ns)
; EBX = alarm structure
; NOTES: - It recalculates absolute time to system time.
; - Repeat interval and other entries are not affected.
; - Alarm most not be running.
; -----------------------------------------------------------------------------
; ------------- Check if alarm is not running
AlarmSetAt: LISTTEST ebx ; is alarm running?
jnz short AlarmSetAt8 ; alarm is running
; ------------- Push registers
push eax ; push EAX
push edx ; push EDX
push esi ; push ESI
push edi ; push EDI
; ------------- Get start time (-> EDX:EAX)
xchg eax,esi ; ESI <- alarm LOW
mov edi,edx ; EDI <- alarm HIGH
call GetStartTime ; get start time (-> EDX:EAX)
; ------------- Recalculate absolute time to system time
sub esi,eax ; recalc to system time LOW
sbb edi,edx ; recalc to system time HIGH
; ------------- Set time of expiration
mov [ebx+ALARM_Expire],esi ; expiration time LOW
mov [ebx+ALARM_Expire+4],edi ; expiration time HIGH
; ------------- Pop registers
pop edi ; pop EDI
pop esi ; pop ESI
pop edx ; pop EDX
pop eax ; pop EAX
AlarmSetAt8: ret
; -----------------------------------------------------------------------------
; Set alarm interval
; -----------------------------------------------------------------------------
; INPUT: EAX = alarm start interval (in milliseconds, 0=now)
; EBX = alarm structure
; ECX = alarm repeat interval (in milliseconds, 0=no repeat)
; NOTES: Alarm most not be running.
; -----------------------------------------------------------------------------
; ------------- Check if alarm is not running
AlarmSetInt: LISTTEST ebx ; is alarm running?
jnz short AlarmSetInt8 ; alarm is running
; ------------- Push registers
push eax ; push EAX
push ecx ; push ECX
push edx ; push EDX
push esi ; push ESI
; ------------- Set repeat interval
xchg eax,ecx ; EAX <- repeat, ECX <- start
mov edx,TIME_1MS ; EDX <- time of 1 ms
mul edx ; recalc repeat interval to 100-ns
mov [ebx+ALARM_Repeat],eax ; repeat interval LOW
mov [ebx+ALARM_Repeat+4],edx ; repeat interval HIGH
; ------------- Get current system time (-> ESI:ECX)
call GetSystemTime ; get system time (-> EDX:EAX)
xchg eax,ecx ; ECX <- system time LOW, EAX <- start
mov esi,edx ; ESI <- system time HIGH
; ------------- Set start interval
mov edx,TIME_1MS ; EDX <- time of 1 ms
mul edx ; recalc start interval to 100-ns
add eax,ecx ; EAX <- start time LOW
adc edx,esi ; ESI <- start time HIGH
mov [ebx+ALARM_Expire],eax ; start time LOW
mov [ebx+ALARM_Expire+4],edx ; start time HIGH
; ------------- Pop registers
pop esi ; pop ESI
pop edx ; pop EDX
pop ecx ; pop ECX
pop eax ; pop EAX
AlarmSetInt8: ret
; -----------------------------------------------------------------------------
; Stop alarm (without lock)
; -----------------------------------------------------------------------------
; INPUT: EBX = alarm structure
; NOTES: Alarm must be running (it does not check if it is).
; -----------------------------------------------------------------------------
; ------------- Push registers
AlarmStop0: push eax ; push EAX
push ecx ; push ECX
; ------------- Decrease number of alarms in the list
dec dword [AlarmListTotal] ; decrease number of alarms
; ------------- Remove alarm from the list
LISTDEL ebx,eax,ecx ; remove alarm from the list
; ------------- Clear alarm list mask if it was last alarm in the list
cmp eax,ecx ; was it last entry in the list?
jne short AlarmStop02 ; it was not last entry in the list
movzx eax,byte [ebx+ALARM_Level] ; EAX <- level in alarm list
sub al,[AlarmListBase] ; EAX <- relative level
btr [AlarmListMask],eax ; clear alarm list mask
; ------------- Mark alarm as not running
AlarmStop02: LISTINIT ebx ; mark alarm as not running
; ------------- Pop registers
pop ecx ; pop ECX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Stop alarm
; -----------------------------------------------------------------------------
; INPUT: EBX = alarm structure
; NOTES: It tests if alarm is running.
; -----------------------------------------------------------------------------
; ------------- Disable interrupts
AlarmStop: pushf ; push flags
cli ; disable interrupts
; ------------- Lock alarm list
ALARMLOCK ; lock alarm list
; ------------- Check if alarm is running
LISTTEST ebx ; is alarm running?
jz short AlarmStop2 ; alarm is not running
; ------------- Stop alarm
call AlarmStop0 ; stop alarm
; ------------- Unlock alarm list
AlarmStop2: ALARMUNLOCK ; unlock alarm list
; ------------- Enable interrupts
popf ; pop flags
ret
; -----------------------------------------------------------------------------
; Start alarm (without lock)
; -----------------------------------------------------------------------------
; INPUT: EBX = alarm structure
; NOTES: Alarm must not be running (it does not check if it is).
; Minimal start time is limited to the current time (it affects
; next repeat time).
; -----------------------------------------------------------------------------
; ------------- Push registers
AlarmStart0: push eax ; push EAX
push ecx ; push ECX
push edx ; push EDX
push esi ; push ESI
; ------------- Offset of expiration time (-> EDX:EAX)
mov eax,[ebx+ALARM_Expire] ; EAX <- expiration time LOW
mov edx,[ebx+ALARM_Expire+4] ; EDX <- expiration time HIGH
sub eax,[AlarmListStart] ; EAX <- relative alarm time LOW
sbb edx,[AlarmListStart+4]; EDX <- relative alarm time HIGH
jns short AlarmStart02 ; offset is OK (non-negative)
sub [ebx+ALARM_Expire],eax ; limit expiration time LOW
sbb [ebx+ALARM_Expire+4],edx ; limit expiration time HIGH
xor eax,eax ; EAX <- 0, limit underflow LOW
xor edx,edx ; EDX <- 0, limit underflow HIGH
; ------------- Correct time to next alarm service
AlarmStart02: or edx,edx ; offset HIGH too big?
jnz short AlarmStart03 ; offset HIGH too big
or eax,eax ; offset LOW too big?
js short AlarmStart03 ; offset LOW too big
cmp eax,[AlarmListNext] ; check time to next service
jge AlarmStart03 ; time is OK
mov [AlarmListNext],eax ; correct time to next service
; ------------- Alarm list level (-> EAX)
AlarmStart03: shrd eax,edx,ALARMLIST_SHIFT ; EAX <- level in alarm list
shr edx,ALARMLIST_SHIFT ; EDX <- level HIGH
jnz short AlarmStart04 ; level overflow
cmp eax,ALARMLIST_MAX ; valid alarm list level?
jb short AlarmStart06 ; level is OK
AlarmStart04: xor eax,eax ; EAX <- 0
mov al,ALARMLIST_MAX-1 ; AL <- limit level
; ------------- Set alarm list mask
AlarmStart06: bts [AlarmListMask],eax ; set alarm list mask
; ------------- Current level in alarm list
add al,[AlarmListBase] ; add base level
mov [ebx+ALARM_Level],al ; store alarm list level
; ------------- Find place where to store alarm to
lea ecx,[AlarmListList+eax*LIST_size]; ECX<-first list head
mov esi,ecx ; ESI <- list head
mov eax,[ebx+ALARM_Expire] ; EAX <- alarm time LOW
mov edx,[ebx+ALARM_Expire+4] ; EDX <- alarm time HIGH
AlarmStart07: mov esi,[esi+LIST_Prev] ; ESI <- previous alarm
cmp esi,ecx ; last alarm?
je short AlarmStart09 ; last alarm
cmp edx,[esi+ALARM_Expire+4] ; expiration time HIGH
jne AlarmStart08 ; not equal HIGH
cmp eax,[esi+ALARM_Expire] ; expiration time LOW
AlarmStart08: jb short AlarmStart07 ; not before, check next alarm
; ------------- Store alarm into the list
AlarmStart09: LISTADD esi, ebx, eax ; add alarm after the found entry
; ------------- Increase number of alarms in the list
inc dword [AlarmListTotal] ; increase number of alarms
; ------------- Pop registers
pop esi ; pop ESI
pop edx ; pop EDX
pop ecx ; pop ECX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Start alarm
; -----------------------------------------------------------------------------
; INPUT: EBX = alarm structure
; NOTES: If alarm is running it stops it first.
; -----------------------------------------------------------------------------
; ------------- Disable interrupts
AlarmStart: pushf ; push flags
cli ; disable interrupts
; ------------- Lock alarm list
ALARMLOCK ; lock alarm list
; ------------- Check if alarm is running
LISTTEST ebx ; is alarm running?
jz short AlarmStart2 ; alarm is not running
; ------------- Stop alarm
call AlarmStop0 ; stop alarm
; ------------- Start alarm
AlarmStart2: call AlarmStart0 ; start alarm
; ------------- Unlock alarm list
ALARMUNLOCK ; unlock alarm list
; ------------- Enable interrupts
popf ; pop flags
ret
; -----------------------------------------------------------------------------
; Alarm time service
; -----------------------------------------------------------------------------
; NOTES: This function must be called with interrupts disabled and only
; from CPU 0.
; -----------------------------------------------------------------------------
; ------------- Push registers
AlarmTime: pusha ; push all registers
; ------------- Get system time with round correction 0.5 ms (-> EDI:ESI)
AlarmTime1: call GetSystemTime ; get system time
add eax,TIME_1MS/2 ; round correction 0.5 ms
adc edx,byte 0 ; carry
xchg eax,esi ; ESI <- system time LOW
mov edi,edx ; EDI <- system time HIGH
; ------------- Lock alarm list
ALARMLOCK ; lock alarm list
; ------------- Prepare next alarm request
mov eax,[AlarmListStart] ; EAX <- current start time
add eax,ALARMLIST_DIF ; EAX <- next level
sub eax,esi ; EAX <- interval to next level
mov [AlarmListNext],eax ; next service
; ------------- Get first alarm from current level of alarm list (-> EBX)
movzx ecx,byte [AlarmListBase] ; ECX <- base level
lea ecx,[AlarmListList+ecx*LIST_size] ; ECX <- list head
mov ebx,[ecx+LIST_Next] ; EBX <- first alarm from the list
cmp ebx,ecx ; is it valid alarm?
je short AlarmTime4 ; there is no alarm
; ------------- Check if this alarm has expired
cmp edi,[ebx+ALARM_Expire+4] ; check expiration time HIGH
jne short AlarmTime2 ; check alarm
cmp esi,[ebx+ALARM_Expire] ; check expiration time LOW
AlarmTime2: jb near AlarmTime8 ; alarm is not expired
; ------------- Correct next alarm request
mov eax,[ebx+LIST_Next] ; EAX <- next alarm service
cmp eax,ecx ; next alarm?
je AlarmTime3 ; no next alarm
mov eax,[eax+ALARM_Expire] ; EAX <- expiration time LOW
sub eax,esi ; EAX <- offset LOW
mov [AlarmListNext],eax ; correct time to next service
; ------------- Stop alarm
AlarmTime3: call AlarmStop0 ; stop alarm
; ------------- Add alarm with repeating to the list again
mov ecx,[ebx+ALARM_Repeat] ; ECX <- repeat time LOW
mov edx,[ebx+ALARM_Repeat+4] ; EDX <- repeat time HIGH
and edx,0ffffffh ; mask out level byte
jnz AlarmTime32 ; repeat interval should be set
jecxz AlarmTime38 ; repeat interval should not be set
AlarmTime32: add [ebx+ALARM_Expire],ecx ; new expiration time LOW
adc [ebx+ALARM_Expire+4],edx ; new expiration time HIGH
call AlarmStart0 ; start alarm again
; ------------- Prepare data for callback function
AlarmTime38: mov ecx,[ebx+ALARM_Data] ; ECX <- user data
mov edx,[ebx+ALARM_Func] ; EDX <- callback function
sub esi,TIME_1MS/2 ; return round correction 0.5 ms
sbb edi,byte 0 ; carry
; ------------- Unlock alarm list
ALARMUNLOCK ; unlock alarm list
; ------------- Call alarm callback function
call edx ; call callback function
cli ; disable interrupts
; ------------- Pop registers
jmp near AlarmTime1 ; next alarm service
; ------------- Check if base level of alarm list should be shifted
AlarmTime4: mov eax,[AlarmListStart] ; EAX <- start time LOW
mov edx,[AlarmListStart+4] ; EDX <- start time HIGH
add eax,ALARMLIST_DIF ; EAX <- new start time LOW
adc edx,byte 0 ; EDX <- new start time HIGH
cmp edi,edx ; compare next level HIGH
jne short AlarmTime5 ; HIGH not equal
cmp esi,eax ; compare next level LOW
AlarmTime5: jb near AlarmTime9 ; next level not reached
; ------------- New start time of base level
mov [AlarmListStart],eax ; new start time LOW
mov [AlarmListStart+4],edx ; new start time HIGH
; ------------- Increase alarm list base level
movzx ecx,byte [AlarmListBase] ; ECX <- alarm list base level
inc cl ; increase alarm list base level
mov [AlarmListBase],cl ; store new alarm list base level
; ------------- Correct next alarm request
lea ebp,[AlarmListList+ecx*LIST_size] ; EBP <- list head
mov ebx,[ebp+LIST_Next] ; EBX <- first alarm from the list
cmp ebx,ebp ; is it valid alarm?
je short AlarmTime54 ; there is no alarm
mov ebx,[ebx+ALARM_Expire] ; EBX <- expiration time LOW
sub ebx,esi ; EBX <- offset LOW
mov [AlarmListNext],ebx ; correct time to next service
; ------------- Rotate alarm list mask
AlarmTime54: mov ebx,AlarmListMask ; EBX <- alarm list mask
bt dword [ebx+0*4],0 ; test bit 0 (set CY if bit 0 is set)
rcr dword [ebx+7*4],1 ; rotate mask 7
rcr dword [ebx+6*4],1 ; rotate mask 6
rcr dword [ebx+5*4],1 ; rotate mask 5
rcr dword [ebx+4*4],1 ; rotate mask 4
rcr dword [ebx+3*4],1 ; rotate mask 3
rcr dword [ebx+2*4],1 ; rotate mask 2
rcr dword [ebx+1*4],1 ; rotate mask 1
rcr dword [ebx+0*4],1 ; rotate mask 0
; ------------- Prepare time of new last level (-> EDX:EAX)
AlarmTime6: add eax,ALARMLIST_DIF*(ALARMLIST_MAX-1) ; time of last list
adc edx,byte 0 ; time of last list HIGH
; ------------- Split alarms in old last level
add cl,ALARMLIST_MAX-2 ; ECX <- old last level
AlarmTime7: lea esi,[AlarmListList+ecx*LIST_size] ; ESI<-last list head
mov edi,[esi+LIST_Prev] ; EDI <- previous alarm
cmp edi,esi ; is it valid alarm?
je short AlarmTime9 ; there is no other alarm
; ------------- Check if this alarm has valid time
cmp edx,[edi+ALARM_Expire+4] ; check alarm time HIGH
jne AlarmTime72 ; HIGH not equal
cmp eax,[edi+ALARM_Expire] ; check alarm time LOW
AlarmTime72: ja short AlarmTime9 ; alarm time is OK
; ------------- Remove alarm from old list
LISTDEL edi,esi,ebx ; remove alarm from the list
; ------------- Clear alarm list mask if it was last alarm in the list
cmp esi,ebx ; was it last entry in the list?
jne short AlarmTime74 ; it was not last entry in the list
xor byte [AlarmListMask+7*4+3],B6 ; clear mask
; ------------- Add alarm into begin of new last list
AlarmTime74: mov ebx,ecx ; EBX <- current level
inc bl ; EBX <- next last level
lea ebx,[AlarmListList+ebx*LIST_size] ; EBX<-last list head
LISTADD ebx,edi,esi ; add alarm into last list
or byte [AlarmListMask+7*4+3],B7 ; set new mask
jmp short AlarmTime7 ; get next alarm
; ------------- Alarm not expired, set new next request
AlarmTime8: mov eax,[ebx+ALARM_Expire] ; EAX <- expiration time LOW
sub eax,esi ; EAX <- remaining time
mov [AlarmListNext],eax ; set remainging time
; ------------- Unlock alarm list
AlarmTime9: ALARMUNLOCK ; unlock alarm list
; ------------- Pop registers
popa ; pop all registers
ret
; -----------------------------------------------------------------------------
; Data
; -----------------------------------------------------------------------------
DATA_SECTION
; ------------- Alarm list lock
align 4, db 0
AlarmListLock: SPINLOCK ; alarm list lock
; -----------------------------------------------------------------------------
; Uninitialized data
; -----------------------------------------------------------------------------
BSS_SECTION
; ------------- Alarm list
align 4, resb 1
AlarmListList: resb LIST_size*ALARMLIST_MAX ; lists of alarms
AlarmListBase: resb 1 ; base level of alarm list
align 4, resb 1
AlarmListNext: resd 1 ; time to next service of alarm list
AlarmListStart: resd 2 ; time of current start of alarm list
AlarmListMask: resd ALARMLIST_MAX/32 ; mask of non-empty lists
AlarmListTotal: resd 1 ; total number of alarms
|