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