xref: /reactos/subsystems/mvdm/ntvdm/hardware/cmos.c (revision fe11f7a2)
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