2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/hardware/cmos.c
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 // FIXME: This call keeps EnableCount increasing without compensating it!
50 EnableHardwareTimer(PeriodicTimer
);
53 static VOID FASTCALL
RtcPeriodicTick(ULONGLONG ElapsedTime
)
55 UNREFERENCED_PARAMETER(ElapsedTime
);
58 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
60 /* Check if there should be an interrupt on a periodic timer tick */
61 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
63 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
66 PicInterruptRequest(RTC_IRQ_NUMBER
);
70 /* Should be called every second */
71 static VOID FASTCALL
RtcTimeUpdate(ULONGLONG ElapsedTime
)
73 SYSTEMTIME CurrentTime
;
75 UNREFERENCED_PARAMETER(ElapsedTime
);
77 /* Get the current time */
78 GetLocalTime(&CurrentTime
);
81 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
83 /* Check if the time matches the alarm time */
84 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
85 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
86 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
88 /* Set the alarm flag */
89 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
91 /* Set IRQF if there should be an interrupt */
92 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
95 /* Check if there should be an interrupt on update */
96 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
98 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
101 PicInterruptRequest(RTC_IRQ_NUMBER
);
105 static VOID WINAPI
CmosWriteAddress(USHORT Port
, BYTE Data
)
107 UNREFERENCED_PARAMETER(Port
);
109 /* Update the NMI enabled flag */
110 NmiEnabled
= !(Data
& CMOS_DISABLE_NMI
);
112 /* Get the register number */
113 Data
&= ~CMOS_DISABLE_NMI
;
115 if (Data
< CMOS_REG_MAX
)
117 /* Select the new register */
118 SelectedRegister
= Data
;
122 /* Default to Status Register D */
123 SelectedRegister
= CMOS_REG_STATUS_D
;
127 static BYTE WINAPI
CmosReadData(USHORT Port
)
130 SYSTEMTIME CurrentTime
;
132 UNREFERENCED_PARAMETER(Port
);
134 /* Get the current time */
135 GetLocalTime(&CurrentTime
);
137 switch (SelectedRegister
)
139 case CMOS_REG_SECONDS
:
141 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wSecond
);
145 case CMOS_REG_ALARM_SEC
:
147 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmSecond
);
151 case CMOS_REG_MINUTES
:
153 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMinute
);
157 case CMOS_REG_ALARM_MIN
:
159 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmMinute
);
165 BOOLEAN Afternoon
= FALSE
;
166 Value
= CurrentTime
.wHour
;
168 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
174 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
176 /* Convert to 12-hour */
177 if (Afternoon
) Value
|= 0x80;
182 case CMOS_REG_ALARM_HRS
:
184 BOOLEAN Afternoon
= FALSE
;
185 Value
= CmosMemory
.AlarmHour
;
187 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
193 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
195 /* Convert to 12-hour */
196 if (Afternoon
) Value
|= 0x80;
201 case CMOS_REG_DAY_OF_WEEK
:
204 * The CMOS value is 1-based but the
205 * GetLocalTime API value is 0-based.
208 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDayOfWeek
+ 1);
214 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDay
);
220 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMonth
);
226 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
% 100);
230 case CMOS_REG_CENTURY
:
232 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
/ 100 + 19);
236 case CMOS_REG_STATUS_C
:
238 /* Return the old value */
239 Value
= CmosMemory
.StatusRegC
;
241 /* Clear status register C */
242 CmosMemory
.StatusRegC
= 0x00;
247 case CMOS_REG_STATUS_A
:
248 case CMOS_REG_STATUS_B
:
249 case CMOS_REG_STATUS_D
:
250 case CMOS_REG_DIAGNOSTICS
:
251 case CMOS_REG_SHUTDOWN_STATUS
:
254 // ASSERT(SelectedRegister < CMOS_REG_MAX);
255 Value
= CmosMemory
.Regs
[SelectedRegister
];
259 /* Return to Status Register D */
260 SelectedRegister
= CMOS_REG_STATUS_D
;
265 static VOID WINAPI
CmosWriteData(USHORT Port
, BYTE Data
)
267 BOOLEAN ChangeTime
= FALSE
;
268 SYSTEMTIME CurrentTime
;
270 UNREFERENCED_PARAMETER(Port
);
272 /* Get the current time */
273 GetLocalTime(&CurrentTime
);
275 switch (SelectedRegister
)
277 case CMOS_REG_SECONDS
:
280 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Data
);
284 case CMOS_REG_ALARM_SEC
:
286 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Data
);
290 case CMOS_REG_MINUTES
:
293 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Data
);
297 case CMOS_REG_ALARM_MIN
:
299 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Data
);
305 BOOLEAN Afternoon
= FALSE
;
309 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Data
& 0x80))
315 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Data
);
317 /* Convert to 24-hour format */
318 if (Afternoon
) CurrentTime
.wHour
+= 12;
323 case CMOS_REG_ALARM_HRS
:
325 BOOLEAN Afternoon
= FALSE
;
327 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Data
& 0x80))
333 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Data
);
335 /* Convert to 24-hour format */
336 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
341 case CMOS_REG_DAY_OF_WEEK
:
345 * The CMOS value is 1-based but the
346 * SetLocalTime API value is 0-based.
350 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Data
);
357 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Data
);
364 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Data
);
372 /* Clear everything except the century */
373 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
375 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Data
);
379 case CMOS_REG_CENTURY
:
385 case CMOS_REG_STATUS_A
:
387 CmosMemory
.StatusRegA
= Data
& 0x7F; // Bit 7 is read-only
388 RtcUpdatePeriodicTimer();
392 case CMOS_REG_STATUS_B
:
394 CmosMemory
.StatusRegB
= Data
;
398 case CMOS_REG_STATUS_C
:
399 case CMOS_REG_STATUS_D
:
400 // Status registers C and D are read-only
403 /* Is the following correct? */
404 case CMOS_REG_EXT_MEMORY_LOW
:
405 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW
:
407 /* Sync EMS and UMS */
408 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
409 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = Data
;
413 /* Is the following correct? */
414 case CMOS_REG_EXT_MEMORY_HIGH
:
415 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
:
417 /* Sync EMS and UMS */
418 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
419 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = Data
;
425 CmosMemory
.Regs
[SelectedRegister
] = Data
;
429 if (ChangeTime
) SetLocalTime(&CurrentTime
);
431 /* Return to Status Register D */
432 SelectedRegister
= CMOS_REG_STATUS_D
;
436 /* PUBLIC FUNCTIONS ***********************************************************/
438 BOOLEAN
IsNmiEnabled(VOID
)
443 VOID
CmosInitialize(VOID
)
445 DWORD CmosSize
= sizeof(CmosMemory
);
447 /* File must not be opened before */
448 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
450 /* Clear the CMOS memory */
451 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
453 /* Always open (and if needed, create) a RAM file with shared access */
454 SetLastError(0); // For debugging purposes
455 hCmosRam
= CreateFileW(L
"cmos.ram",
456 GENERIC_READ
| GENERIC_WRITE
,
457 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
460 FILE_ATTRIBUTE_NORMAL
,
462 DPRINT1("CMOS opening %s (Error: %u)\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
464 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
468 /* Attempt to fill the CMOS memory with the RAM file */
469 SetLastError(0); // For debugging purposes
470 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
471 if (CmosSize
!= sizeof(CmosMemory
))
473 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
474 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
475 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
477 DPRINT1("CMOS loading %s (Error: %u)\n", Success
? "succeeded" : "failed", GetLastError());
478 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
481 /* Overwrite some registers with default values */
482 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
483 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
484 CmosMemory
.StatusRegC
= 0x00;
485 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
486 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
487 CmosMemory
.ShutdownStatus
= 0x00;
489 /* Memory settings */
492 * Conventional memory size is 640 kB,
493 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
494 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
495 * for more information.
497 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_LOW
] = LOBYTE(0x0280);
498 CmosMemory
.Regs
[CMOS_REG_BASE_MEMORY_HIGH
] = HIBYTE(0x0280);
500 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_LOW
] =
501 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_LOW
] = LOBYTE((MAX_ADDRESS
- 0x100000) / 1024);
503 CmosMemory
.Regs
[CMOS_REG_EXT_MEMORY_HIGH
] =
504 CmosMemory
.Regs
[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
] = HIBYTE((MAX_ADDRESS
- 0x100000) / 1024);
506 /* Register the I/O Ports */
507 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWriteAddress
);
508 RegisterIoPort(CMOS_DATA_PORT
, CmosReadData
, CmosWriteData
);
510 ClockTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
,
513 PeriodicTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
| HARDWARE_TIMER_PRECISE
,
518 VOID
CmosCleanup(VOID
)
520 DWORD CmosSize
= sizeof(CmosMemory
);
522 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
524 DestroyHardwareTimer(PeriodicTimer
);
525 DestroyHardwareTimer(ClockTimer
);
527 /* Flush the CMOS memory back to the RAM file and close it */
528 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
529 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
531 CloseHandle(hCmosRam
);
532 hCmosRam
= INVALID_HANDLE_VALUE
;