1 ; MMURTL Operating System Source Code
\r
2 ; Copyright 1991,1992,1993,1994 Richard A. Burgess
\r
3 ; ALL RIGHTS RESERVED
\r
6 ; This file contains the following internal and Public calls
\r
9 ; IntTimer - The Timer ISR
\r
10 ; Sleep() - A function to delay execution of a task
\r
11 ; Alarm() - A function to notify a task of a timer event
\r
12 ; KillAlarm() - Cancels an Alarm
\r
13 ; GetCMOSTime() - Reads the CMOS Time
\r
14 ; GetCMOSDate() - Reads the CMOS Date
\r
15 ; MicroDelay() - Very small delays with no task suspension
\r
16 ; GetTimerTick() - Gets system tick count
\r
18 ;Beep() and Tone() are also here for lack of a better place
\r
27 ; TIMER INTERRUPT COUNTER and TimerBlocks
\r
28 ;-------------------------------------------
\r
29 ; The Timer Block is a structure that contains
\r
30 ; the exchange number of a task that is sleeping,
\r
31 ; or one that has requested an alarm.
\r
32 ; Each TimerBlock is 12 Bytes long
\r
33 ; These are the offsets into the structure
\r
36 fInUse EQU 0 ;DD 00000000h
\r
37 TmrRespExch EQU 4 ;DD 00000000h
\r
38 CountDown EQU 8 ;DD 00000000h
\r
43 PUBLIC TimerTick DD 0 ;Incremented every 10ms (0 on bootup).
\r
44 PUBLIC nTmrBlksUsed DD 0 ;Number of timer blocks in use
\r
45 PUBLIC rgTmrBlks DB (sTmrBlk * nTmrBlks) DUP (0)
\r
48 ;==== Begin Code =============================================================
\r
53 EXTRN enQueueRdy NEAR
\r
54 EXTRN deQueueRdy NEAR
\r
57 ; The timer interrupt checks up to n timer blocks for values to decrement.
\r
58 ; The timer interrupt fires off every 10 milliseconds. This sounds
\r
59 ; like a lot (and it is), but even on a 20 Mhz processor it doesn't consume
\r
60 ; too much bandwidth at all (CPU Time).
\r
62 ; Interrupt latency can be a problem for high speed non-DMA comms.
\r
63 ; A single channel operating at 19,200 bps will interrupt every 520 us.
\r
64 ; Two channels will do it every 260us (continuous comms).
\r
65 ; (with non-buffered UARTS that is...).
\r
67 ; The timer int code also performs the important function of keeping
\r
68 ; tabs on CPU hogs. It is really the onyl part of task scheduling that
\r
69 ; isn't cooperative. It is a small but VERY important part.
\r
71 ; At all times on the system, only one task is actually executing.
\r
72 ; We have now interrupted that task. Other tasks maybe waiting
\r
73 ; at the ReadyQ to run. They may have been placed there by other
\r
74 ; ISRs, and they may be an equal or higher priority than the task
\r
75 ; that is now running (the one we interrupted).
\r
76 ; We check to see if the same task has been running for 100ms or
\r
77 ; more. If so.. we call ChkRdyQ and check the priority of that
\r
78 ; task. If it is the same or higher, we switch to it. Then we place
\r
79 ; the task we interrupted on the RdyQ in exactly the state we
\r
81 ; We couldn't even do it this way if we were using "interrupt tasks"
\r
82 ; because this would "nest" hardware task swtiches. It would
\r
83 ; require manipulating items in the TSS to make it look as if it
\r
84 ; wasn't nested. Keep this in mind if you use the interrupt tasks.
\r
87 PUSHAD ;INTS are disabled automatically
\r
88 INC TimerTick ;Timer Tick, INT 20
\r
89 CMP nTmrBlksUsed, 0 ;Anyone sleeping or have an alarm set?
\r
90 JE SHORT TaskCheck ;No...
\r
93 LEA EAX,rgTmrBlks ;EAX has addr of Timer Blocks
\r
94 MOV ECX,nTmrBlks ;ECX has count of Timer Blocks
\r
95 CLD ;Move forward thru the blocks
\r
98 CMP DWORD PTR [EAX+fInUse],FALSE ;Timer Block found?
\r
99 JE IntTmr03 ;No - goto next block
\r
100 CMP DWORD PTR [EAX+CountDown],0h ;Yes - is count at zero?
\r
101 JNE IntTmr02 ;No - goto decrement it
\r
102 PUSH EAX ;save ptr to rgTmpBlk
\r
103 PUSH ECX ;save current count
\r
104 PUSH DWORD PTR [EAX+TmrRespExch] ;Yes - ISend Message
\r
105 MOV EAX, -1 ;FFFFFFFFh
\r
106 PUSH EAX ;bogus msg
\r
107 PUSH EAX ;bogus msg
\r
108 CALL FWORD PTR _ISendMsg ;tell him his times up!
\r
109 POP ECX ;get count back
\r
110 POP EAX ;get ptr back
\r
111 MOV DWORD PTR [EAX+fInUse],FALSE ;Free up the timer block
\r
112 DEC nTmrBlksUsed ;Correct count of used blocks
\r
113 JMP IntTmr03 ;skip decrement - empty blk
\r
116 DEC DWORD PTR [EAX+CountDown] ;10ms more gone...
\r
119 ADD EAX,sTmrBlk ;next block please!
\r
120 LOOP IntTmr01 ;unless were done
\r
122 ;We will now check to see if this guy has been running
\r
123 ;for more then 30ms (3 ticks). If so, we will
\r
124 ;switch him out if someone with an equal or higher pri
\r
133 MOV EBX, SwitchTick
\r
135 CMP EAX, 3 ;Change this to change the "time slice"
\r
136 JL SHORT TaskCheckDone ;Hasn't been 30ms yet for this guy!
\r
138 CALL ChkRdyQ ;Get next highest Pri in EAX (Leave Queued)
\r
139 OR EAX, EAX ;Is there one at all??
\r
140 JZ TaskCheckDone ;No...
\r
143 MOV DL, [ESI+Priority] ;DL is pri of current
\r
144 CMP DL, [EAX+Priority] ;Compare current pri to highest queued
\r
145 ;The CMP subtracts Pri of Queued from
\r
146 ;current pri. If the Queued Pri is LOWER
\r
147 ;or Equal, we want to Switch. That means
\r
148 ;if we got a CARRY on the subtract, his
\r
149 ;number was higher and we DON'T
\r
150 JC TaskCheckDone ;If current is higher(lower num), keep going!
\r
152 CALL deQueueRdy ; Get high priority TSS off the RdyQ
\r
153 MOV EDI, EAX ; and save in EDI
\r
154 MOV EAX, ESI ; Put current one in EAX
\r
155 CALL enQueueRdy ; and on the RdyQ
\r
157 MOV pRunTSS,EDI ; Make the TSS in EDI the Running TSS
\r
158 MOV BX,[EDI+Tid] ; Get the task Id (TR)
\r
159 MOV TSS_Sel,BX ; Put it in the JumpAddr for Task Swtich
\r
160 INC _nSwitches ; Keep track of how many switches for stats
\r
161 INC _nSlices ; Keep track of how many sliced switches
\r
162 MOV EAX, TimerTick ; Save time of this switch for scheduler
\r
163 MOV SwitchTick, EAX ;
\r
164 PUSH 0 ;Must do this before we switch!
\r
165 CALL FWORD PTR _EndOfIRQ ;
\r
166 JMP FWORD PTR [TSS] ; JMP TSS (This is the task swtich)
\r
171 PUSH 0 ;Must do this before we switch!
\r
172 CALL FWORD PTR _EndOfIRQ ;
\r
176 ;=============================================================================
\r
178 ; Sleep - A Public routine that delays the calling process by setting
\r
179 ; up a timer block with CountDown value and an Exchange to
\r
180 ; send a message to when the countdown reaches zero.
\r
181 ; The timer interrupt sends the message and clears the block
\r
182 ; when it reaches zero.
\r
183 ; This requires an exchange. The exchange
\r
184 ; used is the TSS_Exch in the TSS for the current task.
\r
186 DelayCnt EQU [EBP+0Ch]
\r
193 CMP EAX, 0 ;See if there's no delay
\r
195 LEA EAX,rgTmrBlks ;EAX points to timer blocks
\r
196 MOV ECX,nTmrBlks ;Count of timer blocks
\r
197 CLD ;clear direction flag
\r
199 CLI ;can't let others interfere
\r
200 CMP DWORD PTR [EAX+fInUse],FALSE ;Empty block?
\r
201 JNE Delay02 ;No - goto next block
\r
202 MOV EBX,DelayCnt ;Get delay count
\r
203 MOV [EAX+CountDown],EBX ;
\r
204 MOV DWORD PTR [EAX+fInUse],TRUE ;Use the Timer Block
\r
205 INC nTmrBlksUsed ;Up the blocksInUse count
\r
206 MOV ECX,pRunTSS ;Get TSS_Exch for our use
\r
207 MOV EBX,[ECX+TSS_Exch] ;
\r
208 MOV [EAX+TmrRespExch],EBX ;put it in timer block!
\r
210 PUSH EBX ;Pass exchange (for WaitMsg)
\r
211 ADD ECX,TSS_Msg ;Offset of msg area
\r
213 CALL FWORD PTR _WaitMsg ;and Wait for it to come back
\r
214 MOV EAX,ErcOk ;all is well
\r
219 LOOP Delay01 ;unless were done
\r
220 MOV EAX,ErcNoMoreTBs ;Sorry, out of timer blocks
\r
224 RETF 4 ;FAR return from publics
\r
226 ;=============================================================================
\r
228 ; Alarm - A Public routine that sends a message to the exchange that the
\r
229 ; caller specifies after the number of ticks specified.
\r
230 ; The message is NOT repeatable (must be set up each time).
\r
231 ; The message will always be two DWords with 0FFFFFFFFh in each.
\r
233 ; Alarm(nAlarmExch, AlarmCnt):dErc
\r
235 AlarmExch EQU [EBP+10h]
\r
236 AlarmCnt EQU [EBP+0Ch]
\r
243 CMP EAX, 0 ;See if there's no delay
\r
245 LEA EAX,rgTmrBlks ;EAX points to timer blocks
\r
246 MOV ECX,nTmrBlks ;Count of timer blocks
\r
247 CLD ;clear direction flag
\r
249 CLI ;can't let others interfere
\r
250 CMP DWORD PTR [EAX+fInUse],FALSE ;Empty block?
\r
251 JNE Alarm02 ;No - goto next block
\r
252 MOV EBX,AlarmCnt ;Get delay count
\r
253 MOV [EAX+CountDown],EBX ;
\r
254 MOV DWORD PTR [EAX+fInUse],TRUE ;Use the Timer Block
\r
255 INC nTmrBlksUsed ;Up the blocksInUse count
\r
257 MOV [EAX+TmrRespExch],EBX ;put it in timer block!
\r
258 STI ;It's OK to interrupt now
\r
259 MOV EAX,ErcOk ;all is well
\r
262 STI ;It's OK to interrupt now
\r
264 LOOP Alarm01 ;unless were done
\r
265 MOV EAX,ErcNoMoreTBs ;Sorry, out of timer blocks
\r
271 ;=============================================================================
\r
273 ; KillAlarm - A Public routine that kills an alarm message that is
\r
274 ; set to be sent to an exchange. ALL alarms set to fire off
\r
275 ; to that exchange are killed. If the alarm is already queued
\r
276 ; through the kernel, NOTHING will stop it...
\r
278 ; KillAlarm(nAlarmExch):dErc
\r
280 KAlarmExch EQU [EBP+0Ch]
\r
283 PUBLIC __KillAlarm:
\r
286 CMP nTmrBlksUsed, 0 ;No blocks in use
\r
287 JE KAlarm03 ; so we get out!
\r
288 MOV EBX,KAlarmExch ;Get exchange for killing alarms to
\r
289 LEA EAX,rgTmrBlks ;EAX points to timer blocks
\r
290 MOV ECX,nTmrBlks ;Count of timer blocks
\r
291 CLD ;clear direction flag
\r
293 CLI ;can't let others interfere
\r
294 CMP DWORD PTR [EAX+fInUse],TRUE ;Block in use?
\r
295 JNE KAlarm02 ;No - goto next block
\r
296 CMP [EAX+TmrRespExch],EBX ;Does this match the Exchange?
\r
298 MOV DWORD PTR [EAX+fInUse],FALSE ;Make Empty
\r
299 DEC nTmrBlksUsed ;Make blocksInUse correct
\r
301 STI ;It's OK to interrupt now
\r
303 LOOP KAlarm01 ;unless were done
\r
305 XOR EAX,EAX ;ALl done -- ErcOk
\r
310 ;=============================================================================
\r
311 ; MicroDelay(dDelay):derror Delay count is in 15 us increments
\r
313 ; The timing for this delay is based on the toggle of the refresh bit
\r
314 ; from the System Status port. The toggle is approximately 15us. This
\r
315 ; means this call will not be very accurate for values less than
\r
316 ; 3 or 4 (45 to 60 microseconds). BUT, it's still very much needed.
\r
318 PUBLIC __MicroDelay:
\r
321 MOV ECX, [EBP+0Ch] ;Get delay count
\r
323 JE MDL01 ;get out if they came in with 0!
\r
325 IN AL, 61h ;Get system status port
\r
326 AND AL, 10h ;check refrest bit
\r
327 CMP AH, AL ;Check toggle of bit
\r
328 JE MDL00 ;No toggle yet
\r
329 MOV AH, AL ;Toggle! Move to AH for next compare
\r
337 ;=============================================================================
\r
338 ; GetCMOSTime(pdTimeRet):derror
\r
340 ; The Time is returned from the CMOS clock as a DWord.
\r
341 ; Low order byte is the Seconds (BCD),
\r
342 ; Next byte is the Minutes (BCD),
\r
343 ; Next byte is the Hours (BCD),
\r
344 ; High order byte is 0.
\r
346 pCMOSTimeRet EQU [EBP+12]
\r
348 PUBLIC __GetCMOSTime:
\r
351 XOR EBX, EBX ;Clear time return
\r
358 SHL EBX, 8 ;Minutes
\r
364 SHL EBX, 8 ;Seconds
\r
370 MOV ESI, pCMOSTimeRet ;Give 'em the time
\r
372 XOR EAX, EAX ; No Error
\r
378 ;=============================================================================
\r
379 ; GetCMOSDate(pdTimeRet):derror
\r
381 ; The Date is returned from the CMOS clock as a DWord.
\r
382 ; Low order byte is the Day of Week (BCD 0-6 0=Sunday),
\r
383 ; Next byte is the Day (BCD 1-31),
\r
384 ; Next byte is the Month (BCD 1-12),
\r
385 ; High order byte is year (BCD 0-99).
\r
387 pCMOSDateRet EQU [EBP+12]
\r
389 PUBLIC __GetCMOSDate:
\r
392 XOR EBX, EBX ;Clear date return
\r
406 MOV EAX,07h ;Day of month
\r
412 MOV EAX,06h ;Day of week
\r
417 MOV ESI, pCMOSDateRet ;Give 'em the time
\r
419 XOR EAX, EAX ; No Error
\r
425 ;=============================================================================
\r
426 ; GetTimerTick(pdTickRet):derror
\r
428 ; The Current Timer Tick is returned (it's a DWord).
\r
430 pTickRet EQU [EBP+12]
\r
432 PUBLIC __GetTimerTick:
\r
438 XOR EAX, EAX ;No Error
\r
442 ;=======================================================
\r
444 ; BEEP is a FAR call with no params that beeps the speaker
\r
445 ; at 300 Hz for 350ms.
\r
446 ; TONE is a FAR call with two parmas:
\r
447 ; 1) FREQ in HZ (a DD)
\r
448 ; 2) "ON" time in 10ms increments (a DD)
\r
450 ;The clock freq to Timer 2 is 1.193182 Mhz
\r
451 ;To find the divisor of the clock, divide 1.193182Mhz by Desired Freq.
\r
453 ;================================================
\r
454 ;This does all work for BEEP and TONE
\r
455 ;EBX needs FREQ in HERTZ
\r
456 ;ECX needs length of tone in 10ms increments
\r
459 MOV AL, 10110110b ;Timer 2, LSB, MSB, Binary
\r
462 MOV EAX, 1193182 ;1.193182Mhz
\r
463 DIV EBX ;DIVISOR is in EBX (Freq)
\r
464 OUT 42h, AL ;Send quotient (left in AX)
\r
477 CALL FWORD PTR _Sleep ;ECX is TIME ON in 50ms incs.
\r
488 ;================================================
\r
498 ;================================================
\r
499 ;TONE allows the caller to specify a frequency and duration of a tone
\r
500 ;from the speaker. This call uses Beep_Work (A NEAR call to do the job)
\r
501 ; PARAM 1 is a DD which is the FREQUENCY in HERTZ
\r
502 ; PARAM 2 is a DD which is the length of the tone in 50ms increments
\r
504 ToneFreq EQU [EBP+10h]
\r
505 ToneTime EQU [EBP+0Ch]
\r
517 ;====================== Module End =================================
\r