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 *******************************************************************/
23 /* PRIVATE VARIABLES **********************************************************/
25 static HANDLE hCmosRam
= INVALID_HANDLE_VALUE
;
26 static CMOS_MEMORY CmosMemory
;
28 static BOOLEAN NmiEnabled
= TRUE
;
29 static CMOS_REGISTERS SelectedRegister
= CMOS_REG_STATUS_D
;
31 static PHARDWARE_TIMER ClockTimer
;
32 static PHARDWARE_TIMER PeriodicTimer
;
34 /* PRIVATE FUNCTIONS **********************************************************/
36 static VOID
RtcUpdatePeriodicTimer(VOID
)
38 BYTE RateSelect
= CmosMemory
.StatusRegA
& 0x0F;
42 /* No periodic interrupt */
43 DisableHardwareTimer(PeriodicTimer
);
47 /* 1 and 2 act like 8 and 9 */
48 if (RateSelect
<= 2) RateSelect
+= 7;
50 SetHardwareTimerDelay(PeriodicTimer
, HZ_TO_NS(1 << (16 - RateSelect
)));
51 // FIXME: This call keeps EnableCount increasing without compensating it!
52 EnableHardwareTimer(PeriodicTimer
);
55 static VOID FASTCALL
RtcPeriodicTick(ULONGLONG ElapsedTime
)
57 UNREFERENCED_PARAMETER(ElapsedTime
);
60 CmosMemory
.StatusRegC
|= CMOS_STC_PF
;
62 /* Check if there should be an interrupt on a periodic timer tick */
63 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_PERIODIC
)
65 CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
68 PicInterruptRequest(RTC_IRQ_NUMBER
);
72 /* Should be called every second */
73 static VOID FASTCALL
RtcTimeUpdate(ULONGLONG ElapsedTime
)
75 SYSTEMTIME CurrentTime
;
77 UNREFERENCED_PARAMETER(ElapsedTime
);
79 /* Get the current time */
80 GetLocalTime(&CurrentTime
);
83 CmosMemory
.StatusRegC
|= CMOS_STC_UF
;
85 /* Check if the time matches the alarm time */
86 if ((CurrentTime
.wHour
== CmosMemory
.AlarmHour
) &&
87 (CurrentTime
.wMinute
== CmosMemory
.AlarmMinute
) &&
88 (CurrentTime
.wSecond
== CmosMemory
.AlarmSecond
))
90 /* Set the alarm flag */
91 CmosMemory
.StatusRegC
|= CMOS_STC_AF
;
93 /* Set IRQF if there should be an interrupt */
94 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_ALARM
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
97 /* Check if there should be an interrupt on update */
98 if (CmosMemory
.StatusRegB
& CMOS_STB_INT_ON_UPDATE
) CmosMemory
.StatusRegC
|= CMOS_STC_IRQF
;
100 if (CmosMemory
.StatusRegC
& CMOS_STC_IRQF
)
103 PicInterruptRequest(RTC_IRQ_NUMBER
);
107 static VOID WINAPI
CmosWriteAddress(USHORT Port
, BYTE Data
)
109 UNREFERENCED_PARAMETER(Port
);
111 /* Update the NMI enabled flag */
112 NmiEnabled
= !(Data
& CMOS_DISABLE_NMI
);
114 /* Get the register number */
115 Data
&= ~CMOS_DISABLE_NMI
;
117 if (Data
< CMOS_REG_MAX
)
119 /* Select the new register */
120 SelectedRegister
= Data
;
124 /* Default to Status Register D */
125 SelectedRegister
= CMOS_REG_STATUS_D
;
129 static BYTE WINAPI
CmosReadData(USHORT Port
)
132 SYSTEMTIME CurrentTime
;
134 UNREFERENCED_PARAMETER(Port
);
136 /* Get the current time */
137 GetLocalTime(&CurrentTime
);
139 switch (SelectedRegister
)
141 case CMOS_REG_SECONDS
:
143 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wSecond
);
147 case CMOS_REG_ALARM_SEC
:
149 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmSecond
);
153 case CMOS_REG_MINUTES
:
155 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMinute
);
159 case CMOS_REG_ALARM_MIN
:
161 Value
= READ_CMOS_DATA(CmosMemory
, CmosMemory
.AlarmMinute
);
167 BOOLEAN Afternoon
= FALSE
;
168 Value
= CurrentTime
.wHour
;
170 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
176 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
178 /* Convert to 12-hour */
179 if (Afternoon
) Value
|= 0x80;
184 case CMOS_REG_ALARM_HRS
:
186 BOOLEAN Afternoon
= FALSE
;
187 Value
= CmosMemory
.AlarmHour
;
189 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Value
>= 12))
195 Value
= READ_CMOS_DATA(CmosMemory
, Value
);
197 /* Convert to 12-hour */
198 if (Afternoon
) Value
|= 0x80;
203 case CMOS_REG_DAY_OF_WEEK
:
206 * The CMOS value is 1-based but the
207 * GetLocalTime API value is 0-based.
210 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDayOfWeek
+ 1);
216 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wDay
);
222 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wMonth
);
228 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
% 100);
232 case CMOS_REG_CENTURY
:
234 Value
= READ_CMOS_DATA(CmosMemory
, CurrentTime
.wYear
/ 100 + 19);
238 case CMOS_REG_STATUS_C
:
240 /* Return the old status register value, then clear it */
241 Value
= CmosMemory
.StatusRegC
;
242 CmosMemory
.StatusRegC
= 0x00;
246 case CMOS_REG_STATUS_A
:
247 case CMOS_REG_STATUS_B
:
248 case CMOS_REG_STATUS_D
:
249 case CMOS_REG_DIAGNOSTICS
:
250 case CMOS_REG_SHUTDOWN_STATUS
:
253 // ASSERT(SelectedRegister < CMOS_REG_MAX);
254 Value
= CmosMemory
.Regs
[SelectedRegister
];
258 /* Return to Status Register D */
259 SelectedRegister
= CMOS_REG_STATUS_D
;
264 static VOID WINAPI
CmosWriteData(USHORT Port
, BYTE Data
)
266 BOOLEAN ChangeTime
= FALSE
;
267 SYSTEMTIME CurrentTime
;
269 UNREFERENCED_PARAMETER(Port
);
271 /* Get the current time */
272 GetLocalTime(&CurrentTime
);
274 switch (SelectedRegister
)
276 case CMOS_REG_SECONDS
:
279 CurrentTime
.wSecond
= WRITE_CMOS_DATA(CmosMemory
, Data
);
283 case CMOS_REG_ALARM_SEC
:
285 CmosMemory
.AlarmSecond
= WRITE_CMOS_DATA(CmosMemory
, Data
);
289 case CMOS_REG_MINUTES
:
292 CurrentTime
.wMinute
= WRITE_CMOS_DATA(CmosMemory
, Data
);
296 case CMOS_REG_ALARM_MIN
:
298 CmosMemory
.AlarmMinute
= WRITE_CMOS_DATA(CmosMemory
, Data
);
304 BOOLEAN Afternoon
= FALSE
;
308 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Data
& 0x80))
314 CurrentTime
.wHour
= WRITE_CMOS_DATA(CmosMemory
, Data
);
316 /* Convert to 24-hour format */
317 if (Afternoon
) CurrentTime
.wHour
+= 12;
322 case CMOS_REG_ALARM_HRS
:
324 BOOLEAN Afternoon
= FALSE
;
326 if (!(CmosMemory
.StatusRegB
& CMOS_STB_24HOUR
) && (Data
& 0x80))
332 CmosMemory
.AlarmHour
= WRITE_CMOS_DATA(CmosMemory
, Data
);
334 /* Convert to 24-hour format */
335 if (Afternoon
) CmosMemory
.AlarmHour
+= 12;
340 case CMOS_REG_DAY_OF_WEEK
:
344 * The CMOS value is 1-based but the
345 * SetLocalTime API value is 0-based.
349 CurrentTime
.wDayOfWeek
= WRITE_CMOS_DATA(CmosMemory
, Data
);
356 CurrentTime
.wDay
= WRITE_CMOS_DATA(CmosMemory
, Data
);
363 CurrentTime
.wMonth
= WRITE_CMOS_DATA(CmosMemory
, Data
);
371 /* Clear everything except the century */
372 CurrentTime
.wYear
= (CurrentTime
.wYear
/ 100) * 100;
373 CurrentTime
.wYear
+= WRITE_CMOS_DATA(CmosMemory
, Data
);
377 case CMOS_REG_CENTURY
:
383 case CMOS_REG_STATUS_A
:
385 CmosMemory
.StatusRegA
= Data
& 0x7F; // Bit 7 is read-only
386 RtcUpdatePeriodicTimer();
390 case CMOS_REG_STATUS_B
:
392 CmosMemory
.StatusRegB
= Data
;
396 case CMOS_REG_STATUS_C
:
397 case CMOS_REG_STATUS_D
:
398 // Status registers C and D are read-only
401 /* Is the following correct? */
402 case CMOS_REG_EXT_MEMORY_LOW
:
403 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW
:
405 /* Sync EMS and UMS */
406 CmosMemory
.ExtMemoryLow
=
407 CmosMemory
.ActualExtMemoryLow
= Data
;
411 /* Is the following correct? */
412 case CMOS_REG_EXT_MEMORY_HIGH
:
413 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH
:
415 /* Sync EMS and UMS */
416 CmosMemory
.ExtMemoryHigh
=
417 CmosMemory
.ActualExtMemoryHigh
= Data
;
423 CmosMemory
.Regs
[SelectedRegister
] = Data
;
427 if (ChangeTime
) SetLocalTime(&CurrentTime
);
429 /* Return to Status Register D */
430 SelectedRegister
= CMOS_REG_STATUS_D
;
434 /* PUBLIC FUNCTIONS ***********************************************************/
436 BOOLEAN
IsNmiEnabled(VOID
)
441 VOID
CmosInitialize(VOID
)
443 DWORD CmosSize
= sizeof(CmosMemory
);
445 /* File must not be opened before */
446 ASSERT(hCmosRam
== INVALID_HANDLE_VALUE
);
448 /* Clear the CMOS memory */
449 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
451 /* Always open (and if needed, create) a RAM file with shared access */
452 SetLastError(0); // For debugging purposes
453 hCmosRam
= CreateFileW(L
"cmos.ram",
454 GENERIC_READ
| GENERIC_WRITE
,
455 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
458 FILE_ATTRIBUTE_NORMAL
,
460 DPRINT1("CMOS opening %s (Error: %u)\n", hCmosRam
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
462 if (hCmosRam
!= INVALID_HANDLE_VALUE
)
466 /* Attempt to fill the CMOS memory with the RAM file */
467 SetLastError(0); // For debugging purposes
468 Success
= ReadFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
469 if (CmosSize
!= sizeof(CmosMemory
))
471 /* Bad CMOS RAM file. Reinitialize the CMOS memory. */
472 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize
, sizeof(CmosMemory
));
473 RtlZeroMemory(&CmosMemory
, sizeof(CmosMemory
));
475 DPRINT1("CMOS loading %s (Error: %u)\n", Success
? "succeeded" : "failed", GetLastError());
476 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
479 /* Overwrite some registers with default values */
480 CmosMemory
.StatusRegA
= CMOS_DEFAULT_STA
;
481 CmosMemory
.StatusRegB
= CMOS_DEFAULT_STB
;
482 CmosMemory
.StatusRegC
= 0x00;
483 CmosMemory
.StatusRegD
= CMOS_BATTERY_OK
; // Our CMOS battery works perfectly forever.
484 CmosMemory
.Diagnostics
= 0x00; // Diagnostics must not find any errors.
485 CmosMemory
.ShutdownStatus
= 0x00;
486 CmosMemory
.EquipmentList
= CMOS_EQUIPMENT_LIST
;
488 // HACK: For the moment, set the boot sequence to: 1-Floppy, 2-Hard Disk .
489 CmosMemory
.Regs
[CMOS_REG_SYSOP
] |= (1 << 5);
491 /* Memory settings */
494 * Conventional memory size is 640 kB,
495 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
496 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
497 * for more information.
499 CmosMemory
.BaseMemoryLow
= LOBYTE(0x0280);
500 CmosMemory
.BaseMemoryHigh
= HIBYTE(0x0280);
502 CmosMemory
.ExtMemoryLow
=
503 CmosMemory
.ActualExtMemoryLow
= LOBYTE((MAX_ADDRESS
- 0x100000) / 1024);
504 CmosMemory
.ExtMemoryHigh
=
505 CmosMemory
.ActualExtMemoryHigh
= HIBYTE((MAX_ADDRESS
- 0x100000) / 1024);
507 /* Register the I/O Ports */
508 RegisterIoPort(CMOS_ADDRESS_PORT
, NULL
, CmosWriteAddress
);
509 RegisterIoPort(CMOS_DATA_PORT
, CmosReadData
, CmosWriteData
);
511 ClockTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
,
514 PeriodicTimer
= CreateHardwareTimer(HARDWARE_TIMER_ENABLED
| HARDWARE_TIMER_PRECISE
,
519 VOID
CmosCleanup(VOID
)
521 DWORD CmosSize
= sizeof(CmosMemory
);
523 if (hCmosRam
== INVALID_HANDLE_VALUE
) return;
525 DestroyHardwareTimer(PeriodicTimer
);
526 DestroyHardwareTimer(ClockTimer
);
528 /* Flush the CMOS memory back to the RAM file and close it */
529 SetFilePointer(hCmosRam
, 0, NULL
, FILE_BEGIN
);
530 WriteFile(hCmosRam
, &CmosMemory
, CmosSize
, &CmosSize
, NULL
);
532 CloseHandle(hCmosRam
);
533 hCmosRam
= INVALID_HANDLE_VALUE
;