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 *******************************************************************/
20 /* PRIVATE VARIABLES **********************************************************/
22 static HANDLE hCmosRam
= INVALID_HANDLE_VALUE
;
23 static CMOS_MEMORY CmosMemory
;
25 static BOOLEAN NmiEnabled
= TRUE
;
26 static CMOS_REGISTERS SelectedRegister
= CMOS_REG_STATUS_D
;
28 static PHARDWARE_TIMER ClockTimer
;
29 static PHARDWARE_TIMER PeriodicTimer
;
31 /* PRIVATE FUNCTIONS **********************************************************/
33 static VOID
RtcUpdatePeriodicTimer(VOID
)
35 BYTE RateSelect
= CmosMemory
.StatusRegA
& 0x0F;
39 /* No periodic interrupt */
40 DisableHardwareTimer(PeriodicTimer
);
44 /* 1 and 2 act like 8 and 9 */
45 if (RateSelect
<= 2) RateSelect
+= 7;
47 SetHardwareTimerDelay(PeriodicTimer
, HZ_TO_NS(1 << (16 - RateSelect
)));
48 EnableHardwareTimer(PeriodicTimer
);
51 static VOID FASTCALL
RtcPeriodicTick(ULONGLONG ElapsedTime
)
53 UNREFERENCED_PARAMETER(ElapsedTime
);
56 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
58 /* Check if there should be an interrupt on a periodic timer tick */
59 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
61 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
64 PicInterruptRequest(RTC_IRQ_NUMBER
);
68 /* Should be called every second */
69 static VOID FASTCALL
RtcTimeUpdate(ULONGLONG ElapsedTime
)
71 SYSTEMTIME CurrentTime
;
73 UNREFERENCED_PARAMETER(ElapsedTime
);
75 /* Get the current time */
76 GetLocalTime(&CurrentTime
);
79 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
81 /* Check if the time matches the alarm time */
82 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
83 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
84 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
86 /* Set the alarm flag */
87 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
89 /* Set IRQF if there should be an interrupt */
90 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
93 /* Check if there should be an interrupt on update */
94 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
96 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
99 PicInterruptRequest(RTC_IRQ_NUMBER
);
103 static VOID
CmosWriteAddress(BYTE Value
)
105 /* Update the NMI enabled flag */
106 NmiEnabled
= !(Value
& CMOS_DISABLE_NMI
);
108 /* Get the register number */
109 Value
&= ~CMOS_DISABLE_NMI
;
111 if (Value
< CMOS_REG_MAX
)
113 /* Select the new register */
114 SelectedRegister
= Value
;
118 /* Default to Status Register D */
119 SelectedRegister
= CMOS_REG_STATUS_D
;
123 static BYTE
CmosReadData(VOID
)
126 SYSTEMTIME CurrentTime
;
128 /* Get the current time */
129 GetLocalTime(&CurrentTime
);
131 switch (SelectedRegister
)
133 case CMOS_REG_SECONDS
:
135 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wSecond
);
139 case CMOS_REG_ALARM_SEC
:
141 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmSecond
);
145 case CMOS_REG_MINUTES
:
147 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMinute
);
151 case CMOS_REG_ALARM_MIN
:
153 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmMinute
);
159 BOOLEAN Afternoon
= FALSE
;
160 Value
= CurrentTime
.wHour
;
162 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
168 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
170 /* Convert to 12-hour */
171 if (Afternoon
) Value
|= 0x80;
176 case CMOS_REG_ALARM_HRS
:
178 BOOLEAN Afternoon
= FALSE
;
179 Value
= CmosMemory
.AlarmHour
;
181 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
187 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
189 /* Convert to 12-hour */
190 if (Afternoon
) Value
|= 0x80;
195 case CMOS_REG_DAY_OF_WEEK
:
198 * The CMOS value is 1-based but the
199 * GetLocalTime API value is 0-based.
202 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDayOfWeek
+ 1);
208 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDay
);
214 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMonth
);
220 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
% 100);
224 case CMOS_REG_STATUS_C
:
226 /* Return the old value */
227 Value
= CmosMemory
.StatusRegC
;
229 /* Clear status register C */
230 CmosMemory
.StatusRegC
= 0x00;
235 case CMOS_REG_STATUS_A
:
236 case CMOS_REG_STATUS_B
:
237 case CMOS_REG_STATUS_D
:
238 case CMOS_REG_DIAGNOSTICS
:
239 case CMOS_REG_SHUTDOWN_STATUS
:
242 // ASSERT(SelectedRegister < CMOS_REG_MAX);
243 Value
= CmosMemory
.Regs
[SelectedRegister
];
247 /* Return to Status Register D */
248 SelectedRegister
= CMOS_REG_STATUS_D
;
253 static VOID
CmosWriteData(BYTE Value
)
255 BOOLEAN ChangeTime
= FALSE
;
256 SYSTEMTIME CurrentTime
;
258 /* Get the current time */
259 GetLocalTime(&CurrentTime
);
261 switch (SelectedRegister
)
263 case CMOS_REG_SECONDS
:
266 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
270 case CMOS_REG_ALARM_SEC
:
272 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
276 case CMOS_REG_MINUTES
:
279 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
283 case CMOS_REG_ALARM_MIN
:
285 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
291 BOOLEAN Afternoon
= FALSE
;
295 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
301 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
303 /* Convert to 24-hour format */
304 if (Afternoon
) CurrentTime
.wHour
+= 12;
309 case CMOS_REG_ALARM_HRS
:
311 BOOLEAN Afternoon
= FALSE
;
313 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
319 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
321 /* Convert to 24-hour format */
322 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
327 case CMOS_REG_DAY_OF_WEEK
:
331 * The CMOS value is 1-based but the
332 * SetLocalTime API value is 0-based.
336 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Value
);
343 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Value
);
350 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Value
);
358 /* Clear everything except the century */
359 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
361 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Value
);
365 case CMOS_REG_STATUS_A
:
367 CmosMemory
.StatusRegA
= Value
& 0x7F; // Bit 7 is read-only
368 RtcUpdatePeriodicTimer();
372 case CMOS_REG_STATUS_B
:
374 CmosMemory
.StatusRegB
= Value
;
378 case CMOS_REG_STATUS_C
:
379 case CMOS_REG_STATUS_D
:
380 // Status registers C and D are read-only
383 /* Is the following correct? */
384 case CMOS_REG_EXT_MEMORY_LOW
:
385 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW
:
387 /* Sync EMS and UMS */
388 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
389 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = Value
;
393 /* Is the following correct? */
394 case CMOS_REG_EXT_MEMORY_HIGH
:
395 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
:
397 /* Sync EMS and UMS */
398 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
399 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = Value
;
405 CmosMemory
.Regs
[SelectedRegister
] = Value
;
409 if (ChangeTime
) SetLocalTime(&CurrentTime
);
411 /* Return to Status Register D */
412 SelectedRegister
= CMOS_REG_STATUS_D
;
415 static BYTE WINAPI
CmosReadPort(USHORT Port
)
417 ASSERT(Port
== CMOS_DATA_PORT
);
418 return CmosReadData();
421 static VOID WINAPI
CmosWritePort(USHORT Port
, BYTE Data
)
423 if (Port
== CMOS_ADDRESS_PORT
)
424 CmosWriteAddress(Data
);
425 else if (Port
== CMOS_DATA_PORT
)
430 /* PUBLIC FUNCTIONS ***********************************************************/
432 BOOLEAN
IsNmiEnabled(VOID
)
437 VOID
CmosInitialize(VOID
)
439 DWORD CmosSize
= sizeof(CmosMemory
);
441 /* File must not be opened before */
442 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
444 /* Clear the CMOS memory */
445 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
447 /* Always open (and if needed, create) a RAM file with shared access */
448 SetLastError(0); // For debugging purposes
449 hCmosRam
= CreateFileW(L
"cmos.ram",
450 GENERIC_READ
| GENERIC_WRITE
,
451 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
454 FILE_ATTRIBUTE_NORMAL
,
456 DPRINT1("CMOS opening %s ; GetLastError() = %u\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
458 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
462 /* Attempt to fill the CMOS memory with the RAM file */
463 SetLastError(0); // For debugging purposes
464 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
465 if (CmosSize
!= sizeof(CmosMemory
))
467 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
468 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
469 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
471 DPRINT1("CMOS loading %s ; GetLastError() = %u\n", Success
? "succeeded" : "failed", GetLastError());
472 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
475 /* Overwrite some registers with default values */
476 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
477 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
478 CmosMemory
.StatusRegC
= 0x00;
479 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
480 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
481 CmosMemory
.ShutdownStatus
= 0x00;
483 /* Memory settings */
486 * Conventional memory size is 640 kB,
487 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
488 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
489 * for more information.
491 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_LOW
] = LOBYTE(0x0280);
492 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_HIGH
] = HIBYTE(0x0280);
494 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
495 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = LOBYTE((MAX_ADDRESS
- 0x100000) / 1024);
497 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
498 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = HIBYTE((MAX_ADDRESS
- 0x100000) / 1024);
500 /* Register the I/O Ports */
501 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWritePort
);
502 RegisterIoPort(CMOS_DATA_PORT
, CmosReadPort
, CmosWritePort
);
504 ClockTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
,
507 PeriodicTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
| HARDWARE_TIMER_PRECISE
,
512 VOID
CmosCleanup(VOID
)
514 DWORD CmosSize
= sizeof(CmosMemory
);
516 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
518 DestroyHardwareTimer(PeriodicTimer
);
519 DestroyHardwareTimer(ClockTimer
);
521 /* Flush the CMOS memory back to the RAM file and close it */
522 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
523 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
525 CloseHandle(hCmosRam
);
526 hCmosRam
= INVALID_HANDLE_VALUE
;