--- /dev/null
+; MMURTL Operating System Source Code\r
+; Copyright 1991,1992,1993,1994 Richard A. Burgess\r
+; ALL RIGHTS RESERVED\r
+; Version 1.0\r
+\r
+; This file contains the following internal and Public calls\r
+; dealing with time:\r
+;\r
+; IntTimer - The Timer ISR\r
+; Sleep() - A function to delay execution of a task\r
+; Alarm() - A function to notify a task of a timer event\r
+; KillAlarm() - Cancels an Alarm\r
+; GetCMOSTime() - Reads the CMOS Time\r
+; GetCMOSDate() - Reads the CMOS Date\r
+; MicroDelay() - Very small delays with no task suspension\r
+; GetTimerTick() - Gets system tick count\r
+;\r
+;Beep() and Tone() are also here for lack of a better place\r
+;to put them.\r
+\r
+.DATA\r
+.INCLUDE MOSEDF.INC\r
+.INCLUDE TSS.INC\r
+\r
+.ALIGN DWORD\r
+\r
+; TIMER INTERRUPT COUNTER and TimerBlocks\r
+;-------------------------------------------\r
+; The Timer Block is a structure that contains\r
+; the exchange number of a task that is sleeping,\r
+; or one that has requested an alarm.\r
+; Each TimerBlock is 12 Bytes long\r
+; These are the offsets into the structure\r
+;\r
+sTmrBlk EQU 12\r
+fInUse EQU 0 ;DD 00000000h\r
+TmrRespExch EQU 4 ;DD 00000000h\r
+CountDown EQU 8 ;DD 00000000h\r
+;\r
+EXTRN SwitchTick DD\r
+EXTRN dfHalted DD\r
+\r
+PUBLIC TimerTick DD 0 ;Incremented every 10ms (0 on bootup).\r
+PUBLIC nTmrBlksUsed DD 0 ;Number of timer blocks in use\r
+PUBLIC rgTmrBlks DB (sTmrBlk * nTmrBlks) DUP (0)\r
+\r
+;\r
+;==== Begin Code =============================================================\r
+;\r
+.CODE\r
+;\r
+EXTRN ChkRdyQ NEAR\r
+EXTRN enQueueRdy NEAR\r
+EXTRN deQueueRdy NEAR\r
+;\r
+;\r
+; The timer interrupt checks up to n timer blocks for values to decrement.\r
+; The timer interrupt fires off every 10 milliseconds. This sounds\r
+; like a lot (and it is), but even on a 20 Mhz processor it doesn't consume\r
+; too much bandwidth at all (CPU Time).\r
+;\r
+; Interrupt latency can be a problem for high speed non-DMA comms.\r
+; A single channel operating at 19,200 bps will interrupt every 520 us.\r
+; Two channels will do it every 260us (continuous comms).\r
+; (with non-buffered UARTS that is...).\r
+;\r
+; The timer int code also performs the important function of keeping\r
+; tabs on CPU hogs. It is really the onyl part of task scheduling that\r
+; isn't cooperative. It is a small but VERY important part.\r
+\r
+; At all times on the system, only one task is actually executing.\r
+; We have now interrupted that task. Other tasks maybe waiting\r
+; at the ReadyQ to run. They may have been placed there by other\r
+; ISRs, and they may be an equal or higher priority than the task\r
+; that is now running (the one we interrupted).\r
+; We check to see if the same task has been running for 100ms or\r
+; more. If so.. we call ChkRdyQ and check the priority of that\r
+; task. If it is the same or higher, we switch to it. Then we place\r
+; the task we interrupted on the RdyQ in exactly the state we\r
+; interrupted it.\r
+; We couldn't even do it this way if we were using "interrupt tasks"\r
+; because this would "nest" hardware task swtiches. It would\r
+; require manipulating items in the TSS to make it look as if it\r
+; wasn't nested. Keep this in mind if you use the interrupt tasks.\r
+;\r
+PUBLIC IntTimer:\r
+ PUSHAD ;INTS are disabled automatically\r
+ INC TimerTick ;Timer Tick, INT 20\r
+ CMP nTmrBlksUsed, 0 ;Anyone sleeping or have an alarm set?\r
+ JE SHORT TaskCheck ;No...\r
+\r
+IntTmr00: ;Yes!\r
+ LEA EAX,rgTmrBlks ;EAX has addr of Timer Blocks\r
+ MOV ECX,nTmrBlks ;ECX has count of Timer Blocks\r
+ CLD ;Move forward thru the blocks\r
+\r
+IntTmr01:\r
+ CMP DWORD PTR [EAX+fInUse],FALSE ;Timer Block found?\r
+ JE IntTmr03 ;No - goto next block\r
+ CMP DWORD PTR [EAX+CountDown],0h ;Yes - is count at zero?\r
+ JNE IntTmr02 ;No - goto decrement it\r
+ PUSH EAX ;save ptr to rgTmpBlk\r
+ PUSH ECX ;save current count\r
+ PUSH DWORD PTR [EAX+TmrRespExch] ;Yes - ISend Message\r
+ MOV EAX, -1 ;FFFFFFFFh\r
+ PUSH EAX ;bogus msg\r
+ PUSH EAX ;bogus msg\r
+ CALL FWORD PTR _ISendMsg ;tell him his times up!\r
+ POP ECX ;get count back\r
+ POP EAX ;get ptr back\r
+ MOV DWORD PTR [EAX+fInUse],FALSE ;Free up the timer block\r
+ DEC nTmrBlksUsed ;Correct count of used blocks\r
+ JMP IntTmr03 ;skip decrement - empty blk\r
+\r
+IntTmr02:\r
+ DEC DWORD PTR [EAX+CountDown] ;10ms more gone...\r
+\r
+IntTmr03:\r
+ ADD EAX,sTmrBlk ;next block please!\r
+ LOOP IntTmr01 ;unless were done\r
+\r
+ ;We will now check to see if this guy has been running\r
+ ;for more then 30ms (3 ticks). If so, we will\r
+ ;switch him out if someone with an equal or higher pri\r
+ ;is on the RdyQ\r
+\r
+TaskCheck:\r
+ MOV EAX, dfHalted\r
+ OR EAX, EAX\r
+ JNZ TaskCheckDone\r
+\r
+ MOV EAX, TimerTick\r
+ MOV EBX, SwitchTick\r
+ SUB EAX, EBX\r
+ CMP EAX, 3 ;Change this to change the "time slice"\r
+ JL SHORT TaskCheckDone ;Hasn't been 30ms yet for this guy!\r
+\r
+ CALL ChkRdyQ ;Get next highest Pri in EAX (Leave Queued)\r
+ OR EAX, EAX ;Is there one at all??\r
+ JZ TaskCheckDone ;No...\r
+\r
+ MOV ESI, pRunTSS\r
+ MOV DL, [ESI+Priority] ;DL is pri of current\r
+ CMP DL, [EAX+Priority] ;Compare current pri to highest queued\r
+ ;The CMP subtracts Pri of Queued from\r
+ ;current pri. If the Queued Pri is LOWER\r
+ ;or Equal, we want to Switch. That means\r
+ ;if we got a CARRY on the subtract, his\r
+ ;number was higher and we DON'T\r
+ JC TaskCheckDone ;If current is higher(lower num), keep going!\r
+\r
+ CALL deQueueRdy ; Get high priority TSS off the RdyQ\r
+ MOV EDI, EAX ; and save in EDI\r
+ MOV EAX, ESI ; Put current one in EAX\r
+ CALL enQueueRdy ; and on the RdyQ\r
+\r
+ MOV pRunTSS,EDI ; Make the TSS in EDI the Running TSS\r
+ MOV BX,[EDI+Tid] ; Get the task Id (TR)\r
+ MOV TSS_Sel,BX ; Put it in the JumpAddr for Task Swtich\r
+ INC _nSwitches ; Keep track of how many switches for stats\r
+ INC _nSlices ; Keep track of how many sliced switches\r
+ MOV EAX, TimerTick ; Save time of this switch for scheduler\r
+ MOV SwitchTick, EAX ;\r
+ PUSH 0 ;Must do this before we switch!\r
+ CALL FWORD PTR _EndOfIRQ ;\r
+ JMP FWORD PTR [TSS] ; JMP TSS (This is the task swtich)\r
+ POPAD ;\r
+ IRETD ;\r
+\r
+TaskCheckDone:\r
+ PUSH 0 ;Must do this before we switch!\r
+ CALL FWORD PTR _EndOfIRQ ;\r
+ POPAD ;\r
+ IRETD ;\r
+\r
+;=============================================================================\r
+;\r
+; Sleep - A Public routine that delays the calling process by setting\r
+; up a timer block with CountDown value and an Exchange to\r
+; send a message to when the countdown reaches zero.\r
+; The timer interrupt sends the message and clears the block\r
+; when it reaches zero.\r
+; This requires an exchange. The exchange\r
+; used is the TSS_Exch in the TSS for the current task.\r
+;\r
+DelayCnt EQU [EBP+0Ch]\r
+;\r
+;\r
+PUBLIC __Sleep:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ MOV EAX, DelayCnt\r
+ CMP EAX, 0 ;See if there's no delay\r
+ JE Delay03\r
+ LEA EAX,rgTmrBlks ;EAX points to timer blocks\r
+ MOV ECX,nTmrBlks ;Count of timer blocks\r
+ CLD ;clear direction flag\r
+Delay01:\r
+ CLI ;can't let others interfere\r
+ CMP DWORD PTR [EAX+fInUse],FALSE ;Empty block?\r
+ JNE Delay02 ;No - goto next block\r
+ MOV EBX,DelayCnt ;Get delay count\r
+ MOV [EAX+CountDown],EBX ;\r
+ MOV DWORD PTR [EAX+fInUse],TRUE ;Use the Timer Block\r
+ INC nTmrBlksUsed ;Up the blocksInUse count\r
+ MOV ECX,pRunTSS ;Get TSS_Exch for our use\r
+ MOV EBX,[ECX+TSS_Exch] ;\r
+ MOV [EAX+TmrRespExch],EBX ;put it in timer block!\r
+ STI\r
+ PUSH EBX ;Pass exchange (for WaitMsg)\r
+ ADD ECX,TSS_Msg ;Offset of msg area\r
+ PUSH ECX\r
+ CALL FWORD PTR _WaitMsg ;and Wait for it to come back\r
+ MOV EAX,ErcOk ;all is well\r
+ JMP Delay03\r
+Delay02:\r
+ STI\r
+ ADD EAX,sTmrBlk\r
+ LOOP Delay01 ;unless were done\r
+ MOV EAX,ErcNoMoreTBs ;Sorry, out of timer blocks\r
+Delay03:\r
+ MOV ESP,EBP ;\r
+ POP EBP ;\r
+ RETF 4 ;FAR return from publics\r
+\r
+;=============================================================================\r
+;\r
+; Alarm - A Public routine that sends a message to the exchange that the\r
+; caller specifies after the number of ticks specified.\r
+; The message is NOT repeatable (must be set up each time).\r
+; The message will always be two DWords with 0FFFFFFFFh in each.\r
+;\r
+; Alarm(nAlarmExch, AlarmCnt):dErc\r
+;\r
+AlarmExch EQU [EBP+10h]\r
+AlarmCnt EQU [EBP+0Ch]\r
+;\r
+;\r
+PUBLIC __Alarm:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ MOV EAX, AlarmCnt\r
+ CMP EAX, 0 ;See if there's no delay\r
+ JE Alarm03\r
+ LEA EAX,rgTmrBlks ;EAX points to timer blocks\r
+ MOV ECX,nTmrBlks ;Count of timer blocks\r
+ CLD ;clear direction flag\r
+Alarm01:\r
+ CLI ;can't let others interfere\r
+ CMP DWORD PTR [EAX+fInUse],FALSE ;Empty block?\r
+ JNE Alarm02 ;No - goto next block\r
+ MOV EBX,AlarmCnt ;Get delay count\r
+ MOV [EAX+CountDown],EBX ;\r
+ MOV DWORD PTR [EAX+fInUse],TRUE ;Use the Timer Block\r
+ INC nTmrBlksUsed ;Up the blocksInUse count\r
+ MOV EBX, AlarmExch\r
+ MOV [EAX+TmrRespExch],EBX ;put it in timer block!\r
+ STI ;It's OK to interrupt now\r
+ MOV EAX,ErcOk ;all is well\r
+ JMP Alarm03\r
+Alarm02:\r
+ STI ;It's OK to interrupt now\r
+ ADD EAX,sTmrBlk\r
+ LOOP Alarm01 ;unless were done\r
+ MOV EAX,ErcNoMoreTBs ;Sorry, out of timer blocks\r
+Alarm03:\r
+ MOV ESP,EBP ;\r
+ POP EBP ;\r
+ RETF 8 ;\r
+;\r
+;=============================================================================\r
+;\r
+; KillAlarm - A Public routine that kills an alarm message that is\r
+; set to be sent to an exchange. ALL alarms set to fire off\r
+; to that exchange are killed. If the alarm is already queued\r
+; through the kernel, NOTHING will stop it...\r
+;\r
+; KillAlarm(nAlarmExch):dErc\r
+;\r
+KAlarmExch EQU [EBP+0Ch]\r
+;\r
+;\r
+PUBLIC __KillAlarm:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ CMP nTmrBlksUsed, 0 ;No blocks in use\r
+ JE KAlarm03 ; so we get out!\r
+ MOV EBX,KAlarmExch ;Get exchange for killing alarms to\r
+ LEA EAX,rgTmrBlks ;EAX points to timer blocks\r
+ MOV ECX,nTmrBlks ;Count of timer blocks\r
+ CLD ;clear direction flag\r
+KAlarm01:\r
+ CLI ;can't let others interfere\r
+ CMP DWORD PTR [EAX+fInUse],TRUE ;Block in use?\r
+ JNE KAlarm02 ;No - goto next block\r
+ CMP [EAX+TmrRespExch],EBX ;Does this match the Exchange?\r
+ JNE KAlarm02\r
+ MOV DWORD PTR [EAX+fInUse],FALSE ;Make Empty\r
+ DEC nTmrBlksUsed ;Make blocksInUse correct\r
+KAlarm02:\r
+ STI ;It's OK to interrupt now\r
+ ADD EAX,sTmrBlk\r
+ LOOP KAlarm01 ;unless were done\r
+KAlarm03:\r
+ XOR EAX,EAX ;ALl done -- ErcOk\r
+ MOV ESP,EBP ;\r
+ POP EBP ;\r
+ RETF 4 ;\r
+;\r
+;=============================================================================\r
+; MicroDelay(dDelay):derror Delay count is in 15 us increments\r
+;\r
+; The timing for this delay is based on the toggle of the refresh bit\r
+; from the System Status port. The toggle is approximately 15us. This\r
+; means this call will not be very accurate for values less than\r
+; 3 or 4 (45 to 60 microseconds). BUT, it's still very much needed.\r
+;\r
+PUBLIC __MicroDelay:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ MOV ECX, [EBP+0Ch] ;Get delay count\r
+ CMP ECX, 0\r
+ JE MDL01 ;get out if they came in with 0!\r
+MDL00:\r
+ IN AL, 61h ;Get system status port\r
+ AND AL, 10h ;check refrest bit\r
+ CMP AH, AL ;Check toggle of bit\r
+ JE MDL00 ;No toggle yet\r
+ MOV AH, AL ;Toggle! Move to AH for next compare\r
+ LOOP MDL00\r
+MDL01:\r
+ XOR EAX, EAX\r
+ MOV ESP,EBP ;\r
+ POP EBP ;\r
+ RETF 4 ;\r
+\r
+;=============================================================================\r
+; GetCMOSTime(pdTimeRet):derror\r
+;\r
+; The Time is returned from the CMOS clock as a DWord.\r
+; Low order byte is the Seconds (BCD),\r
+; Next byte is the Minutes (BCD),\r
+; Next byte is the Hours (BCD),\r
+; High order byte is 0.\r
+;\r
+pCMOSTimeRet EQU [EBP+12]\r
+\r
+PUBLIC __GetCMOSTime:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ XOR EBX, EBX ;Clear time return\r
+\r
+ MOV EAX,04h ;Hours\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL, AL\r
+\r
+ SHL EBX, 8 ;Minutes\r
+ MOV EAX,02h\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL,AL\r
+\r
+ SHL EBX, 8 ;Seconds\r
+ MOV EAX,00h\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL,AL\r
+\r
+ MOV ESI, pCMOSTimeRet ;Give 'em the time\r
+ MOV [ESI], EBX\r
+ XOR EAX, EAX ; No Error\r
+\r
+ MOV ESP,EBP ;\r
+ POP EBP ;\r
+ RETF 4 ;\r
+\r
+;=============================================================================\r
+; GetCMOSDate(pdTimeRet):derror\r
+;\r
+; The Date is returned from the CMOS clock as a DWord.\r
+; Low order byte is the Day of Week (BCD 0-6 0=Sunday),\r
+; Next byte is the Day (BCD 1-31),\r
+; Next byte is the Month (BCD 1-12),\r
+; High order byte is year (BCD 0-99).\r
+;\r
+pCMOSDateRet EQU [EBP+12]\r
+\r
+PUBLIC __GetCMOSDate:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ XOR EBX, EBX ;Clear date return\r
+\r
+ MOV EAX,09h ;Year\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL, AL\r
+ SHL EBX, 8 ;\r
+\r
+ MOV EAX,08h ;Month\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL, AL\r
+ SHL EBX, 8\r
+\r
+ MOV EAX,07h ;Day of month\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL,AL\r
+ SHL EBX, 8 ;\r
+\r
+ MOV EAX,06h ;Day of week\r
+ OUT 70h,AL\r
+ IN AL,71h\r
+ MOV BL,AL\r
+\r
+ MOV ESI, pCMOSDateRet ;Give 'em the time\r
+ MOV [ESI], EBX\r
+ XOR EAX, EAX ; No Error\r
+\r
+ MOV ESP,EBP ;\r
+ POP EBP ;\r
+ RETF 4 ;\r
+\r
+;=============================================================================\r
+; GetTimerTick(pdTickRet):derror\r
+;\r
+; The Current Timer Tick is returned (it's a DWord).\r
+;\r
+pTickRet EQU [EBP+12]\r
+\r
+PUBLIC __GetTimerTick:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ MOV ESI, pTickRet\r
+ MOV EAX, TimerTick\r
+ MOV [ESI], EAX\r
+ XOR EAX, EAX ;No Error\r
+ POP EBP ;\r
+ RETF 4 ;\r
+\r
+;=======================================================\r
+; Noise makers:\r
+; BEEP is a FAR call with no params that beeps the speaker\r
+; at 300 Hz for 350ms.\r
+; TONE is a FAR call with two parmas:\r
+; 1) FREQ in HZ (a DD)\r
+; 2) "ON" time in 10ms increments (a DD)\r
+;\r
+;The clock freq to Timer 2 is 1.193182 Mhz\r
+;To find the divisor of the clock, divide 1.193182Mhz by Desired Freq.\r
+;\r
+;================================================\r
+;This does all work for BEEP and TONE\r
+;EBX needs FREQ in HERTZ\r
+;ECX needs length of tone in 10ms increments\r
+\r
+BEEP_Work:\r
+ MOV AL, 10110110b ;Timer 2, LSB, MSB, Binary\r
+ OUT 43h, AL\r
+ XOR EDX, EDX\r
+ MOV EAX, 1193182 ;1.193182Mhz\r
+ DIV EBX ;DIVISOR is in EBX (Freq)\r
+ OUT 42h, AL ;Send quotient (left in AX)\r
+ MOV AL, AH\r
+ NOP\r
+ NOP\r
+ NOP\r
+ NOP\r
+ OUT 42h, AL\r
+ IN AL, 61h\r
+ OR AL, 00000011b\r
+ PUSH EAX\r
+ POP EAX\r
+ OUT 61h, AL\r
+ PUSH ECX ;\r
+ CALL FWORD PTR _Sleep ;ECX is TIME ON in 50ms incs.\r
+ IN AL, 61h\r
+ NOP\r
+ NOP\r
+ NOP\r
+ NOP\r
+ AND AL, 11111100b\r
+ OUT 61h, AL\r
+ RETN\r
+\r
+\r
+;================================================\r
+PUBLIC __Beep:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ MOV EBX, 800 ;Freq\r
+ MOV ECX, 35 ;350ms\r
+ CALL Beep_Work\r
+ POP EBP ;\r
+ RETF\r
+\r
+;================================================\r
+;TONE allows the caller to specify a frequency and duration of a tone\r
+;from the speaker. This call uses Beep_Work (A NEAR call to do the job)\r
+; PARAM 1 is a DD which is the FREQUENCY in HERTZ\r
+; PARAM 2 is a DD which is the length of the tone in 50ms increments\r
+;\r
+ToneFreq EQU [EBP+10h]\r
+ToneTime EQU [EBP+0Ch]\r
+\r
+PUBLIC __Tone:\r
+ PUSH EBP ;\r
+ MOV EBP,ESP ;\r
+ MOV EBX, ToneFreq\r
+ MOV ECX, ToneTime\r
+ CALL Beep_Work\r
+ POP EBP ;\r
+ RETF 8\r
+\r
+\r
+;====================== Module End =================================\r