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