390c1d26b75e5dca240d599a3f4cd06331fd4085
[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 "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 EnableHardwareTimer(PeriodicTimer);
50 }
51
52 static VOID FASTCALL RtcPeriodicTick(ULONGLONG ElapsedTime)
53 {
54 UNREFERENCED_PARAMETER(ElapsedTime);
55
56 /* Set PF */
57 CmosMemory.StatusRegC |= CMOS_STC_PF;
58
59 /* Check if there should be an interrupt on a periodic timer tick */
60 if (CmosMemory.StatusRegB & CMOS_STB_INT_PERIODIC)
61 {
62 CmosMemory.StatusRegC |= CMOS_STC_IRQF;
63
64 /* Interrupt! */
65 PicInterruptRequest(RTC_IRQ_NUMBER);
66 }
67 }
68
69 /* Should be called every second */
70 static VOID FASTCALL RtcTimeUpdate(ULONGLONG ElapsedTime)
71 {
72 SYSTEMTIME CurrentTime;
73
74 UNREFERENCED_PARAMETER(ElapsedTime);
75
76 /* Get the current time */
77 GetLocalTime(&CurrentTime);
78
79 /* Set UF */
80 CmosMemory.StatusRegC |= CMOS_STC_UF;
81
82 /* Check if the time matches the alarm time */
83 if ((CurrentTime.wHour == CmosMemory.AlarmHour ) &&
84 (CurrentTime.wMinute == CmosMemory.AlarmMinute) &&
85 (CurrentTime.wSecond == CmosMemory.AlarmSecond))
86 {
87 /* Set the alarm flag */
88 CmosMemory.StatusRegC |= CMOS_STC_AF;
89
90 /* Set IRQF if there should be an interrupt */
91 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_ALARM) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
92 }
93
94 /* Check if there should be an interrupt on update */
95 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_UPDATE) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
96
97 if (CmosMemory.StatusRegC & CMOS_STC_IRQF)
98 {
99 /* Interrupt! */
100 PicInterruptRequest(RTC_IRQ_NUMBER);
101 }
102 }
103
104 static VOID CmosWriteAddress(BYTE Value)
105 {
106 /* Update the NMI enabled flag */
107 NmiEnabled = !(Value & CMOS_DISABLE_NMI);
108
109 /* Get the register number */
110 Value &= ~CMOS_DISABLE_NMI;
111
112 if (Value < CMOS_REG_MAX)
113 {
114 /* Select the new register */
115 SelectedRegister = Value;
116 }
117 else
118 {
119 /* Default to Status Register D */
120 SelectedRegister = CMOS_REG_STATUS_D;
121 }
122 }
123
124 static BYTE CmosReadData(VOID)
125 {
126 BYTE Value;
127 SYSTEMTIME CurrentTime;
128
129 /* Get the current time */
130 GetLocalTime(&CurrentTime);
131
132 switch (SelectedRegister)
133 {
134 case CMOS_REG_SECONDS:
135 {
136 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wSecond);
137 break;
138 }
139
140 case CMOS_REG_ALARM_SEC:
141 {
142 Value = READ_CMOS_DATA(CmosMemory, CmosMemory.AlarmSecond);
143 break;
144 }
145
146 case CMOS_REG_MINUTES:
147 {
148 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wMinute);
149 break;
150 }
151
152 case CMOS_REG_ALARM_MIN:
153 {
154 Value = READ_CMOS_DATA(CmosMemory, CmosMemory.AlarmMinute);
155 break;
156 }
157
158 case CMOS_REG_HOURS:
159 {
160 BOOLEAN Afternoon = FALSE;
161 Value = CurrentTime.wHour;
162
163 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
164 {
165 Value -= 12;
166 Afternoon = TRUE;
167 }
168
169 Value = READ_CMOS_DATA(CmosMemory, Value);
170
171 /* Convert to 12-hour */
172 if (Afternoon) Value |= 0x80;
173
174 break;
175 }
176
177 case CMOS_REG_ALARM_HRS:
178 {
179 BOOLEAN Afternoon = FALSE;
180 Value = CmosMemory.AlarmHour;
181
182 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
183 {
184 Value -= 12;
185 Afternoon = TRUE;
186 }
187
188 Value = READ_CMOS_DATA(CmosMemory, Value);
189
190 /* Convert to 12-hour */
191 if (Afternoon) Value |= 0x80;
192
193 break;
194 }
195
196 case CMOS_REG_DAY_OF_WEEK:
197 {
198 /*
199 * The CMOS value is 1-based but the
200 * GetLocalTime API value is 0-based.
201 * Correct it.
202 */
203 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wDayOfWeek + 1);
204 break;
205 }
206
207 case CMOS_REG_DAY:
208 {
209 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wDay);
210 break;
211 }
212
213 case CMOS_REG_MONTH:
214 {
215 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wMonth);
216 break;
217 }
218
219 case CMOS_REG_YEAR:
220 {
221 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wYear % 100);
222 break;
223 }
224
225 case CMOS_REG_STATUS_C:
226 {
227 /* Return the old value */
228 Value = CmosMemory.StatusRegC;
229
230 /* Clear status register C */
231 CmosMemory.StatusRegC = 0x00;
232
233 break;
234 }
235
236 case CMOS_REG_STATUS_A:
237 case CMOS_REG_STATUS_B:
238 case CMOS_REG_STATUS_D:
239 case CMOS_REG_DIAGNOSTICS:
240 case CMOS_REG_SHUTDOWN_STATUS:
241 default:
242 {
243 // ASSERT(SelectedRegister < CMOS_REG_MAX);
244 Value = CmosMemory.Regs[SelectedRegister];
245 }
246 }
247
248 /* Return to Status Register D */
249 SelectedRegister = CMOS_REG_STATUS_D;
250
251 return Value;
252 }
253
254 static VOID CmosWriteData(BYTE Value)
255 {
256 BOOLEAN ChangeTime = FALSE;
257 SYSTEMTIME CurrentTime;
258
259 /* Get the current time */
260 GetLocalTime(&CurrentTime);
261
262 switch (SelectedRegister)
263 {
264 case CMOS_REG_SECONDS:
265 {
266 ChangeTime = TRUE;
267 CurrentTime.wSecond = WRITE_CMOS_DATA(CmosMemory, Value);
268 break;
269 }
270
271 case CMOS_REG_ALARM_SEC:
272 {
273 CmosMemory.AlarmSecond = WRITE_CMOS_DATA(CmosMemory, Value);
274 break;
275 }
276
277 case CMOS_REG_MINUTES:
278 {
279 ChangeTime = TRUE;
280 CurrentTime.wMinute = WRITE_CMOS_DATA(CmosMemory, Value);
281 break;
282 }
283
284 case CMOS_REG_ALARM_MIN:
285 {
286 CmosMemory.AlarmMinute = WRITE_CMOS_DATA(CmosMemory, Value);
287 break;
288 }
289
290 case CMOS_REG_HOURS:
291 {
292 BOOLEAN Afternoon = FALSE;
293
294 ChangeTime = TRUE;
295
296 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
297 {
298 Value &= ~0x80;
299 Afternoon = TRUE;
300 }
301
302 CurrentTime.wHour = WRITE_CMOS_DATA(CmosMemory, Value);
303
304 /* Convert to 24-hour format */
305 if (Afternoon) CurrentTime.wHour += 12;
306
307 break;
308 }
309
310 case CMOS_REG_ALARM_HRS:
311 {
312 BOOLEAN Afternoon = FALSE;
313
314 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
315 {
316 Value &= ~0x80;
317 Afternoon = TRUE;
318 }
319
320 CmosMemory.AlarmHour = WRITE_CMOS_DATA(CmosMemory, Value);
321
322 /* Convert to 24-hour format */
323 if (Afternoon) CmosMemory.AlarmHour += 12;
324
325 break;
326 }
327
328 case CMOS_REG_DAY_OF_WEEK:
329 {
330 ChangeTime = TRUE;
331 /*
332 * The CMOS value is 1-based but the
333 * SetLocalTime API value is 0-based.
334 * Correct it.
335 */
336 Value -= 1;
337 CurrentTime.wDayOfWeek = WRITE_CMOS_DATA(CmosMemory, Value);
338 break;
339 }
340
341 case CMOS_REG_DAY:
342 {
343 ChangeTime = TRUE;
344 CurrentTime.wDay = WRITE_CMOS_DATA(CmosMemory, Value);
345 break;
346 }
347
348 case CMOS_REG_MONTH:
349 {
350 ChangeTime = TRUE;
351 CurrentTime.wMonth = WRITE_CMOS_DATA(CmosMemory, Value);
352 break;
353 }
354
355 case CMOS_REG_YEAR:
356 {
357 ChangeTime = TRUE;
358
359 /* Clear everything except the century */
360 CurrentTime.wYear = (CurrentTime.wYear / 100) * 100;
361
362 CurrentTime.wYear += WRITE_CMOS_DATA(CmosMemory, Value);
363 break;
364 }
365
366 case CMOS_REG_STATUS_A:
367 {
368 CmosMemory.StatusRegA = Value & 0x7F; // Bit 7 is read-only
369 RtcUpdatePeriodicTimer();
370 break;
371 }
372
373 case CMOS_REG_STATUS_B:
374 {
375 CmosMemory.StatusRegB = Value;
376 break;
377 }
378
379 case CMOS_REG_STATUS_C:
380 case CMOS_REG_STATUS_D:
381 // Status registers C and D are read-only
382 break;
383
384 /* Is the following correct? */
385 case CMOS_REG_EXT_MEMORY_LOW:
386 case CMOS_REG_ACTUAL_EXT_MEMORY_LOW:
387 {
388 /* Sync EMS and UMS */
389 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_LOW] =
390 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_LOW] = Value;
391 break;
392 }
393
394 /* Is the following correct? */
395 case CMOS_REG_EXT_MEMORY_HIGH:
396 case CMOS_REG_ACTUAL_EXT_MEMORY_HIGH:
397 {
398 /* Sync EMS and UMS */
399 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_HIGH] =
400 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH] = Value;
401 break;
402 }
403
404 default:
405 {
406 CmosMemory.Regs[SelectedRegister] = Value;
407 }
408 }
409
410 if (ChangeTime) SetLocalTime(&CurrentTime);
411
412 /* Return to Status Register D */
413 SelectedRegister = CMOS_REG_STATUS_D;
414 }
415
416 static BYTE WINAPI CmosReadPort(USHORT Port)
417 {
418 ASSERT(Port == CMOS_DATA_PORT);
419 return CmosReadData();
420 }
421
422 static VOID WINAPI CmosWritePort(USHORT Port, BYTE Data)
423 {
424 if (Port == CMOS_ADDRESS_PORT)
425 CmosWriteAddress(Data);
426 else if (Port == CMOS_DATA_PORT)
427 CmosWriteData(Data);
428 }
429
430
431 /* PUBLIC FUNCTIONS ***********************************************************/
432
433 BOOLEAN IsNmiEnabled(VOID)
434 {
435 return NmiEnabled;
436 }
437
438 VOID CmosInitialize(VOID)
439 {
440 DWORD CmosSize = sizeof(CmosMemory);
441
442 /* File must not be opened before */
443 ASSERT(hCmosRam == INVALID_HANDLE_VALUE);
444
445 /* Clear the CMOS memory */
446 RtlZeroMemory(&CmosMemory, sizeof(CmosMemory));
447
448 /* Always open (and if needed, create) a RAM file with shared access */
449 SetLastError(0); // For debugging purposes
450 hCmosRam = CreateFileW(L"cmos.ram",
451 GENERIC_READ | GENERIC_WRITE,
452 FILE_SHARE_READ | FILE_SHARE_WRITE,
453 NULL,
454 OPEN_ALWAYS,
455 FILE_ATTRIBUTE_NORMAL,
456 NULL);
457 DPRINT1("CMOS opening %s (Error: %u)\n", hCmosRam != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
458
459 if (hCmosRam != INVALID_HANDLE_VALUE)
460 {
461 BOOL Success;
462
463 /* Attempt to fill the CMOS memory with the RAM file */
464 SetLastError(0); // For debugging purposes
465 Success = ReadFile(hCmosRam, &CmosMemory, CmosSize, &CmosSize, NULL);
466 if (CmosSize != sizeof(CmosMemory))
467 {
468 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
469 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize, sizeof(CmosMemory));
470 RtlZeroMemory(&CmosMemory, sizeof(CmosMemory));
471 }
472 DPRINT1("CMOS loading %s (Error: %u)\n", Success ? "succeeded" : "failed", GetLastError());
473 SetFilePointer(hCmosRam, 0, NULL, FILE_BEGIN);
474 }
475
476 /* Overwrite some registers with default values */
477 CmosMemory.StatusRegA = CMOS_DEFAULT_STA;
478 CmosMemory.StatusRegB = CMOS_DEFAULT_STB;
479 CmosMemory.StatusRegC = 0x00;
480 CmosMemory.StatusRegD = CMOS_BATTERY_OK; // Our CMOS battery works perfectly forever.
481 CmosMemory.Diagnostics = 0x00; // Diagnostics must not find any errors.
482 CmosMemory.ShutdownStatus = 0x00;
483
484 /* Memory settings */
485
486 /*
487 * Conventional memory size is 640 kB,
488 * see: http://webpages.charter.net/danrollins/techhelp/0184.HTM
489 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
490 * for more information.
491 */
492 CmosMemory.Regs[CMOS_REG_BASE_MEMORY_LOW ] = LOBYTE(0x0280);
493 CmosMemory.Regs[CMOS_REG_BASE_MEMORY_HIGH] = HIBYTE(0x0280);
494
495 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_LOW] =
496 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_LOW] = LOBYTE((MAX_ADDRESS - 0x100000) / 1024);
497
498 CmosMemory.Regs[CMOS_REG_EXT_MEMORY_HIGH] =
499 CmosMemory.Regs[CMOS_REG_ACTUAL_EXT_MEMORY_HIGH] = HIBYTE((MAX_ADDRESS - 0x100000) / 1024);
500
501 /* Register the I/O Ports */
502 RegisterIoPort(CMOS_ADDRESS_PORT, NULL , CmosWritePort);
503 RegisterIoPort(CMOS_DATA_PORT , CmosReadPort, CmosWritePort);
504
505 ClockTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED,
506 HZ_TO_NS(1),
507 RtcTimeUpdate);
508 PeriodicTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED | HARDWARE_TIMER_PRECISE,
509 HZ_TO_NS(1000),
510 RtcPeriodicTick);
511 }
512
513 VOID CmosCleanup(VOID)
514 {
515 DWORD CmosSize = sizeof(CmosMemory);
516
517 if (hCmosRam == INVALID_HANDLE_VALUE) return;
518
519 DestroyHardwareTimer(PeriodicTimer);
520 DestroyHardwareTimer(ClockTimer);
521
522 /* Flush the CMOS memory back to the RAM file and close it */
523 SetFilePointer(hCmosRam, 0, NULL, FILE_BEGIN);
524 WriteFile(hCmosRam, &CmosMemory, CmosSize, &CmosSize, NULL);
525
526 CloseHandle(hCmosRam);
527 hCmosRam = INVALID_HANDLE_VALUE;
528 }
529
530 /* EOF */