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
)
57 SYSTEMTIME CurrentTime
;
59 /* Get the current time */
60 GetLocalTime(&CurrentTime
);
62 switch (SelectedRegister
)
64 case CMOS_REG_SECONDS
:
66 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wSecond
);
70 case CMOS_REG_ALARM_SEC
:
72 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmSecond
);
76 case CMOS_REG_MINUTES
:
78 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMinute
);
82 case CMOS_REG_ALARM_MIN
:
84 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmMinute
);
90 BOOLEAN Afternoon
= FALSE
;
91 Value
= CurrentTime
.wHour
;
93 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
99 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
101 /* Convert to 12-hour */
102 if (Afternoon
) Value
|= 0x80;
107 case CMOS_REG_ALARM_HRS
:
109 BOOLEAN Afternoon
= FALSE
;
110 Value
= CmosMemory
.AlarmHour
;
112 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
118 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
120 /* Convert to 12-hour */
121 if (Afternoon
) Value
|= 0x80;
126 case CMOS_REG_DAY_OF_WEEK
:
129 * The CMOS value is 1-based but the
130 * GetLocalTime API value is 0-based.
133 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDayOfWeek
+ 1);
139 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDay
);
145 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMonth
);
151 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
% 100);
155 case CMOS_REG_STATUS_C
:
157 /* Return the old value */
158 Value
= CmosMemory
.StatusRegC
;
160 /* Clear status register C */
161 CmosMemory
.StatusRegC
= 0x00;
166 case CMOS_REG_STATUS_A
:
167 case CMOS_REG_STATUS_B
:
168 case CMOS_REG_STATUS_D
:
169 case CMOS_REG_DIAGNOSTICS
:
170 case CMOS_REG_SHUTDOWN_STATUS
:
173 // ASSERT(SelectedRegister < CMOS_REG_MAX);
174 Value
= CmosMemory
.Regs
[SelectedRegister
];
178 /* Return to Status Register D */
179 SelectedRegister
= CMOS_REG_STATUS_D
;
184 VOID
CmosWriteData(BYTE Value
)
186 BOOLEAN ChangeTime
= FALSE
;
187 SYSTEMTIME CurrentTime
;
189 /* Get the current time */
190 GetLocalTime(&CurrentTime
);
192 switch (SelectedRegister
)
194 case CMOS_REG_SECONDS
:
197 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
201 case CMOS_REG_ALARM_SEC
:
203 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Value
);
207 case CMOS_REG_MINUTES
:
210 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
214 case CMOS_REG_ALARM_MIN
:
216 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Value
);
222 BOOLEAN Afternoon
= FALSE
;
226 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
232 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
234 /* Convert to 24-hour format */
235 if (Afternoon
) CurrentTime
.wHour
+= 12;
240 case CMOS_REG_ALARM_HRS
:
242 BOOLEAN Afternoon
= FALSE
;
244 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
& 0x80))
250 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Value
);
252 /* Convert to 24-hour format */
253 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
258 case CMOS_REG_DAY_OF_WEEK
:
262 * The CMOS value is 1-based but the
263 * SetLocalTime API value is 0-based.
267 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Value
);
274 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Value
);
281 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Value
);
289 /* Clear everything except the century */
290 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
292 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Value
);
296 case CMOS_REG_STATUS_A
:
298 CmosMemory
.StatusRegA
= Value
& 0x7F; // Bit 7 is read-only
302 case CMOS_REG_STATUS_B
:
304 CmosMemory
.StatusRegB
= Value
;
308 case CMOS_REG_STATUS_C
:
309 case CMOS_REG_STATUS_D
:
310 // Status registers C and D are read-only
313 /* Is the following correct? */
314 case CMOS_REG_EXT_MEMORY_LOW
:
315 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW
:
317 /* Sync EMS and UMS */
318 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
319 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = Value
;
323 /* Is the following correct? */
324 case CMOS_REG_EXT_MEMORY_HIGH
:
325 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
:
327 /* Sync EMS and UMS */
328 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
329 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = Value
;
335 CmosMemory
.Regs
[SelectedRegister
] = Value
;
339 if (ChangeTime
) SetLocalTime(&CurrentTime
);
341 /* Return to Status Register D */
342 SelectedRegister
= CMOS_REG_STATUS_D
;
345 BYTE WINAPI
CmosReadPort(ULONG Port
)
347 ASSERT(Port
== CMOS_DATA_PORT
);
348 return CmosReadData();
351 VOID WINAPI
CmosWritePort(ULONG Port
, BYTE Data
)
353 if (Port
== CMOS_ADDRESS_PORT
)
354 CmosWriteAddress(Data
);
355 else if (Port
== CMOS_DATA_PORT
)
359 DWORD
RtcGetTicksPerSecond(VOID
)
361 BYTE RateSelect
= CmosMemory
.StatusRegB
& 0x0F;
365 /* No periodic interrupt */
369 /* 1 and 2 act like 8 and 9 */
370 if (RateSelect
<= 2) RateSelect
+= 7;
372 return 1 << (16 - RateSelect
);
375 VOID
RtcPeriodicTick(VOID
)
378 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
380 /* Check if there should be an interrupt on a periodic timer tick */
381 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
383 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
386 PicInterruptRequest(RTC_IRQ_NUMBER
);
390 /* Should be called every second */
391 VOID
RtcTimeUpdate(VOID
)
393 SYSTEMTIME CurrentTime
;
395 /* Get the current time */
396 GetLocalTime(&CurrentTime
);
399 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
401 /* Check if the time matches the alarm time */
402 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
403 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
404 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
406 /* Set the alarm flag */
407 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
409 /* Set IRQF if there should be an interrupt */
410 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
413 /* Check if there should be an interrupt on update */
414 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
416 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
419 PicInterruptRequest(RTC_IRQ_NUMBER
);
423 VOID
CmosInitialize(VOID
)
425 DWORD CmosSize
= sizeof(CmosMemory
);
427 /* File must not be opened before */
428 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
430 /* Clear the CMOS memory */
431 ZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
433 /* Always open (and if needed, create) a RAM file with shared access */
434 SetLastError(0); // For debugging purposes
435 hCmosRam
= CreateFileW(L
"cmos.ram",
436 GENERIC_READ
| GENERIC_WRITE
,
437 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
440 FILE_ATTRIBUTE_NORMAL
,
442 DPRINT1("CMOS opening %s ; GetLastError() = %u\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
444 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
448 /* Attempt to fill the CMOS memory with the RAM file */
449 SetLastError(0); // For debugging purposes
450 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
451 if (CmosSize
!= sizeof(CmosMemory
))
453 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
454 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
455 ZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
457 DPRINT1("CMOS loading %s ; GetLastError() = %u\n", Success
? "succeeded" : "failed", GetLastError());
458 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
461 /* Overwrite some registers with default values */
462 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
463 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
464 CmosMemory
.StatusRegC
= 0x00;
465 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
466 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
467 CmosMemory
.ShutdownStatus
= 0x00;
469 /* Memory settings */
472 * Conventional memory size is 640 kB,
473 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
474 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
475 * for more information.
477 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_LOW
] = LOBYTE(0x0280);
478 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_HIGH
] = HIBYTE(0x0280);
480 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
481 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = LOBYTE((MAX_ADDRESS
- 0x100000) / 1024);
483 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
484 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = HIBYTE((MAX_ADDRESS
- 0x100000) / 1024);
486 /* Register the I/O Ports */
487 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWritePort
);
488 RegisterIoPort(CMOS_DATA_PORT
, CmosReadPort
, CmosWritePort
);
491 VOID
CmosCleanup(VOID
)
493 DWORD CmosSize
= sizeof(CmosMemory
);
495 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
497 /* Flush the CMOS memory back to the RAM file and close it */
498 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
499 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
501 CloseHandle(hCmosRam
);
502 hCmosRam
= INVALID_HANDLE_VALUE
;