]> pd.if.org Git - mmurtl/blob - ossource/tmrcode.asm
autocommit for file dated 2003-12-29 17:36:54
[mmurtl] / ossource / tmrcode.asm
1 ;   MMURTL Operating System Source Code\r
2 ;   Copyright 1991,1992,1993,1994 Richard A. Burgess\r
3 ;   ALL RIGHTS RESERVED\r
4 ;   Version 1.0\r
5 \r
6 ; This file contains the following internal and Public calls\r
7 ; dealing with time:\r
8 ;\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
17 ;\r
18 ;Beep() and Tone() are also here for lack of a better place\r
19 ;to put them.\r
20 \r
21 .DATA\r
22 .INCLUDE MOSEDF.INC\r
23 .INCLUDE TSS.INC\r
24 \r
25 .ALIGN DWORD\r
26 \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
34 ;\r
35 sTmrBlk         EQU 12\r
36 fInUse          EQU 0   ;DD 00000000h\r
37 TmrRespExch     EQU 4   ;DD 00000000h\r
38 CountDown       EQU 8   ;DD 00000000h\r
39 ;\r
40 EXTRN SwitchTick DD\r
41 EXTRN dfHalted   DD\r
42 \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
46 \r
47 ;\r
48 ;==== Begin Code =============================================================\r
49 ;\r
50 .CODE\r
51 ;\r
52 EXTRN ChkRdyQ NEAR\r
53 EXTRN enQueueRdy NEAR\r
54 EXTRN deQueueRdy NEAR\r
55 ;\r
56 ;\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
61 ;\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
66 ;\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
70 \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
80 ; interrupted it.\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
85 ;\r
86 PUBLIC IntTimer:\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
91 \r
92 IntTmr00:                                                       ;Yes!\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
96 \r
97 IntTmr01:\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
114 \r
115 IntTmr02:\r
116                 DEC DWORD PTR [EAX+CountDown]   ;10ms more gone...\r
117 \r
118 IntTmr03:\r
119                 ADD EAX,sTmrBlk             ;next block please!\r
120                 LOOP IntTmr01               ;unless were done\r
121 \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
125                 ;is on the RdyQ\r
126 \r
127 TaskCheck:\r
128                 MOV EAX, dfHalted\r
129                 OR EAX, EAX\r
130                 JNZ TaskCheckDone\r
131 \r
132                 MOV     EAX, TimerTick\r
133                 MOV EBX, SwitchTick\r
134                 SUB EAX, EBX\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
137 \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
141 \r
142                 MOV ESI, pRunTSS\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
151 \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
156 \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
167                 POPAD                   ;\r
168                 IRETD                   ;\r
169 \r
170 TaskCheckDone:\r
171                 PUSH 0                                          ;Must do this before we switch!\r
172                 CALL FWORD PTR _EndOfIRQ    ;\r
173                 POPAD                   ;\r
174                 IRETD                   ;\r
175 \r
176 ;=============================================================================\r
177 ;\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
185 ;\r
186 DelayCnt        EQU [EBP+0Ch]\r
187 ;\r
188 ;\r
189 PUBLIC __Sleep:\r
190                 PUSH EBP                    ;\r
191                 MOV EBP,ESP                 ;\r
192                 MOV EAX, DelayCnt\r
193                 CMP EAX, 0                                      ;See if there's no delay\r
194                 JE Delay03\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
198 Delay01:\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
209                 STI\r
210                 PUSH EBX                    ;Pass exchange (for WaitMsg)\r
211                 ADD ECX,TSS_Msg             ;Offset of msg area\r
212                 PUSH ECX\r
213                 CALL FWORD PTR _WaitMsg       ;and Wait for it to come back\r
214                 MOV EAX,ErcOk               ;all is well\r
215                 JMP Delay03\r
216 Delay02:\r
217                 STI\r
218                 ADD EAX,sTmrBlk\r
219                 LOOP Delay01                ;unless were done\r
220                 MOV EAX,ErcNoMoreTBs        ;Sorry, out of timer blocks\r
221 Delay03:\r
222                 MOV ESP,EBP                 ;\r
223                 POP EBP                     ;\r
224                 RETF 4                      ;FAR return from publics\r
225 \r
226 ;=============================================================================\r
227 ;\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
232 ;\r
233 ;               Alarm(nAlarmExch, AlarmCnt):dErc\r
234 ;\r
235 AlarmExch       EQU [EBP+10h]\r
236 AlarmCnt        EQU [EBP+0Ch]\r
237 ;\r
238 ;\r
239 PUBLIC __Alarm:\r
240                 PUSH EBP                    ;\r
241                 MOV EBP,ESP                 ;\r
242                 MOV EAX, AlarmCnt\r
243                 CMP EAX, 0                                      ;See if there's no delay\r
244                 JE Alarm03\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
248 Alarm01:\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
256                 MOV EBX, AlarmExch\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
260                 JMP Alarm03\r
261 Alarm02:\r
262                 STI                         ;It's OK to interrupt now\r
263                 ADD EAX,sTmrBlk\r
264                 LOOP Alarm01                ;unless were done\r
265                 MOV EAX,ErcNoMoreTBs        ;Sorry, out of timer blocks\r
266 Alarm03:\r
267                 MOV ESP,EBP                 ;\r
268                 POP EBP                     ;\r
269                 RETF 8                      ;\r
270 ;\r
271 ;=============================================================================\r
272 ;\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
277 ;\r
278 ;               KillAlarm(nAlarmExch):dErc\r
279 ;\r
280 KAlarmExch      EQU [EBP+0Ch]\r
281 ;\r
282 ;\r
283 PUBLIC __KillAlarm:\r
284                 PUSH EBP                    ;\r
285                 MOV EBP,ESP                 ;\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
292 KAlarm01:\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
297                 JNE KAlarm02\r
298                 MOV DWORD PTR [EAX+fInUse],FALSE     ;Make Empty\r
299                 DEC nTmrBlksUsed                        ;Make blocksInUse correct\r
300 KAlarm02:\r
301                 STI                         ;It's OK to interrupt now\r
302                 ADD EAX,sTmrBlk\r
303                 LOOP KAlarm01               ;unless were done\r
304 KAlarm03:\r
305                 XOR EAX,EAX                             ;ALl done -- ErcOk\r
306                 MOV ESP,EBP                 ;\r
307                 POP EBP                     ;\r
308                 RETF 4                      ;\r
309 ;\r
310 ;=============================================================================\r
311 ;   MicroDelay(dDelay):derror  Delay count is in 15 us increments\r
312 ;\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
317 ;\r
318 PUBLIC __MicroDelay:\r
319                 PUSH EBP                    ;\r
320                 MOV EBP,ESP                 ;\r
321                 MOV ECX, [EBP+0Ch]                      ;Get delay count\r
322                 CMP ECX, 0\r
323                 JE MDL01                                        ;get out if they came in with 0!\r
324 MDL00:\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
330                 LOOP MDL00\r
331 MDL01:\r
332                 XOR EAX, EAX\r
333                 MOV ESP,EBP                 ;\r
334                 POP EBP                     ;\r
335                 RETF 4                      ;\r
336 \r
337 ;=============================================================================\r
338 ;   GetCMOSTime(pdTimeRet):derror\r
339 ;\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
345 ;\r
346 pCMOSTimeRet EQU [EBP+12]\r
347 \r
348 PUBLIC __GetCMOSTime:\r
349                 PUSH EBP                    ;\r
350                 MOV EBP,ESP                 ;\r
351                 XOR EBX, EBX                            ;Clear time return\r
352 \r
353             MOV EAX,04h                                 ;Hours\r
354                 OUT 70h,AL\r
355                 IN AL,71h\r
356                 MOV BL, AL\r
357 \r
358                 SHL EBX, 8                                      ;Minutes\r
359             MOV EAX,02h\r
360                 OUT 70h,AL\r
361                 IN AL,71h\r
362                 MOV BL,AL\r
363 \r
364                 SHL EBX, 8                      ;Seconds\r
365             MOV EAX,00h\r
366                 OUT 70h,AL\r
367                 IN AL,71h\r
368                 MOV BL,AL\r
369 \r
370                 MOV ESI, pCMOSTimeRet           ;Give 'em the time\r
371                 MOV [ESI], EBX\r
372                 XOR EAX, EAX                            ; No Error\r
373 \r
374                 MOV ESP,EBP                 ;\r
375                 POP EBP                     ;\r
376                 RETF 4                      ;\r
377 \r
378 ;=============================================================================\r
379 ;   GetCMOSDate(pdTimeRet):derror\r
380 ;\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
386 ;\r
387 pCMOSDateRet EQU [EBP+12]\r
388 \r
389 PUBLIC __GetCMOSDate:\r
390                 PUSH EBP                    ;\r
391                 MOV EBP,ESP                 ;\r
392                 XOR EBX, EBX                            ;Clear date return\r
393 \r
394             MOV EAX,09h                                 ;Year\r
395                 OUT 70h,AL\r
396                 IN AL,71h\r
397                 MOV BL, AL\r
398                 SHL EBX, 8                                      ;\r
399 \r
400             MOV EAX,08h                                 ;Month\r
401                 OUT 70h,AL\r
402                 IN AL,71h\r
403                 MOV BL, AL\r
404                 SHL EBX, 8\r
405 \r
406             MOV EAX,07h                                 ;Day of month\r
407                 OUT 70h,AL\r
408                 IN AL,71h\r
409                 MOV BL,AL\r
410                 SHL EBX, 8                      ;\r
411 \r
412             MOV EAX,06h                                 ;Day of week\r
413                 OUT 70h,AL\r
414                 IN AL,71h\r
415                 MOV BL,AL\r
416 \r
417                 MOV ESI, pCMOSDateRet           ;Give 'em the time\r
418                 MOV [ESI], EBX\r
419                 XOR EAX, EAX                            ; No Error\r
420 \r
421                 MOV ESP,EBP                 ;\r
422                 POP EBP                     ;\r
423                 RETF 4                      ;\r
424 \r
425 ;=============================================================================\r
426 ;   GetTimerTick(pdTickRet):derror\r
427 ;\r
428 ; The Current Timer Tick is returned (it's a DWord).\r
429 ;\r
430 pTickRet EQU [EBP+12]\r
431 \r
432 PUBLIC __GetTimerTick:\r
433                 PUSH EBP                    ;\r
434                 MOV EBP,ESP                 ;\r
435                 MOV ESI, pTickRet\r
436                 MOV EAX, TimerTick\r
437                 MOV [ESI], EAX\r
438                 XOR EAX, EAX                            ;No Error\r
439                 POP EBP                     ;\r
440                 RETF 4                      ;\r
441 \r
442 ;=======================================================\r
443 ; Noise makers:\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
449 ;\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
452 ;\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
457 \r
458 BEEP_Work:\r
459                 MOV AL, 10110110b           ;Timer 2, LSB, MSB, Binary\r
460                 OUT 43h, AL\r
461                 XOR EDX, EDX\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
465                 MOV AL, AH\r
466                 NOP\r
467                 NOP\r
468                 NOP\r
469                 NOP\r
470                 OUT 42h, AL\r
471                 IN AL, 61h\r
472                 OR AL, 00000011b\r
473                 PUSH EAX\r
474                 POP EAX\r
475                 OUT 61h, AL\r
476                 PUSH ECX                    ;\r
477                 CALL FWORD PTR _Sleep       ;ECX is TIME ON in 50ms incs.\r
478                 IN AL, 61h\r
479                 NOP\r
480                 NOP\r
481                 NOP\r
482                 NOP\r
483                 AND AL, 11111100b\r
484                 OUT 61h, AL\r
485                 RETN\r
486 \r
487 \r
488 ;================================================\r
489 PUBLIC __Beep:\r
490                 PUSH EBP                    ;\r
491                 MOV EBP,ESP                 ;\r
492                 MOV EBX, 800                            ;Freq\r
493                 MOV ECX, 35                                     ;350ms\r
494                 CALL Beep_Work\r
495                 POP EBP                     ;\r
496                 RETF\r
497 \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
503 ;\r
504 ToneFreq        EQU [EBP+10h]\r
505 ToneTime        EQU [EBP+0Ch]\r
506 \r
507 PUBLIC __Tone:\r
508                 PUSH EBP                        ;\r
509                 MOV EBP,ESP                     ;\r
510                 MOV EBX, ToneFreq\r
511                 MOV ECX, ToneTime\r
512                 CALL Beep_Work\r
513                 POP EBP                         ;\r
514                 RETF 8\r
515 \r
516 \r
517 ;====================== Module End =================================\r