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 *******************************************************************/
21 /* PRIVATE VARIABLES **********************************************************/
23 static HANDLE hCmosRam
= INVALID_HANDLE_VALUE
;
24 static CMOS_MEMORY CmosMemory
;
26 static BOOLEAN NmiEnabled
= TRUE
;
27 static CMOS_REGISTERS SelectedRegister
= CMOS_REG_STATUS_D
;
29 static PHARDWARE_TIMER ClockTimer
;
30 static PHARDWARE_TIMER PeriodicTimer
;
32 /* PRIVATE FUNCTIONS **********************************************************/
34 static VOID
RtcUpdatePeriodicTimer(VOID
)
36 BYTE RateSelect
= CmosMemory
.StatusRegA
& 0x0F;
40 /* No periodic interrupt */
41 DisableHardwareTimer(PeriodicTimer
);
45 /* 1 and 2 act like 8 and 9 */
46 if (RateSelect
<= 2) RateSelect
+= 7;
48 SetHardwareTimerDelay(PeriodicTimer
, HZ_TO_NS(1 << (16 - RateSelect
)));
49 EnableHardwareTimer(PeriodicTimer
);
52 static VOID FASTCALL
RtcPeriodicTick(ULONGLONG ElapsedTime
)
54 UNREFERENCED_PARAMETER(ElapsedTime
);
57 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
59 /* Check if there should be an interrupt on a periodic timer tick */
60 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
62 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
65 PicInterruptRequest(RTC_IRQ_NUMBER
);
69 /* Should be called every second */
70 static VOID FASTCALL
RtcTimeUpdate(ULONGLONG ElapsedTime
)
72 SYSTEMTIME CurrentTime
;
74 UNREFERENCED_PARAMETER(ElapsedTime
);
76 /* Get the current time */
77 GetLocalTime(&CurrentTime
);
80 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
82 /* Check if the time matches the alarm time */
83 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
84 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
85 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
87 /* Set the alarm flag */
88 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
90 /* Set IRQF if there should be an interrupt */
91 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
94 /* Check if there should be an interrupt on update */
95 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
97 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
100 PicInterruptRequest(RTC_IRQ_NUMBER
);
104 static VOID
CmosWriteAddress(BYTE Value
)
106 /* Update the NMI enabled flag */
107 NmiEnabled
= !(Value
& CMOS_DISABLE_NMI
);
109 /* Get the register number */
110 Value
&= ~CMOS_DISABLE_NMI
;
112 if (Value
< CMOS_REG_MAX
)
114 /* Select the new register */
115 SelectedRegister
= Value
;
119 /* Default to Status Register D */
120 SelectedRegister
= CMOS_REG_STATUS_D
;
124 static BYTE
CmosReadData(VOID
)
127 SYSTEMTIME CurrentTime
;
129 /* Get the current time */
130 GetLocalTime(&CurrentTime
);
132 switch (SelectedRegister
)
134 case CMOS_REG_SECONDS
:
136 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wSecond
);
140 case CMOS_REG_ALARM_SEC
:
142 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmSecond
);
146 case CMOS_REG_MINUTES
:
148 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMinute
);
152 case CMOS_REG_ALARM_MIN
:
154 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmMinute
);
160 BOOLEAN Afternoon
= FALSE
;
161 Value
= CurrentTime
.wHour
;
163 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
169 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
171 /* Convert to 12-hour */
172 if (Afternoon
) Value
|= 0x80;
177 case CMOS_REG_ALARM_HRS
:
179 BOOLEAN Afternoon
= FALSE
;
180 Value
= CmosMemory
.AlarmHour
;
182 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
188 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
190 /* Convert to 12-hour */
191 if (Afternoon
) Value
|= 0x80;
196 case CMOS_REG_DAY_OF_WEEK
:
199 * The CMOS value is 1-based but the
200 * GetLocalTime API value is 0-based.
203 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDayOfWeek
+ 1);
209 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDay
);
215 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMonth
);
221 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
% 100);
225 case CMOS_REG_STATUS_C
:
227 /* Return the old value */
228 Value
= CmosMemory
.StatusRegC
;
230 /* Clear status register C */
231 CmosMemory
.StatusRegC
= 0x00;
236 case CMOS_REG_STATUS_A
:
237 case CMOS_REG_STATUS_B
:
238 case CMOS_REG_STATUS_D
:
239 case CMOS_REG_DIAGNOSTICS
:
240 case CMOS_REG_SHUTDOWN_STATUS
:
243 // ASSERT(SelectedRegister < CMOS_REG_MAX);
244 Value
= CmosMemory
.Regs
[SelectedRegister
];
248 /* Return to Status Register D */
249 SelectedRegister
= CMOS_REG_STATUS_D
;
254 static VOID
CmosWriteData(BYTE Value
)
256 BOOLEAN ChangeTime
= FALSE
;
257 SYSTEMTIME CurrentTime
;
259 /* Get the current time */
260 GetLocalTime(&CurrentTime
);
262 switch (SelectedRegister
)
264 case CMOS_REG_SECONDS
:
267 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
271 case CMOS_REG_ALARM_SEC
:
273 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
277 case CMOS_REG_MINUTES
:
280 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
284 case CMOS_REG_ALARM_MIN
:
286 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
292 BOOLEAN Afternoon
= FALSE
;
296 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
302 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
304 /* Convert to 24-hour format */
305 if (Afternoon
) CurrentTime
.wHour
+= 12;
310 case CMOS_REG_ALARM_HRS
:
312 BOOLEAN Afternoon
= FALSE
;
314 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
320 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
322 /* Convert to 24-hour format */
323 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
328 case CMOS_REG_DAY_OF_WEEK
:
332 * The CMOS value is 1-based but the
333 * SetLocalTime API value is 0-based.
337 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Value
);
344 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Value
);
351 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Value
);
359 /* Clear everything except the century */
360 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
362 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Value
);
366 case CMOS_REG_STATUS_A
:
368 CmosMemory
.StatusRegA
= Value
& 0x7F; // Bit 7 is read-only
369 RtcUpdatePeriodicTimer();
373 case CMOS_REG_STATUS_B
:
375 CmosMemory
.StatusRegB
= Value
;
379 case CMOS_REG_STATUS_C
:
380 case CMOS_REG_STATUS_D
:
381 // Status registers C and D are read-only
384 /* Is the following correct? */
385 case CMOS_REG_EXT_MEMORY_LOW
:
386 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW
:
388 /* Sync EMS and UMS */
389 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
390 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = Value
;
394 /* Is the following correct? */
395 case CMOS_REG_EXT_MEMORY_HIGH
:
396 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
:
398 /* Sync EMS and UMS */
399 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
400 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = Value
;
406 CmosMemory
.Regs
[SelectedRegister
] = Value
;
410 if (ChangeTime
) SetLocalTime(&CurrentTime
);
412 /* Return to Status Register D */
413 SelectedRegister
= CMOS_REG_STATUS_D
;
416 static BYTE WINAPI
CmosReadPort(USHORT Port
)
418 ASSERT(Port
== CMOS_DATA_PORT
);
419 return CmosReadData();
422 static VOID WINAPI
CmosWritePort(USHORT Port
, BYTE Data
)
424 if (Port
== CMOS_ADDRESS_PORT
)
425 CmosWriteAddress(Data
);
426 else if (Port
== CMOS_DATA_PORT
)
431 /* PUBLIC FUNCTIONS ***********************************************************/
433 BOOLEAN
IsNmiEnabled(VOID
)
438 VOID
CmosInitialize(VOID
)
440 DWORD CmosSize
= sizeof(CmosMemory
);
442 /* File must not be opened before */
443 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
445 /* Clear the CMOS memory */
446 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
448 /* Always open (and if needed, create) a RAM file with shared access */
449 SetLastError(0); // For debugging purposes
450 hCmosRam
= CreateFileW(L
"cmos.ram",
451 GENERIC_READ
| GENERIC_WRITE
,
452 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
455 FILE_ATTRIBUTE_NORMAL
,
457 DPRINT1("CMOS opening %s (Error: %u)\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
459 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
463 /* Attempt to fill the CMOS memory with the RAM file */
464 SetLastError(0); // For debugging purposes
465 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
466 if (CmosSize
!= sizeof(CmosMemory
))
468 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
469 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
470 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
472 DPRINT1("CMOS loading %s (Error: %u)\n", Success
? "succeeded" : "failed", GetLastError());
473 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
476 /* Overwrite some registers with default values */
477 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
478 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
479 CmosMemory
.StatusRegC
= 0x00;
480 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
481 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
482 CmosMemory
.ShutdownStatus
= 0x00;
484 /* Memory settings */
487 * Conventional memory size is 640 kB,
488 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
489 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
490 * for more information.
492 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_LOW
] = LOBYTE(0x0280);
493 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_HIGH
] = HIBYTE(0x0280);
495 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
496 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = LOBYTE((MAX_ADDRESS
- 0x100000) / 1024);
498 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
499 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = HIBYTE((MAX_ADDRESS
- 0x100000) / 1024);
501 /* Register the I/O Ports */
502 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWritePort
);
503 RegisterIoPort(CMOS_DATA_PORT
, CmosReadPort
, CmosWritePort
);
505 ClockTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
,
508 PeriodicTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
| HARDWARE_TIMER_PRECISE
,
513 VOID
CmosCleanup(VOID
)
515 DWORD CmosSize
= sizeof(CmosMemory
);
517 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
519 DestroyHardwareTimer(PeriodicTimer
);
520 DestroyHardwareTimer(ClockTimer
);
522 /* Flush the CMOS memory back to the RAM file and close it */
523 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
524 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
526 CloseHandle(hCmosRam
);
527 hCmosRam
= INVALID_HANDLE_VALUE
;