[NTVDM]: Partial revert to r67234: delays were in fact better than frequencies, in...
[reactos.git] / reactos / subsystems / mvdm / ntvdm / hardware / cmos.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: cmos.c
5 * PURPOSE: CMOS Real Time Clock emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "emulator.h"
14 #include "cmos.h"
15
16 #include "io.h"
17 #include "pic.h"
18 #include "clock.h"
19
20 /* PRIVATE VARIABLES **********************************************************/
21
22 static HANDLE hCmosRam = INVALID_HANDLE_VALUE;
23 static CMOS_MEMORY CmosMemory;
24
25 static BOOLEAN NmiEnabled = TRUE;
26 static CMOS_REGISTERS SelectedRegister = CMOS_REG_STATUS_D;
27
28 static PHARDWARE_TIMER ClockTimer;
29 static PHARDWARE_TIMER PeriodicTimer;
30
31 /* PRIVATE FUNCTIONS **********************************************************/
32
33 static VOID RtcUpdatePeriodicTimer(VOID)
34 {
35 BYTE RateSelect = CmosMemory.StatusRegA & 0x0F;
36
37 if (RateSelect == 0)
38 {
39 /* No periodic interrupt */
40 DisableHardwareTimer(PeriodicTimer);
41 return;
42 }
43
44 /* 1 and 2 act like 8 and 9 */
45 if (RateSelect <= 2) RateSelect += 7;
46
47 SetHardwareTimerDelay(PeriodicTimer, HZ_TO_NS(1 << (16 - RateSelect)));
48 EnableHardwareTimer(PeriodicTimer);
49 }
50
51 static VOID FASTCALL RtcPeriodicTick(ULONGLONG ElapsedTime)
52 {
53 UNREFERENCED_PARAMETER(ElapsedTime);
54
55 /* Set PF */
56 CmosMemory.StatusRegC |= CMOS_STC_PF;
57
58 /* Check if there should be an interrupt on a periodic timer tick */
59 if (CmosMemory.StatusRegB & CMOS_STB_INT_PERIODIC)
60 {
61 CmosMemory.StatusRegC |= CMOS_STC_IRQF;
62
63 /* Interrupt! */
64 PicInterruptRequest(RTC_IRQ_NUMBER);
65 }
66 }
67
68 /* Should be called every second */
69 static VOID FASTCALL RtcTimeUpdate(ULONGLONG ElapsedTime)
70 {
71 SYSTEMTIME CurrentTime;
72
73 UNREFERENCED_PARAMETER(ElapsedTime);
74
75 /* Get the current time */
76 GetLocalTime(&CurrentTime);
77
78 /* Set UF */
79 CmosMemory.StatusRegC |= CMOS_STC_UF;
80
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))
85 {
86 /* Set the alarm flag */
87 CmosMemory.StatusRegC |= CMOS_STC_AF;
88
89 /* Set IRQF if there should be an interrupt */
90 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_ALARM) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
91 }
92
93 /* Check if there should be an interrupt on update */
94 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_UPDATE) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
95
96 if (CmosMemory.StatusRegC & CMOS_STC_IRQF)
97 {
98 /* Interrupt! */
99 PicInterruptRequest(RTC_IRQ_NUMBER);
100 }
101 }
102
103 static VOID CmosWriteAddress(BYTE Value)
104 {
105 /* Update the NMI enabled flag */
106 NmiEnabled = !(Value & CMOS_DISABLE_NMI);
107
108 /* Get the register number */
109 Value &= ~CMOS_DISABLE_NMI;
110
111 if (Value < CMOS_REG_MAX)
112 {
113 /* Select the new register */
114 SelectedRegister = Value;
115 }
116 else
117 {
118 /* Default to Status Register D */
119 SelectedRegister = CMOS_REG_STATUS_D;
120 }
121 }
122
123 static BYTE CmosReadData(VOID)
124 {
125 BYTE Value;
126 SYSTEMTIME CurrentTime;
127
128 /* Get the current time */
129 GetLocalTime(&CurrentTime);
130
131 switch (SelectedRegister)
132 {
133 case CMOS_REG_SECONDS:
134 {
135 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wSecond);
136 break;
137 }
138
139 case CMOS_REG_ALARM_SEC:
140 {
141 Value = READ_CMOS_DATA(CmosMemory, CmosMemory.AlarmSecond);
142 break;
143 }
144
145 case CMOS_REG_MINUTES:
146 {
147 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wMinute);
148 break;
149 }
150
151 case CMOS_REG_ALARM_MIN:
152 {
153 Value = READ_CMOS_DATA(CmosMemory, CmosMemory.AlarmMinute);
154 break;
155 }
156
157 case CMOS_REG_HOURS:
158 {
159 BOOLEAN Afternoon = FALSE;
160 Value = CurrentTime.wHour;
161
162 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
163 {
164 Value -= 12;
165 Afternoon = TRUE;
166 }
167
168 Value = READ_CMOS_DATA(CmosMemory, Value);
169
170 /* Convert to 12-hour */
171 if (Afternoon) Value |= 0x80;
172
173 break;
174 }
175
176 case CMOS_REG_ALARM_HRS:
177 {
178 BOOLEAN Afternoon = FALSE;
179 Value = CmosMemory.AlarmHour;
180
181 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
182 {
183 Value -= 12;
184 Afternoon = TRUE;
185 }
186
187 Value = READ_CMOS_DATA(CmosMemory, Value);
188
189 /* Convert to 12-hour */
190 if (Afternoon) Value |= 0x80;
191
192 break;
193 }
194
195 case CMOS_REG_DAY_OF_WEEK:
196 {
197 /*
198 * The CMOS value is 1-based but the
199 * GetLocalTime API value is 0-based.
200 * Correct it.
201 */
202 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wDayOfWeek + 1);
203 break;
204 }
205
206 case CMOS_REG_DAY:
207 {
208 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wDay);
209 break;
210 }
211
212 case CMOS_REG_MONTH:
213 {
214 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wMonth);
215 break;
216 }
217
218 case CMOS_REG_YEAR:
219 {
220 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wYear % 100);
221 break;
222 }
223
224 case CMOS_REG_STATUS_C:
225 {
226 /* Return the old value */
227 Value = CmosMemory.StatusRegC;
228
229 /* Clear status register C */
230 CmosMemory.StatusRegC = 0x00;
231
232 break;
233 }
234
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:
240 default:
241 {
242 // ASSERT(SelectedRegister < CMOS_REG_MAX);
243 Value = CmosMemory.Regs[SelectedRegister];
244 }
245 }
246
247 /* Return to Status Register D */
248 SelectedRegister = CMOS_REG_STATUS_D;
249
250 return Value;
251 }
252
253 static VOID CmosWriteData(BYTE Value)
254 {
255 BOOLEAN ChangeTime = FALSE;
256 SYSTEMTIME CurrentTime;
257
258 /* Get the current time */
259 GetLocalTime(&CurrentTime);
260
261 switch (SelectedRegister)
262 {
263 case CMOS_REG_SECONDS:
264 {
265 ChangeTime = TRUE;
266 CurrentTime.wSecond = WRITE_CMOS_DATA(CmosMemory, Value);
267 break;
268 }
269
270 case CMOS_REG_ALARM_SEC:
271 {
272 CmosMemory.AlarmSecond = WRITE_CMOS_DATA(CmosMemory, Value);
273 break;
274 }
275
276 case CMOS_REG_MINUTES:
277 {
278 ChangeTime = TRUE;
279 CurrentTime.wMinute = WRITE_CMOS_DATA(CmosMemory, Value);
280 break;
281 }
282
283 case CMOS_REG_ALARM_MIN:
284 {
285 CmosMemory.AlarmMinute = WRITE_CMOS_DATA(CmosMemory, Value);
286 break;
287 }
288
289 case CMOS_REG_HOURS:
290 {
291 BOOLEAN Afternoon = FALSE;
292
293 ChangeTime = TRUE;
294
295 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
296 {
297 Value &= ~0x80;
298 Afternoon = TRUE;
299 }
300
301 CurrentTime.wHour = WRITE_CMOS_DATA(CmosMemory, Value);
302
303 /* Convert to 24-hour format */
304 if (Afternoon) CurrentTime.wHour += 12;
305
306 break;
307 }
308
309 case CMOS_REG_ALARM_HRS:
310 {
311 BOOLEAN Afternoon = FALSE;
312
313 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
314 {
315 Value &= ~0x80;
316 Afternoon = TRUE;
317 }
318
319 CmosMemory.AlarmHour = WRITE_CMOS_DATA(CmosMemory, Value);
320
321 /* Convert to 24-hour format */
322 if (Afternoon) CmosMemory.AlarmHour += 12;
323
324 break;
325 }
326
327 case CMOS_REG_DAY_OF_WEEK:
328 {
329 ChangeTime = TRUE;
330 /*
331 * The CMOS value is 1-based but the
332 * SetLocalTime API value is 0-based.
333 * Correct it.
334 */
335 Value -= 1;
336 CurrentTime.wDayOfWeek = WRITE_CMOS_DATA(CmosMemory, Value);
337 break;
338 }
339
340 case CMOS_REG_DAY:
341 {
342 ChangeTime = TRUE;
343 CurrentTime.wDay = WRITE_CMOS_DATA(CmosMemory, Value);
344 break;
345 }
346
347 case CMOS_REG_MONTH:
348 {
349 ChangeTime = TRUE;
350 CurrentTime.wMonth = WRITE_CMOS_DATA(CmosMemory, Value);
351 break;
352 }
353
354 case CMOS_REG_YEAR:
355 {
356 ChangeTime = TRUE;
357
358 /* Clear everything except the century */
359 CurrentTime.wYear = (CurrentTime.wYear / 100) * 100;
360
361 CurrentTime.wYear += WRITE_CMOS_DATA(CmosMemory, Value);
362 break;
363 }
364
365 case CMOS_REG_STATUS_A:
366 {
367 CmosMemory.StatusRegA = Value & 0x7F; // Bit 7 is read-only
368 RtcUpdatePeriodicTimer();
369 break;
370 }
371
372 case CMOS_REG_STATUS_B:
373 {
374 CmosMemory.StatusRegB = Value;
375 break;
376 }
377
378 case CMOS_REG_STATUS_C:
379 case CMOS_REG_STATUS_D:
380 // Status registers C and D are read-only
381 break;
382
383 /* Is the following correct? */
384 case CMOS_REG_EXT_MEMORY_LOW:
385 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW:
386 {
387 /* Sync EMS and UMS */
388 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_LOW] =
389 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_LOW] = Value;
390 break;
391 }
392
393 /* Is the following correct? */
394 case CMOS_REG_EXT_MEMORY_HIGH:
395 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH:
396 {
397 /* Sync EMS and UMS */
398 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_HIGH] =
399 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH] = Value;
400 break;
401 }
402
403 default:
404 {
405 CmosMemory.Regs[SelectedRegister] = Value;
406 }
407 }
408
409 if (ChangeTime) SetLocalTime(&CurrentTime);
410
411 /* Return to Status Register D */
412 SelectedRegister = CMOS_REG_STATUS_D;
413 }
414
415 static BYTE WINAPI CmosReadPort(USHORT Port)
416 {
417 ASSERT(Port == CMOS_DATA_PORT);
418 return CmosReadData();
419 }
420
421 static VOID WINAPI CmosWritePort(USHORT Port, BYTE Data)
422 {
423 if (Port == CMOS_ADDRESS_PORT)
424 CmosWriteAddress(Data);
425 else if (Port == CMOS_DATA_PORT)
426 CmosWriteData(Data);
427 }
428
429
430 /* PUBLIC FUNCTIONS ***********************************************************/
431
432 BOOLEAN IsNmiEnabled(VOID)
433 {
434 return NmiEnabled;
435 }
436
437 VOID CmosInitialize(VOID)
438 {
439 DWORD CmosSize = sizeof(CmosMemory);
440
441 /* File must not be opened before */
442 ASSERT(hCmosRam == INVALID_HANDLE_VALUE);
443
444 /* Clear the CMOS memory */
445 RtlZeroMemory(&CmosMemory, sizeof(CmosMemory));
446
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,
452 NULL,
453 OPEN_ALWAYS,
454 FILE_ATTRIBUTE_NORMAL,
455 NULL);
456 DPRINT1("CMOS opening %s ; GetLastError() = %u\n", hCmosRam != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
457
458 if (hCmosRam != INVALID_HANDLE_VALUE)
459 {
460 BOOL Success;
461
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))
466 {
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));
470 }
471 DPRINT1("CMOS loading %s ; GetLastError() = %u\n", Success ? "succeeded" : "failed", GetLastError());
472 SetFilePointer(hCmosRam, 0, NULL, FILE_BEGIN);
473 }
474
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;
482
483 /* Memory settings */
484
485 /*
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.
490 */
491 CmosMemory.Regs[CMOS_REG_BASE_MEMORY_LOW ] = LOBYTE(0x0280);
492 CmosMemory.Regs[CMOS_REG_BASE_MEMORY_HIGH] = HIBYTE(0x0280);
493
494 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_LOW] =
495 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_LOW] = LOBYTE((MAX_ADDRESS - 0x100000) / 1024);
496
497 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_HIGH] =
498 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH] = HIBYTE((MAX_ADDRESS - 0x100000) / 1024);
499
500 /* Register the I/O Ports */
501 RegisterIoPort(CMOS_ADDRESS_PORT, NULL , CmosWritePort);
502 RegisterIoPort(CMOS_DATA_PORT , CmosReadPort, CmosWritePort);
503
504 ClockTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED,
505 HZ_TO_NS(1),
506 RtcTimeUpdate);
507 PeriodicTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED | HARDWARE_TIMER_PRECISE,
508 HZ_TO_NS(1000),
509 RtcPeriodicTick);
510 }
511
512 VOID CmosCleanup(VOID)
513 {
514 DWORD CmosSize = sizeof(CmosMemory);
515
516 if (hCmosRam == INVALID_HANDLE_VALUE) return;
517
518 DestroyHardwareTimer(PeriodicTimer);
519 DestroyHardwareTimer(ClockTimer);
520
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);
524
525 CloseHandle(hCmosRam);
526 hCmosRam = INVALID_HANDLE_VALUE;
527 }
528
529 /* EOF */