[NTVDM]: Start to organize the code...
[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 #include "io.h"
16 #include "bios/bios.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_BASE_MEMORY_LOW:
142 return Bda->MemorySize & 0xFF;
143
144 case CMOS_REG_BASE_MEMORY_HIGH:
145 return Bda->MemorySize >> 8;
146
147 case CMOS_REG_EXT_MEMORY_LOW:
148 return ((MAX_ADDRESS - 0x100000) / 1024) & 0xFF;
149
150 case CMOS_REG_EXT_MEMORY_HIGH:
151 return ((MAX_ADDRESS - 0x100000) / 1024) >> 8;
152
153 case CMOS_REG_STATUS_A:
154 case CMOS_REG_STATUS_B:
155 case CMOS_REG_STATUS_D:
156 case CMOS_REG_DIAGNOSTICS:
157 case CMOS_REG_SHUTDOWN_STATUS:
158 default:
159 {
160 // ASSERT(SelectedRegister < CMOS_REG_MAX);
161 return CmosMemory.Regs[SelectedRegister];
162 }
163 }
164
165 /* Return to Status Register D */
166 SelectedRegister = CMOS_REG_STATUS_D;
167 }
168
169 VOID CmosWriteData(BYTE Value)
170 {
171 BOOLEAN ChangeTime = FALSE;
172 SYSTEMTIME CurrentTime;
173
174 /* Get the current time */
175 GetLocalTime(&CurrentTime);
176
177 switch (SelectedRegister)
178 {
179 case CMOS_REG_SECONDS:
180 {
181 ChangeTime = TRUE;
182 CurrentTime.wSecond = WRITE_CMOS_DATA(CmosMemory, Value);
183 break;
184 }
185
186 case CMOS_REG_ALARM_SEC:
187 {
188 CmosMemory.AlarmSecond = WRITE_CMOS_DATA(CmosMemory, Value);
189 break;
190 }
191
192 case CMOS_REG_MINUTES:
193 {
194 ChangeTime = TRUE;
195 CurrentTime.wMinute = WRITE_CMOS_DATA(CmosMemory, Value);
196 break;
197 }
198
199 case CMOS_REG_ALARM_MIN:
200 {
201 CmosMemory.AlarmMinute = WRITE_CMOS_DATA(CmosMemory, Value);
202 break;
203 }
204
205 case CMOS_REG_HOURS:
206 {
207 BOOLEAN Afternoon = FALSE;
208
209 ChangeTime = TRUE;
210
211 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
212 {
213 Value &= ~0x80;
214 Afternoon = TRUE;
215 }
216
217 CurrentTime.wHour = WRITE_CMOS_DATA(CmosMemory, Value);
218
219 /* Convert to 24-hour format */
220 if (Afternoon) CurrentTime.wHour += 12;
221
222 break;
223 }
224
225 case CMOS_REG_ALARM_HRS:
226 {
227 BOOLEAN Afternoon = FALSE;
228
229 if (!(CmosMemory.StatusRegB & CMOS_STB_24HOUR) && (Value & 0x80))
230 {
231 Value &= ~0x80;
232 Afternoon = TRUE;
233 }
234
235 CmosMemory.AlarmHour = WRITE_CMOS_DATA(CmosMemory, Value);
236
237 /* Convert to 24-hour format */
238 if (Afternoon) CmosMemory.AlarmHour += 12;
239
240 break;
241 }
242
243 case CMOS_REG_DAY_OF_WEEK:
244 {
245 ChangeTime = TRUE;
246 /*
247 * The CMOS value is 1-based but the
248 * SetLocalTime API value is 0-based.
249 * Correct it.
250 */
251 Value -= 1;
252 CurrentTime.wDayOfWeek = WRITE_CMOS_DATA(CmosMemory, Value);
253 break;
254 }
255
256 case CMOS_REG_DAY:
257 {
258 ChangeTime = TRUE;
259 CurrentTime.wDay = WRITE_CMOS_DATA(CmosMemory, Value);
260 break;
261 }
262
263 case CMOS_REG_MONTH:
264 {
265 ChangeTime = TRUE;
266 CurrentTime.wMonth = WRITE_CMOS_DATA(CmosMemory, Value);
267 break;
268 }
269
270 case CMOS_REG_YEAR:
271 {
272 ChangeTime = TRUE;
273
274 /* Clear everything except the century */
275 CurrentTime.wYear = (CurrentTime.wYear / 100) * 100;
276
277 CurrentTime.wYear += WRITE_CMOS_DATA(CmosMemory, Value);
278 break;
279 }
280
281 case CMOS_REG_STATUS_A:
282 {
283 CmosMemory.StatusRegA = Value & 0x7F; // Bit 7 is read-only
284 break;
285 }
286
287 case CMOS_REG_STATUS_B:
288 {
289 CmosMemory.StatusRegB = Value;
290 break;
291 }
292
293 case CMOS_REG_STATUS_C:
294 case CMOS_REG_STATUS_D:
295 // Status registers C and D are read-only
296 break;
297
298 default:
299 {
300 CmosMemory.Regs[SelectedRegister] = Value;
301 }
302 }
303
304 if (ChangeTime) SetLocalTime(&CurrentTime);
305
306 /* Return to Status Register D */
307 SelectedRegister = CMOS_REG_STATUS_D;
308 }
309
310 BYTE WINAPI CmosReadPort(ULONG Port)
311 {
312 return CmosReadData();
313 }
314
315 VOID WINAPI CmosWritePort(ULONG Port, BYTE Data)
316 {
317 if (Port == CMOS_ADDRESS_PORT)
318 CmosWriteAddress(Data);
319 else if (Port == CMOS_DATA_PORT)
320 CmosWriteData(Data);
321 }
322
323 DWORD RtcGetTicksPerSecond(VOID)
324 {
325 BYTE RateSelect = CmosMemory.StatusRegB & 0x0F;
326
327 if (RateSelect == 0)
328 {
329 /* No periodic interrupt */
330 return 0;
331 }
332
333 /* 1 and 2 act like 8 and 9 */
334 if (RateSelect <= 2) RateSelect += 7;
335
336 return 1 << (16 - RateSelect);
337 }
338
339 VOID RtcPeriodicTick(VOID)
340 {
341 /* Set PF */
342 CmosMemory.StatusRegC |= CMOS_STC_PF;
343
344 /* Check if there should be an interrupt on a periodic timer tick */
345 if (CmosMemory.StatusRegB & CMOS_STB_INT_PERIODIC)
346 {
347 CmosMemory.StatusRegC |= CMOS_STC_IRQF;
348
349 /* Interrupt! */
350 PicInterruptRequest(RTC_IRQ_NUMBER);
351 }
352 }
353
354 /* Should be called every second */
355 VOID RtcTimeUpdate(VOID)
356 {
357 SYSTEMTIME CurrentTime;
358
359 /* Get the current time */
360 GetLocalTime(&CurrentTime);
361
362 /* Set UF */
363 CmosMemory.StatusRegC |= CMOS_STC_UF;
364
365 /* Check if the time matches the alarm time */
366 if ((CurrentTime.wHour == CmosMemory.AlarmHour ) &&
367 (CurrentTime.wMinute == CmosMemory.AlarmMinute) &&
368 (CurrentTime.wSecond == CmosMemory.AlarmSecond))
369 {
370 /* Set the alarm flag */
371 CmosMemory.StatusRegC |= CMOS_STC_AF;
372
373 /* Set IRQF if there should be an interrupt */
374 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_ALARM) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
375 }
376
377 /* Check if there should be an interrupt on update */
378 if (CmosMemory.StatusRegB & CMOS_STB_INT_ON_UPDATE) CmosMemory.StatusRegC |= CMOS_STC_IRQF;
379
380 if (CmosMemory.StatusRegC & CMOS_STC_IRQF)
381 {
382 /* Interrupt! */
383 PicInterruptRequest(RTC_IRQ_NUMBER);
384 }
385 }
386
387 BOOLEAN CmosInitialize(VOID)
388 {
389 DWORD CmosSize = sizeof(CmosMemory);
390
391 /* File must not be opened before */
392 ASSERT(hCmosRam == INVALID_HANDLE_VALUE);
393
394 /* Clear the CMOS memory */
395 ZeroMemory(&CmosMemory, sizeof(CmosMemory));
396
397 /* Always open (and if needed, create) a RAM file with shared access */
398 SetLastError(0); // For debugging purposes
399 hCmosRam = CreateFileW(L"cmos.ram",
400 GENERIC_READ | GENERIC_WRITE,
401 FILE_SHARE_READ | FILE_SHARE_WRITE,
402 NULL,
403 OPEN_ALWAYS,
404 FILE_ATTRIBUTE_NORMAL,
405 NULL);
406 DPRINT1("CMOS opening %s ; GetLastError() = %u\n", hCmosRam != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
407
408 if (hCmosRam != INVALID_HANDLE_VALUE)
409 {
410 BOOL Success = FALSE;
411
412 /* Attempt to fill the CMOS memory with the RAM file */
413 SetLastError(0); // For debugging purposes
414 Success = ReadFile(hCmosRam, &CmosMemory, CmosSize, &CmosSize, NULL);
415 if (CmosSize != sizeof(CmosMemory))
416 {
417 /* Bad CMOS Ram file. Reinitialize the CMOS memory. */
418 DPRINT1("Invalid CMOS file, read bytes %u, expected bytes %u\n", CmosSize, sizeof(CmosMemory));
419 ZeroMemory(&CmosMemory, sizeof(CmosMemory));
420 }
421 DPRINT1("CMOS loading %s ; GetLastError() = %u\n", Success ? "succeeded" : "failed", GetLastError());
422 SetFilePointer(hCmosRam, 0, NULL, FILE_BEGIN);
423 }
424
425 /* Overwrite some registers with default values */
426 CmosMemory.StatusRegA = CMOS_DEFAULT_STA;
427 CmosMemory.StatusRegB = CMOS_DEFAULT_STB;
428 CmosMemory.StatusRegC = 0x00;
429 CmosMemory.StatusRegD = CMOS_BATTERY_OK; // Our CMOS battery works perfectly forever.
430 CmosMemory.Diagnostics = 0x00; // Diagnostics must not find any errors.
431 CmosMemory.ShutdownStatus = 0x00;
432
433 /* Register the I/O Ports */
434 RegisterIoPort(CMOS_ADDRESS_PORT, NULL , CmosWritePort);
435 RegisterIoPort(CMOS_DATA_PORT , CmosReadPort, CmosWritePort);
436
437 return TRUE;
438 }
439
440 VOID CmosCleanup(VOID)
441 {
442 DWORD CmosSize = sizeof(CmosMemory);
443
444 if (hCmosRam == INVALID_HANDLE_VALUE) return;
445
446 /* Flush the CMOS memory back to the RAM file and close it */
447 SetFilePointer(hCmosRam, 0, NULL, FILE_BEGIN);
448 WriteFile(hCmosRam, &CmosMemory, CmosSize, &CmosSize, NULL);
449
450 CloseHandle(hCmosRam);
451 hCmosRam = INVALID_HANDLE_VALUE;
452 }
453
454 /* EOF */