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 *******************************************************************/
16 #include "bios/bios.h"
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_BASE_MEMORY_LOW
:
142 return Bda
->MemorySize
& 0xFF;
144 case CMOS_REG_BASE_MEMORY_HIGH
:
145 return Bda
->MemorySize
>> 8;
147 case CMOS_REG_EXT_MEMORY_LOW
:
148 return ((MAX_ADDRESS
- 0x100000) / 1024) & 0xFF;
150 case CMOS_REG_EXT_MEMORY_HIGH
:
151 return ((MAX_ADDRESS
- 0x100000) / 1024) >> 8;
153 case CMOS_REG_STATUS_A
:
154 case CMOS_REG_STATUS_B
:
155 case CMOS_REG_STATUS_D
:
156 case CMOS_REG_DIAGNOSTICS
:
157 case CMOS_REG_SHUTDOWN_STATUS
:
160 // ASSERT(SelectedRegister < CMOS_REG_MAX);
161 return CmosMemory
.Regs
[SelectedRegister
];
165 /* Return to Status Register D */
166 SelectedRegister
= CMOS_REG_STATUS_D
;
169 VOID
CmosWriteData(BYTE Value
)
171 BOOLEAN ChangeTime
= FALSE
;
172 SYSTEMTIME CurrentTime
;
174 /* Get the current time */
175 GetLocalTime(&CurrentTime
);
177 switch (SelectedRegister
)
179 case CMOS_REG_SECONDS
:
182 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
186 case CMOS_REG_ALARM_SEC
:
188 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
192 case CMOS_REG_MINUTES
:
195 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
199 case CMOS_REG_ALARM_MIN
:
201 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
207 BOOLEAN Afternoon
= FALSE
;
211 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
217 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
219 /* Convert to 24-hour format */
220 if (Afternoon
) CurrentTime
.wHour
+= 12;
225 case CMOS_REG_ALARM_HRS
:
227 BOOLEAN Afternoon
= FALSE
;
229 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
235 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
237 /* Convert to 24-hour format */
238 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
243 case CMOS_REG_DAY_OF_WEEK
:
247 * The CMOS value is 1-based but the
248 * SetLocalTime API value is 0-based.
252 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Value
);
259 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Value
);
266 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Value
);
274 /* Clear everything except the century */
275 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
277 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Value
);
281 case CMOS_REG_STATUS_A
:
283 CmosMemory
.StatusRegA
= Value
& 0x7F; // Bit 7 is read-only
287 case CMOS_REG_STATUS_B
:
289 CmosMemory
.StatusRegB
= Value
;
293 case CMOS_REG_STATUS_C
:
294 case CMOS_REG_STATUS_D
:
295 // Status registers C and D are read-only
300 CmosMemory
.Regs
[SelectedRegister
] = Value
;
304 if (ChangeTime
) SetLocalTime(&CurrentTime
);
306 /* Return to Status Register D */
307 SelectedRegister
= CMOS_REG_STATUS_D
;
310 BYTE WINAPI
CmosReadPort(ULONG Port
)
312 return CmosReadData();
315 VOID WINAPI
CmosWritePort(ULONG Port
, BYTE Data
)
317 if (Port
== CMOS_ADDRESS_PORT
)
318 CmosWriteAddress(Data
);
319 else if (Port
== CMOS_DATA_PORT
)
323 DWORD
RtcGetTicksPerSecond(VOID
)
325 BYTE RateSelect
= CmosMemory
.StatusRegB
& 0x0F;
329 /* No periodic interrupt */
333 /* 1 and 2 act like 8 and 9 */
334 if (RateSelect
<= 2) RateSelect
+= 7;
336 return 1 << (16 - RateSelect
);
339 VOID
RtcPeriodicTick(VOID
)
342 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
344 /* Check if there should be an interrupt on a periodic timer tick */
345 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
347 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
350 PicInterruptRequest(RTC_IRQ_NUMBER
);
354 /* Should be called every second */
355 VOID
RtcTimeUpdate(VOID
)
357 SYSTEMTIME CurrentTime
;
359 /* Get the current time */
360 GetLocalTime(&CurrentTime
);
363 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
365 /* Check if the time matches the alarm time */
366 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
367 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
368 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
370 /* Set the alarm flag */
371 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
373 /* Set IRQF if there should be an interrupt */
374 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
377 /* Check if there should be an interrupt on update */
378 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
380 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
383 PicInterruptRequest(RTC_IRQ_NUMBER
);
387 BOOLEAN
CmosInitialize(VOID
)
389 DWORD CmosSize
= sizeof(CmosMemory
);
391 /* File must not be opened before */
392 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
394 /* Clear the CMOS memory */
395 ZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
397 /* Always open (and if needed, create) a RAM file with shared access */
398 SetLastError(0); // For debugging purposes
399 hCmosRam
= CreateFileW(L
"cmos.ram",
400 GENERIC_READ
| GENERIC_WRITE
,
401 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
404 FILE_ATTRIBUTE_NORMAL
,
406 DPRINT1("CMOS opening %s ; GetLastError() = %u\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
408 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
410 BOOL Success
= FALSE
;
412 /* Attempt to fill the CMOS memory with the RAM file */
413 SetLastError(0); // For debugging purposes
414 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
415 if (CmosSize
!= sizeof(CmosMemory
))
417 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
418 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
419 ZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
421 DPRINT1("CMOS loading %s ; GetLastError() = %u\n", Success
? "succeeded" : "failed", GetLastError());
422 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
425 /* Overwrite some registers with default values */
426 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
427 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
428 CmosMemory
.StatusRegC
= 0x00;
429 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
430 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
431 CmosMemory
.ShutdownStatus
= 0x00;
433 /* Register the I/O Ports */
434 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWritePort
);
435 RegisterIoPort(CMOS_DATA_PORT
, CmosReadPort
, CmosWritePort
);
440 VOID
CmosCleanup(VOID
)
442 DWORD CmosSize
= sizeof(CmosMemory
);
444 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
446 /* Flush the CMOS memory back to the RAM file and close it */
447 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
448 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
450 CloseHandle(hCmosRam
);
451 hCmosRam
= INVALID_HANDLE_VALUE
;