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