2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
5 * PURPOSE: CMOS Real Time Clock emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
9 /* INCLUDES *******************************************************************/
19 /* PRIVATE VARIABLES **********************************************************/
21 static HANDLE hCmosRam
= INVALID_HANDLE_VALUE
;
22 static CMOS_MEMORY CmosMemory
;
24 static BOOLEAN NmiEnabled
= TRUE
;
25 static CMOS_REGISTERS SelectedRegister
= CMOS_REG_STATUS_D
;
27 /* PUBLIC FUNCTIONS ***********************************************************/
29 BOOLEAN
IsNmiEnabled(VOID
)
34 VOID
CmosWriteAddress(BYTE Value
)
36 /* Update the NMI enabled flag */
37 NmiEnabled
= !(Value
& CMOS_DISABLE_NMI
);
39 /* Get the register number */
40 Value
&= ~CMOS_DISABLE_NMI
;
42 if (Value
< CMOS_REG_MAX
)
44 /* Select the new register */
45 SelectedRegister
= Value
;
49 /* Default to Status Register D */
50 SelectedRegister
= CMOS_REG_STATUS_D
;
54 BYTE
CmosReadData(VOID
)
56 SYSTEMTIME CurrentTime
;
58 /* Get the current time */
59 GetLocalTime(&CurrentTime
);
61 switch (SelectedRegister
)
63 case CMOS_REG_SECONDS
:
64 return READ_CMOS_DATA(CmosMemory
, CurrentTime
.wSecond
);
66 case CMOS_REG_ALARM_SEC
:
67 return READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmSecond
);
69 case CMOS_REG_MINUTES
:
70 return READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMinute
);
72 case CMOS_REG_ALARM_MIN
:
73 return READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmMinute
);
77 BOOLEAN Afternoon
= FALSE
;
78 BYTE Value
= CurrentTime
.wHour
;
80 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
86 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
88 /* Convert to 12-hour */
89 if (Afternoon
) Value
|= 0x80;
94 case CMOS_REG_ALARM_HRS
:
96 BOOLEAN Afternoon
= FALSE
;
97 BYTE Value
= CmosMemory
.AlarmHour
;
99 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
105 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
107 /* Convert to 12-hour */
108 if (Afternoon
) Value
|= 0x80;
113 case CMOS_REG_DAY_OF_WEEK
:
115 * The CMOS value is 1-based but the
116 * GetLocalTime API value is 0-based.
119 return READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDayOfWeek
+ 1);
122 return READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDay
);
125 return READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMonth
);
128 return READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
% 100);
130 case CMOS_REG_STATUS_C
:
132 BYTE Value
= CmosMemory
.StatusRegC
;
134 /* Clear status register C */
135 CmosMemory
.StatusRegC
= 0x00;
137 /* Return the old value */
141 case CMOS_REG_STATUS_A
:
142 case CMOS_REG_STATUS_B
:
143 case CMOS_REG_STATUS_D
:
144 case CMOS_REG_DIAGNOSTICS
:
145 case CMOS_REG_SHUTDOWN_STATUS
:
148 // ASSERT(SelectedRegister < CMOS_REG_MAX);
149 return CmosMemory
.Regs
[SelectedRegister
];
153 /* Return to Status Register D */
154 SelectedRegister
= CMOS_REG_STATUS_D
;
157 VOID
CmosWriteData(BYTE Value
)
159 BOOLEAN ChangeTime
= FALSE
;
160 SYSTEMTIME CurrentTime
;
162 /* Get the current time */
163 GetLocalTime(&CurrentTime
);
165 switch (SelectedRegister
)
167 case CMOS_REG_SECONDS
:
170 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
174 case CMOS_REG_ALARM_SEC
:
176 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
180 case CMOS_REG_MINUTES
:
183 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
187 case CMOS_REG_ALARM_MIN
:
189 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
195 BOOLEAN Afternoon
= FALSE
;
199 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
205 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
207 /* Convert to 24-hour format */
208 if (Afternoon
) CurrentTime
.wHour
+= 12;
213 case CMOS_REG_ALARM_HRS
:
215 BOOLEAN Afternoon
= FALSE
;
217 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
223 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
225 /* Convert to 24-hour format */
226 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
231 case CMOS_REG_DAY_OF_WEEK
:
235 * The CMOS value is 1-based but the
236 * SetLocalTime API value is 0-based.
240 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Value
);
247 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Value
);
254 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Value
);
262 /* Clear everything except the century */
263 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
265 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Value
);
269 case CMOS_REG_STATUS_A
:
271 CmosMemory
.StatusRegA
= Value
& 0x7F; // Bit 7 is read-only
275 case CMOS_REG_STATUS_B
:
277 CmosMemory
.StatusRegB
= Value
;
281 case CMOS_REG_STATUS_C
:
282 case CMOS_REG_STATUS_D
:
283 // Status registers C and D are read-only
286 /* Is the following correct? */
287 case CMOS_REG_EXT_MEMORY_LOW
:
288 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW
:
290 /* Sync EMS and UMS */
291 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
292 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = Value
;
296 /* Is the following correct? */
297 case CMOS_REG_EXT_MEMORY_HIGH
:
298 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
:
300 /* Sync EMS and UMS */
301 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
302 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = Value
;
308 CmosMemory
.Regs
[SelectedRegister
] = Value
;
312 if (ChangeTime
) SetLocalTime(&CurrentTime
);
314 /* Return to Status Register D */
315 SelectedRegister
= CMOS_REG_STATUS_D
;
318 BYTE WINAPI
CmosReadPort(ULONG Port
)
320 ASSERT(Port
== CMOS_DATA_PORT
);
321 return CmosReadData();
324 VOID WINAPI
CmosWritePort(ULONG Port
, BYTE Data
)
326 if (Port
== CMOS_ADDRESS_PORT
)
327 CmosWriteAddress(Data
);
328 else if (Port
== CMOS_DATA_PORT
)
332 DWORD
RtcGetTicksPerSecond(VOID
)
334 BYTE RateSelect
= CmosMemory
.StatusRegB
& 0x0F;
338 /* No periodic interrupt */
342 /* 1 and 2 act like 8 and 9 */
343 if (RateSelect
<= 2) RateSelect
+= 7;
345 return 1 << (16 - RateSelect
);
348 VOID
RtcPeriodicTick(VOID
)
351 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
353 /* Check if there should be an interrupt on a periodic timer tick */
354 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
356 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
359 PicInterruptRequest(RTC_IRQ_NUMBER
);
363 /* Should be called every second */
364 VOID
RtcTimeUpdate(VOID
)
366 SYSTEMTIME CurrentTime
;
368 /* Get the current time */
369 GetLocalTime(&CurrentTime
);
372 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
374 /* Check if the time matches the alarm time */
375 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
376 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
377 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
379 /* Set the alarm flag */
380 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
382 /* Set IRQF if there should be an interrupt */
383 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
386 /* Check if there should be an interrupt on update */
387 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
389 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
392 PicInterruptRequest(RTC_IRQ_NUMBER
);
396 VOID
CmosInitialize(VOID
)
398 DWORD CmosSize
= sizeof(CmosMemory
);
400 /* File must not be opened before */
401 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
403 /* Clear the CMOS memory */
404 ZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
406 /* Always open (and if needed, create) a RAM file with shared access */
407 SetLastError(0); // For debugging purposes
408 hCmosRam
= CreateFileW(L
"cmos.ram",
409 GENERIC_READ
| GENERIC_WRITE
,
410 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
413 FILE_ATTRIBUTE_NORMAL
,
415 DPRINT1("CMOS opening %s ; GetLastError() = %u\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
417 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
421 /* Attempt to fill the CMOS memory with the RAM file */
422 SetLastError(0); // For debugging purposes
423 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
424 if (CmosSize
!= sizeof(CmosMemory
))
426 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
427 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
428 ZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
430 DPRINT1("CMOS loading %s ; GetLastError() = %u\n", Success
? "succeeded" : "failed", GetLastError());
431 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
434 /* Overwrite some registers with default values */
435 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
436 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
437 CmosMemory
.StatusRegC
= 0x00;
438 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
439 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
440 CmosMemory
.ShutdownStatus
= 0x00;
442 /* Memory settings */
445 * Conventional memory size is 640 kB,
446 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
447 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
448 * for more information.
450 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_LOW
] = LOBYTE(0x0280);
451 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_HIGH
] = HIBYTE(0x0280);
453 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
454 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = LOBYTE((MAX_ADDRESS
- 0x100000) / 1024);
456 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
457 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = HIBYTE((MAX_ADDRESS
- 0x100000) / 1024);
459 /* Register the I/O Ports */
460 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWritePort
);
461 RegisterIoPort(CMOS_DATA_PORT
, CmosReadPort
, CmosWritePort
);
464 VOID
CmosCleanup(VOID
)
466 DWORD CmosSize
= sizeof(CmosMemory
);
468 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
470 /* Flush the CMOS memory back to the RAM file and close it */
471 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
472 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
474 CloseHandle(hCmosRam
);
475 hCmosRam
= INVALID_HANDLE_VALUE
;