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 #include "ntvdm.h"
12
13 #define NDEBUG
14 #include <debug.h>
15
16 #include "emulator.h"
17 #include "cmos.h"
18
19 #include "io.h"
20 #include "pic.h"
21 #include "clock.h"
22
23 /* PRIVATE VARIABLES **********************************************************/
24
25 #define CMOS_RAM_FILE "cmos.ram"
26
27 static HANDLE hCmosRam = INVALID_HANDLE_VALUE;
28 static CMOS_MEMORY CmosMemory;
29
30 static BOOLEAN NmiEnabled = TRUE;
31 static CMOS_REGISTERS SelectedRegister = CMOS_REG_STATUS_D;
32
33 static PHARDWARE_TIMER ClockTimer;
34 static PHARDWARE_TIMER PeriodicTimer;
35
36 /* PRIVATE FUNCTIONS **********************************************************/
37
RtcUpdatePeriodicTimer(VOID)38 static VOID RtcUpdatePeriodicTimer(VOID)
39 {
40 BYTE RateSelect = CmosMemory.StatusRegA & 0x0F;
41
42 if (RateSelect == 0)
43 {
44 /* No periodic interrupt */
45 DisableHardwareTimer(PeriodicTimer);
46 return;
47 }
48
49 /* 1 and 2 act like 8 and 9 */
50 if (RateSelect <= 2) RateSelect += 7;
51
52 SetHardwareTimerDelay(PeriodicTimer, HZ_TO_NS(1 << (16 - RateSelect)));
53 // FIXME: This call keeps EnableCount increasing without compensating it!
54 EnableHardwareTimer(PeriodicTimer);
55 }
56
RtcPeriodicTick(ULONGLONG ElapsedTime)57 static VOID FASTCALL RtcPeriodicTick(ULONGLONG ElapsedTime)
58 {
59 UNREFERENCED_PARAMETER(ElapsedTime);
60
61 /* Set PF */
62 CmosMemory.StatusRegC |= CMOS_STC_PF;
63
64 /* Check if there should be an interrupt on a periodic timer tick */
65 if (CmosMemory.StatusRegB & CMOS_STB_INT_PERIODIC)
66 {
67 CmosMemory.StatusRegC |= CMOS_STC_IRQF;
68
69 /* Interrupt! */
70 PicInterruptRequest(RTC_IRQ_NUMBER);
71 }
72 }
73
74 /* Should be called every second */
RtcTimeUpdate(ULONGLONG ElapsedTime)75 static VOID FASTCALL RtcTimeUpdate(ULONGLONG ElapsedTime)
76 {
77 SYSTEMTIME CurrentTime;
78
79 UNREFERENCED_PARAMETER(ElapsedTime);
80
81 /* Get the current time */
82 GetLocalTime(&CurrentTime);
83
84 /* Set UF */
85 CmosMemory.StatusRegC |= CMOS_STC_UF;
86
87 /* Check if the time matches the alarm time */
88 if ((CurrentTime.wHour == CmosMemory.AlarmHour ) &&
89 (CurrentTime.wMinute == CmosMemory.AlarmMinute) &&
90 (CurrentTime.wSecond == CmosMemory.AlarmSecond))
91 {
92 /* Set the alarm flag */
93 CmosMemory.StatusRegC |= CMOS_STC_AF;
94
95 /* Set IRQF if there should be an interrupt */
96 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_ALARM) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
97 }
98
99 /* Check if there should be an interrupt on update */
100 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_UPDATE) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
101
102 if (CmosMemory.StatusRegC & CMOS_STC_IRQF)
103 {
104 /* Interrupt! */
105 PicInterruptRequest(RTC_IRQ_NUMBER);
106 }
107 }
108
CmosWriteAddress(USHORT Port,BYTE Data)109 static VOID WINAPI CmosWriteAddress(USHORT Port, BYTE Data)
110 {
111 UNREFERENCED_PARAMETER(Port);
112
113 /* Update the NMI enabled flag */
114 NmiEnabled = !(Data & CMOS_DISABLE_NMI);
115
116 /* Get the register number */
117 Data &= ~CMOS_DISABLE_NMI;
118
119 if (Data < CMOS_REG_MAX)
120 {
121 /* Select the new register */
122 SelectedRegister = Data;
123 }
124 else
125 {
126 /* Default to Status Register D */
127 SelectedRegister = CMOS_REG_STATUS_D;
128 }
129 }
130
CmosReadData(USHORT Port)131 static BYTE WINAPI CmosReadData(USHORT Port)
132 {
133 BYTE Value;
134 SYSTEMTIME CurrentTime;
135
136 UNREFERENCED_PARAMETER(Port);
137
138 /* Get the current time */
139 GetLocalTime(&CurrentTime);
140
141 switch (SelectedRegister)
142 {
143 case CMOS_REG_SECONDS:
144 {
145 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wSecond);
146 break;
147 }
148
149 case CMOS_REG_ALARM_SEC:
150 {
151 Value = READ_CMOS_DATA(CmosMemory, CmosMemory.AlarmSecond);
152 break;
153 }
154
155 case CMOS_REG_MINUTES:
156 {
157 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wMinute);
158 break;
159 }
160
161 case CMOS_REG_ALARM_MIN:
162 {
163 Value = READ_CMOS_DATA(CmosMemory, CmosMemory.AlarmMinute);
164 break;
165 }
166
167 case CMOS_REG_HOURS:
168 {
169 BOOLEAN Afternoon = FALSE;
170 Value = CurrentTime.wHour;
171
172 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
173 {
174 Value -= 12;
175 Afternoon = TRUE;
176 }
177
178 Value = READ_CMOS_DATA(CmosMemory, Value);
179
180 /* Convert to 12-hour */
181 if (Afternoon) Value |= 0x80;
182
183 break;
184 }
185
186 case CMOS_REG_ALARM_HRS:
187 {
188 BOOLEAN Afternoon = FALSE;
189 Value = CmosMemory.AlarmHour;
190
191 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value >= 12))
192 {
193 Value -= 12;
194 Afternoon = TRUE;
195 }
196
197 Value = READ_CMOS_DATA(CmosMemory, Value);
198
199 /* Convert to 12-hour */
200 if (Afternoon) Value |= 0x80;
201
202 break;
203 }
204
205 case CMOS_REG_DAY_OF_WEEK:
206 {
207 /*
208 * The CMOS value is 1-based but the
209 * GetLocalTime API value is 0-based.
210 * Correct it.
211 */
212 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wDayOfWeek + 1);
213 break;
214 }
215
216 case CMOS_REG_DAY:
217 {
218 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wDay);
219 break;
220 }
221
222 case CMOS_REG_MONTH:
223 {
224 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wMonth);
225 break;
226 }
227
228 case CMOS_REG_YEAR:
229 {
230 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wYear % 100);
231 break;
232 }
233
234 case CMOS_REG_CENTURY:
235 {
236 Value = READ_CMOS_DATA(CmosMemory, CurrentTime.wYear / 100 + 19);
237 break;
238 }
239
240 case CMOS_REG_STATUS_C:
241 {
242 /* Return the old status register value, then clear it */
243 Value = CmosMemory.StatusRegC;
244 CmosMemory.StatusRegC = 0x00;
245 break;
246 }
247
248 case CMOS_REG_STATUS_A:
249 case CMOS_REG_STATUS_B:
250 case CMOS_REG_STATUS_D:
251 case CMOS_REG_DIAGNOSTICS:
252 case CMOS_REG_SHUTDOWN_STATUS:
253 default:
254 {
255 // ASSERT(SelectedRegister < CMOS_REG_MAX);
256 Value = CmosMemory.Regs[SelectedRegister];
257 }
258 }
259
260 /* Return to Status Register D */
261 SelectedRegister = CMOS_REG_STATUS_D;
262
263 return Value;
264 }
265
CmosWriteData(USHORT Port,BYTE Data)266 static VOID WINAPI CmosWriteData(USHORT Port, BYTE Data)
267 {
268 BOOLEAN ChangeTime = FALSE;
269 SYSTEMTIME CurrentTime;
270
271 UNREFERENCED_PARAMETER(Port);
272
273 /* Get the current time */
274 GetLocalTime(&CurrentTime);
275
276 switch (SelectedRegister)
277 {
278 case CMOS_REG_SECONDS:
279 {
280 ChangeTime = TRUE;
281 CurrentTime.wSecond = WRITE_CMOS_DATA(CmosMemory, Data);
282 break;
283 }
284
285 case CMOS_REG_ALARM_SEC:
286 {
287 CmosMemory.AlarmSecond = WRITE_CMOS_DATA(CmosMemory, Data);
288 break;
289 }
290
291 case CMOS_REG_MINUTES:
292 {
293 ChangeTime = TRUE;
294 CurrentTime.wMinute = WRITE_CMOS_DATA(CmosMemory, Data);
295 break;
296 }
297
298 case CMOS_REG_ALARM_MIN:
299 {
300 CmosMemory.AlarmMinute = WRITE_CMOS_DATA(CmosMemory, Data);
301 break;
302 }
303
304 case CMOS_REG_HOURS:
305 {
306 BOOLEAN Afternoon = FALSE;
307
308 ChangeTime = TRUE;
309
310 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Data & 0x80))
311 {
312 Data &= ~0x80;
313 Afternoon = TRUE;
314 }
315
316 CurrentTime.wHour = WRITE_CMOS_DATA(CmosMemory, Data);
317
318 /* Convert to 24-hour format */
319 if (Afternoon) CurrentTime.wHour += 12;
320
321 break;
322 }
323
324 case CMOS_REG_ALARM_HRS:
325 {
326 BOOLEAN Afternoon = FALSE;
327
328 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Data & 0x80))
329 {
330 Data &= ~0x80;
331 Afternoon = TRUE;
332 }
333
334 CmosMemory.AlarmHour = WRITE_CMOS_DATA(CmosMemory, Data);
335
336 /* Convert to 24-hour format */
337 if (Afternoon) CmosMemory.AlarmHour += 12;
338
339 break;
340 }
341
342 case CMOS_REG_DAY_OF_WEEK:
343 {
344 ChangeTime = TRUE;
345 /*
346 * The CMOS value is 1-based but the
347 * SetLocalTime API value is 0-based.
348 * Correct it.
349 */
350 Data -= 1;
351 CurrentTime.wDayOfWeek = WRITE_CMOS_DATA(CmosMemory, Data);
352 break;
353 }
354
355 case CMOS_REG_DAY:
356 {
357 ChangeTime = TRUE;
358 CurrentTime.wDay = WRITE_CMOS_DATA(CmosMemory, Data);
359 break;
360 }
361
362 case CMOS_REG_MONTH:
363 {
364 ChangeTime = TRUE;
365 CurrentTime.wMonth = WRITE_CMOS_DATA(CmosMemory, Data);
366 break;
367 }
368
369 case CMOS_REG_YEAR:
370 {
371 ChangeTime = TRUE;
372
373 /* Clear everything except the century */
374 CurrentTime.wYear = (CurrentTime.wYear / 100) * 100;
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.ExtMemoryLow =
409 CmosMemory.ActualExtMemoryLow = 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.ExtMemoryHigh =
419 CmosMemory.ActualExtMemoryHigh = 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
IsNmiEnabled(VOID)438 BOOLEAN IsNmiEnabled(VOID)
439 {
440 return NmiEnabled;
441 }
442
443 static inline BOOL
CmosWriteFile(_In_ HANDLE FileHandle,_In_ PVOID Buffer,_In_ ULONG BufferSize,_Out_opt_ PULONG BytesWritten)444 CmosWriteFile(
445 _In_ HANDLE FileHandle,
446 _In_ PVOID Buffer,
447 _In_ ULONG BufferSize,
448 _Out_opt_ PULONG BytesWritten)
449 {
450 BOOL Success;
451 ULONG Written;
452
453 SetFilePointer(FileHandle, 0, NULL, FILE_BEGIN);
454 Success = WriteFile(FileHandle, Buffer, BufferSize, &Written, NULL);
455 if (BytesWritten)
456 *BytesWritten = (Success ? Written : 0);
457 return Success;
458 }
459
CmosInitialize(VOID)460 VOID CmosInitialize(VOID)
461 {
462 BOOL Success;
463 WCHAR CmosPath[_countof(NtVdmPath) + _countof("\\" CMOS_RAM_FILE)];
464
465 /* CMOS file must not be opened before */
466 ASSERT(hCmosRam == INVALID_HANDLE_VALUE);
467
468 /* Always open (and if needed, create) a RAM file with shared access */
469 Success = NT_SUCCESS(RtlStringCbPrintfW(CmosPath,
470 sizeof(CmosPath),
471 L"%s\\" L(CMOS_RAM_FILE),
472 NtVdmPath));
473 if (!Success)
474 DPRINT1("Could not create CMOS file path!\n");
475
476 if (Success)
477 {
478 SetLastError(ERROR_SUCCESS);
479 hCmosRam = CreateFileW(CmosPath,
480 GENERIC_READ | GENERIC_WRITE,
481 FILE_SHARE_READ | FILE_SHARE_WRITE,
482 NULL,
483 OPEN_ALWAYS,
484 FILE_ATTRIBUTE_NORMAL,
485 NULL);
486 Success = (hCmosRam != INVALID_HANDLE_VALUE);
487 if (!Success)
488 DPRINT1("CMOS opening failed (Error: %u)\n", GetLastError());
489 }
490
491 /* Clear the CMOS memory */
492 RtlZeroMemory(&CmosMemory, sizeof(CmosMemory));
493
494 /* Load the file only if it already existed and was opened, not newly created */
495 if (Success)
496 {
497 if ((GetLastError() == ERROR_ALREADY_EXISTS) /* || (GetLastError() == ERROR_FILE_EXISTS) */)
498 {
499 /* Attempt to load the CMOS memory from the RAM file */
500 DWORD CmosSize = sizeof(CmosMemory);
501 Success = ReadFile(hCmosRam, &CmosMemory, CmosSize, &CmosSize, NULL);
502 if (!Success)
503 {
504 DPRINT1("CMOS loading failed (Error: %u)\n", GetLastError());
505 }
506 else if (CmosSize != sizeof(CmosMemory))
507 {
508 /* Invalid CMOS RAM file; reinitialize the CMOS memory */
509 DPRINT1("Invalid CMOS file, read %u bytes, expected %u bytes\n",
510 CmosSize, sizeof(CmosMemory));
511 Success = FALSE;
512 }
513 if (!Success)
514 {
515 /* Reset the CMOS memory and its RAM file */
516 RtlZeroMemory(&CmosMemory, sizeof(CmosMemory));
517 CmosWriteFile(hCmosRam, &CmosMemory, sizeof(CmosMemory), NULL);
518 }
519 }
520 else
521 {
522 /* Reset the CMOS RAM file */
523 CmosWriteFile(hCmosRam, &CmosMemory, sizeof(CmosMemory), NULL);
524 }
525 SetFilePointer(hCmosRam, 0, NULL, FILE_BEGIN);
526 }
527
528 /* Overwrite some registers with default values */
529 CmosMemory.StatusRegA = CMOS_DEFAULT_STA;
530 CmosMemory.StatusRegB = CMOS_DEFAULT_STB;
531 CmosMemory.StatusRegC = 0x00;
532 CmosMemory.StatusRegD = CMOS_BATTERY_OK; // Our CMOS battery works perfectly forever.
533 CmosMemory.Diagnostics = 0x00; // Diagnostics must not find any errors.
534 CmosMemory.ShutdownStatus = 0x00;
535 CmosMemory.EquipmentList = CMOS_EQUIPMENT_LIST;
536
537 // HACK: For the moment, set the boot sequence to: 1-Floppy, 2-Hard Disk .
538 CmosMemory.Regs[CMOS_REG_SYSOP] |= (1 << 5);
539
540 /* Memory settings */
541
542 /*
543 * Conventional memory size is 640 kB,
544 * see: http://www.techhelpmanual.com/184-int_12h__conventional_memory_size.html
545 * and see Ralf Brown: http://www.ctyme.com/intr/rb-0598.htm
546 * for more information.
547 */
548 CmosMemory.BaseMemoryLow = LOBYTE(0x0280);
549 CmosMemory.BaseMemoryHigh = HIBYTE(0x0280);
550
551 CmosMemory.ExtMemoryLow =
552 CmosMemory.ActualExtMemoryLow = LOBYTE((MAX_ADDRESS - 0x100000) / 1024);
553 CmosMemory.ExtMemoryHigh =
554 CmosMemory.ActualExtMemoryHigh = HIBYTE((MAX_ADDRESS - 0x100000) / 1024);
555
556 /* Register the I/O Ports */
557 RegisterIoPort(CMOS_ADDRESS_PORT, NULL, CmosWriteAddress);
558 RegisterIoPort(CMOS_DATA_PORT , CmosReadData, CmosWriteData );
559
560 ClockTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED,
561 HZ_TO_NS(1),
562 RtcTimeUpdate);
563 PeriodicTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED | HARDWARE_TIMER_PRECISE,
564 HZ_TO_NS(1000),
565 RtcPeriodicTick);
566 }
567
CmosCleanup(VOID)568 VOID CmosCleanup(VOID)
569 {
570 DestroyHardwareTimer(PeriodicTimer);
571 DestroyHardwareTimer(ClockTimer);
572
573 if (hCmosRam != INVALID_HANDLE_VALUE)
574 {
575 /* Flush the CMOS memory back to the RAM file and close it */
576 BOOL Success;
577 DWORD CmosSize = sizeof(CmosMemory);
578
579 Success = CmosWriteFile(hCmosRam, &CmosMemory, CmosSize, &CmosSize);
580 if (!Success || (CmosSize != sizeof(CmosMemory)))
581 {
582 DPRINT1("CMOS saving failed (Error: %u), written %u bytes, expected %u bytes\n",
583 GetLastError(), CmosSize, sizeof(CmosMemory));
584 }
585
586 CloseHandle(hCmosRam);
587 hCmosRam = INVALID_HANDLE_VALUE;
588 }
589 }
590
591 /* EOF */
592