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 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 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 */ 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 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 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 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 438 BOOLEAN IsNmiEnabled(VOID) 439 { 440 return NmiEnabled; 441 } 442 443 static inline BOOL 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 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://webpages.charter.net/danrollins/techhelp/0184.HTM 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 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