1 /* 2 * COPYRIGHT: GPL - See COPYING in the top level directory 3 * PROJECT: ReactOS Virtual DOS Machine 4 * FILE: subsystems/mvdm/ntvdm/hardware/disk.c 5 * PURPOSE: Generic Disk Controller (Floppy, Hard Disk, ...) 6 * PROGRAMMERS: Hermes Belusca-Maito (hermes.belusca@sfr.fr) 7 * 8 * NOTE 1: This file is meant to be splitted into FDC and HDC 9 * when its code will grow out of control! 10 * 11 * NOTE 2: This is poor-man implementation, i.e. at the moment this file 12 * contains an API for manipulating the disks for the rest of NTVDM, 13 * but does not implement real hardware emulation (IO ports, etc...). 14 * One will have to progressively transform it into a real HW emulation 15 * and, in case the disk APIs are needed, move them elsewhere. 16 * 17 * FIXME: The big endian support (which is hardcoded here for machines 18 * in little endian) *MUST* be fixed! 19 */ 20 21 /* INCLUDES *******************************************************************/ 22 23 #include "ntvdm.h" 24 25 #define NDEBUG 26 #include <debug.h> 27 28 #include "emulator.h" 29 #include "disk.h" 30 31 // #include "io.h" 32 #include "memory.h" 33 34 #include "utils.h" 35 36 37 /**************** HARD DRIVES -- VHD FIXED DISK FORMAT SUPPORT ****************/ 38 39 // http://citrixblogger.org/2008/12/01/dynamic-vhd-walkthrough/ 40 // http://www.microsoft.com/en-us/download/details.aspx?id=23850 41 // https://projects.honeynet.org/svn/sebek/virtualization/qebek/trunk/block/vpc.c 42 // https://git.virtualopensystems.com/trescca/qemu/raw/40645c7bfd7c4d45381927e1e80081fa827c368a/block/vpc.c 43 // https://gitweb.gentoo.org/proj/qemu-kvm.git/tree/block/vpc.c?h=qemu-kvm-0.12.4-gentoo&id=827dccd6740639c64732418539bf17e6e4c99d77 44 45 #pragma pack(push, 1) 46 47 enum VHD_TYPE 48 { 49 VHD_FIXED = 2, 50 VHD_DYNAMIC = 3, 51 VHD_DIFFERENCING = 4, 52 }; 53 54 // Seconds since Jan 1, 2000 0:00:00 (UTC) 55 #define VHD_TIMESTAMP_BASE 946684800 56 57 // Always in BIG-endian format! 58 typedef struct _VHD_FOOTER 59 { 60 CHAR creator[8]; // "conectix" 61 ULONG features; 62 ULONG version; 63 64 // Offset of next header structure, 0xFFFFFFFF if none 65 ULONG64 data_offset; 66 67 // Seconds since Jan 1, 2000 0:00:00 (UTC) 68 ULONG timestamp; 69 70 CHAR creator_app[4]; // "vpc "; "win" 71 USHORT major; 72 USHORT minor; 73 CHAR creator_os[4]; // "Wi2k" 74 75 ULONG64 orig_size; 76 ULONG64 size; 77 78 USHORT cyls; 79 BYTE heads; 80 BYTE secs_per_cyl; 81 82 ULONG type; // VHD_TYPE 83 84 // Checksum of the Hard Disk Footer ("one's complement of the sum of all 85 // the bytes in the footer without the checksum field") 86 ULONG checksum; 87 88 // UUID used to identify a parent hard disk (backing file) 89 BYTE uuid[16]; 90 91 BYTE in_saved_state; 92 93 BYTE Padding[0x200-0x55]; 94 95 } VHD_FOOTER, *PVHD_FOOTER; 96 C_ASSERT(sizeof(VHD_FOOTER) == 0x200); 97 98 #pragma pack(pop) 99 100 #if 0 101 /* 102 * Calculates the number of cylinders, heads and sectors per cylinder 103 * based on a given number of sectors. This is the algorithm described 104 * in the VHD specification. 105 * 106 * Note that the geometry doesn't always exactly match total_sectors but 107 * may round it down. 108 * 109 * Returns TRUE on success, FALSE if the size is larger than 127 GB 110 */ 111 static BOOLEAN 112 calculate_geometry(ULONG64 total_sectors, PUSHORT cyls, 113 PBYTE heads, PBYTE secs_per_cyl) 114 { 115 ULONG cyls_times_heads; 116 117 if (total_sectors > 65535 * 16 * 255) 118 return FALSE; 119 120 if (total_sectors > 65535 * 16 * 63) 121 { 122 *secs_per_cyl = 255; 123 *heads = 16; 124 cyls_times_heads = total_sectors / *secs_per_cyl; 125 } 126 else 127 { 128 *secs_per_cyl = 17; 129 cyls_times_heads = total_sectors / *secs_per_cyl; 130 *heads = (cyls_times_heads + 1023) / 1024; 131 132 if (*heads < 4) 133 *heads = 4; 134 135 if (cyls_times_heads >= (*heads * 1024) || *heads > 16) 136 { 137 *secs_per_cyl = 31; 138 *heads = 16; 139 cyls_times_heads = total_sectors / *secs_per_cyl; 140 } 141 142 if (cyls_times_heads >= (*heads * 1024)) 143 { 144 *secs_per_cyl = 63; 145 *heads = 16; 146 cyls_times_heads = total_sectors / *secs_per_cyl; 147 } 148 } 149 150 *cyls = cyls_times_heads / *heads; 151 152 return TRUE; 153 } 154 #endif 155 156 157 158 /*************************** FLOPPY DISK CONTROLLER ***************************/ 159 160 // A Floppy Controller can support up to 4 floppy drives. 161 static DISK_IMAGE XDCFloppyDrive[4]; 162 163 // Taken from DOSBox 164 typedef struct _DISK_GEO 165 { 166 DWORD ksize; /* Size in kilobytes */ 167 WORD secttrack; /* Sectors per track */ 168 WORD headscyl; /* Heads per cylinder */ 169 WORD cylcount; /* Cylinders per side */ 170 WORD biosval; /* Type to return from BIOS & CMOS */ 171 } DISK_GEO, *PDISK_GEO; 172 173 // FIXME: At the moment, all of our diskettes have 512 bytes per sector... 174 static WORD HackSectorSize = 512; 175 static DISK_GEO DiskGeometryList[] = 176 { 177 { 160, 8, 1, 40, 0}, 178 { 180, 9, 1, 40, 0}, 179 { 200, 10, 1, 40, 0}, 180 { 320, 8, 2, 40, 1}, 181 { 360, 9, 2, 40, 1}, 182 { 400, 10, 2, 40, 1}, 183 { 720, 9, 2, 80, 3}, 184 {1200, 15, 2, 80, 2}, 185 {1440, 18, 2, 80, 4}, 186 {2880, 36, 2, 80, 6}, 187 }; 188 189 BOOLEAN 190 MountFDI(IN PDISK_IMAGE DiskImage, 191 IN HANDLE hFile) 192 { 193 ULONG FileSize; 194 USHORT i; 195 196 /* 197 * Retrieve the size of the file. In NTVDM we will handle files 198 * of maximum 1Mb so we can largely use GetFileSize only. 199 */ 200 FileSize = GetFileSize(hFile, NULL); 201 if (FileSize == INVALID_FILE_SIZE && GetLastError() != ERROR_SUCCESS) 202 { 203 /* We failed, bail out */ 204 DisplayMessage(L"MountFDI: Error when retrieving file size, or size too large (%d).", FileSize); 205 return FALSE; 206 } 207 208 /* Convert the size in kB */ 209 FileSize /= 1024; 210 211 /* Find the floppy format in the list, and mount it if found */ 212 for (i = 0; i < ARRAYSIZE(DiskGeometryList); ++i) 213 { 214 if (DiskGeometryList[i].ksize == FileSize || 215 DiskGeometryList[i].ksize + 1 == FileSize) 216 { 217 /* Found, mount it */ 218 DiskImage->DiskType = DiskGeometryList[i].biosval; 219 DiskImage->DiskInfo.Cylinders = DiskGeometryList[i].cylcount; 220 DiskImage->DiskInfo.Heads = DiskGeometryList[i].headscyl; 221 DiskImage->DiskInfo.Sectors = DiskGeometryList[i].secttrack; 222 DiskImage->DiskInfo.SectorSize = HackSectorSize; 223 224 /* Set the file pointer to the beginning */ 225 SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 226 227 DiskImage->hDisk = hFile; 228 return TRUE; 229 } 230 } 231 232 /* If we are here, we failed to find a suitable format. Bail out. */ 233 DisplayMessage(L"MountFDI: Floppy image of invalid size %d.", FileSize); 234 return FALSE; 235 } 236 237 238 /************************** IDE HARD DISK CONTROLLER **************************/ 239 240 // An IDE Hard Disk Controller can support up to 4 drives: 241 // Primary Master Drive, Primary Slave Drive, 242 // Secondary Master Drive, Secondary Slave Drive. 243 static DISK_IMAGE XDCHardDrive[4]; 244 245 BOOLEAN 246 MountHDD(IN PDISK_IMAGE DiskImage, 247 IN HANDLE hFile) 248 { 249 /**** Support for VHD fixed disks ****/ 250 DWORD FilePointer, BytesToRead; 251 VHD_FOOTER vhd_footer; 252 253 /* Go to the end of the file and retrieve the footer */ 254 FilePointer = SetFilePointer(hFile, -(LONG)sizeof(VHD_FOOTER), NULL, FILE_END); 255 if (FilePointer == INVALID_SET_FILE_POINTER) 256 { 257 DPRINT1("MountHDD: Error when seeking HDD footer, last error = %d\n", GetLastError()); 258 return FALSE; 259 } 260 261 /* Read footer */ 262 // FIXME: We may consider just mapping section to the file... 263 BytesToRead = sizeof(VHD_FOOTER); 264 if (!ReadFile(hFile, &vhd_footer, BytesToRead, &BytesToRead, NULL)) 265 { 266 DPRINT1("MountHDD: Error when reading HDD footer, last error = %d\n", GetLastError()); 267 return FALSE; 268 } 269 270 /* Perform validity checks */ 271 if (RtlCompareMemory(vhd_footer.creator, "conectix", 272 sizeof(vhd_footer.creator)) != sizeof(vhd_footer.creator)) 273 { 274 DisplayMessage(L"MountHDD: Invalid HDD image (expected VHD)."); 275 return FALSE; 276 } 277 if (vhd_footer.version != 0x00000100 && 278 vhd_footer.version != 0x00000500) // FIXME: Big endian! 279 { 280 DisplayMessage(L"MountHDD: VHD HDD image of unexpected version %d.", vhd_footer.version); 281 return FALSE; 282 } 283 if (RtlUlongByteSwap(vhd_footer.type) != VHD_FIXED) // FIXME: Big endian! 284 { 285 DisplayMessage(L"MountHDD: Only VHD HDD fixed images are supported."); 286 return FALSE; 287 } 288 if (vhd_footer.data_offset != 0xFFFFFFFFFFFFFFFF) 289 { 290 DisplayMessage(L"MountHDD: Unexpected data offset for VHD HDD fixed image."); 291 return FALSE; 292 } 293 if (vhd_footer.orig_size != vhd_footer.size) 294 { 295 DisplayMessage(L"MountHDD: VHD HDD fixed image size should be the same as its original size."); 296 return FALSE; 297 } 298 // FIXME: Checksum! 299 300 /* Found, mount it */ 301 DiskImage->DiskType = 0; 302 DiskImage->DiskInfo.Cylinders = RtlUshortByteSwap(vhd_footer.cyls); // FIXME: Big endian! 303 DiskImage->DiskInfo.Heads = vhd_footer.heads; 304 DiskImage->DiskInfo.Sectors = vhd_footer.secs_per_cyl; 305 DiskImage->DiskInfo.SectorSize = RtlUlonglongByteSwap(vhd_footer.size) / // FIXME: Big endian! 306 DiskImage->DiskInfo.Cylinders / 307 DiskImage->DiskInfo.Heads / DiskImage->DiskInfo.Sectors; 308 309 /* Set the file pointer to the beginning */ 310 SetFilePointer(hFile, 0, NULL, FILE_BEGIN); 311 312 DiskImage->hDisk = hFile; 313 return TRUE; 314 } 315 316 317 318 /************************ GENERIC DISK CONTROLLER API *************************/ 319 320 BOOLEAN 321 IsDiskPresent(IN PDISK_IMAGE DiskImage) 322 { 323 ASSERT(DiskImage); 324 return (DiskImage->hDisk != INVALID_HANDLE_VALUE && DiskImage->hDisk != NULL); 325 } 326 327 BYTE 328 SeekDisk(IN PDISK_IMAGE DiskImage, 329 IN WORD Cylinder, 330 IN BYTE Head, 331 IN BYTE Sector) 332 { 333 DWORD FilePointer; 334 335 /* Check that the sector number is 1-based */ 336 // FIXME: Or do it in the caller? 337 if (Sector == 0) 338 { 339 /* Return error */ 340 return 0x01; 341 } 342 343 /* Set position */ 344 // Compute the offset 345 FilePointer = (DWORD)((DWORD)((DWORD)Cylinder * DiskImage->DiskInfo.Heads + Head) 346 * DiskImage->DiskInfo.Sectors + (Sector - 1)) 347 * DiskImage->DiskInfo.SectorSize; 348 FilePointer = SetFilePointer(DiskImage->hDisk, FilePointer, NULL, FILE_BEGIN); 349 if (FilePointer == INVALID_SET_FILE_POINTER) 350 { 351 /* Return error */ 352 return 0x40; 353 } 354 355 return 0x00; 356 } 357 358 BYTE 359 ReadDisk(IN PDISK_IMAGE DiskImage, 360 IN WORD Cylinder, 361 IN BYTE Head, 362 IN BYTE Sector, 363 IN BYTE NumSectors) 364 { 365 BYTE Result; 366 DWORD BytesToRead; 367 368 PVOID LocalBuffer; 369 BYTE StaticBuffer[1024]; 370 371 /* Read the sectors */ 372 Result = SeekDisk(DiskImage, Cylinder, Head, Sector); 373 if (Result != 0x00) 374 return Result; 375 376 BytesToRead = (DWORD)NumSectors * DiskImage->DiskInfo.SectorSize; 377 378 // FIXME: Consider just looping around filling each time the buffer... 379 380 if (BytesToRead <= sizeof(StaticBuffer)) 381 { 382 LocalBuffer = StaticBuffer; 383 } 384 else 385 { 386 LocalBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToRead); 387 ASSERT(LocalBuffer != NULL); 388 } 389 390 if (ReadFile(DiskImage->hDisk, LocalBuffer, BytesToRead, &BytesToRead, NULL)) 391 { 392 /* Write to the memory */ 393 EmulatorWriteMemory(&EmulatorContext, 394 TO_LINEAR(getES(), getBX()), 395 LocalBuffer, 396 BytesToRead); 397 398 Result = 0x00; 399 } 400 else 401 { 402 Result = 0x04; 403 } 404 405 if (LocalBuffer != StaticBuffer) 406 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer); 407 408 /* Return success or error */ 409 return Result; 410 } 411 412 BYTE 413 WriteDisk(IN PDISK_IMAGE DiskImage, 414 IN WORD Cylinder, 415 IN BYTE Head, 416 IN BYTE Sector, 417 IN BYTE NumSectors) 418 { 419 BYTE Result; 420 DWORD BytesToWrite; 421 422 PVOID LocalBuffer; 423 BYTE StaticBuffer[1024]; 424 425 /* Check for write protection */ 426 if (DiskImage->ReadOnly) 427 { 428 /* Return error */ 429 return 0x03; 430 } 431 432 /* Write the sectors */ 433 Result = SeekDisk(DiskImage, Cylinder, Head, Sector); 434 if (Result != 0x00) 435 return Result; 436 437 BytesToWrite = (DWORD)NumSectors * DiskImage->DiskInfo.SectorSize; 438 439 // FIXME: Consider just looping around filling each time the buffer... 440 441 if (BytesToWrite <= sizeof(StaticBuffer)) 442 { 443 LocalBuffer = StaticBuffer; 444 } 445 else 446 { 447 LocalBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToWrite); 448 ASSERT(LocalBuffer != NULL); 449 } 450 451 /* Read from the memory */ 452 EmulatorReadMemory(&EmulatorContext, 453 TO_LINEAR(getES(), getBX()), 454 LocalBuffer, 455 BytesToWrite); 456 457 if (WriteFile(DiskImage->hDisk, LocalBuffer, BytesToWrite, &BytesToWrite, NULL)) 458 Result = 0x00; 459 else 460 Result = 0x04; 461 462 if (LocalBuffer != StaticBuffer) 463 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer); 464 465 /* Return success or error */ 466 return Result; 467 } 468 469 typedef BOOLEAN (*MOUNT_DISK_HANDLER)(IN PDISK_IMAGE DiskImage, IN HANDLE hFile); 470 471 typedef struct _DISK_MOUNT_INFO 472 { 473 PDISK_IMAGE DiskArray; 474 ULONG NumDisks; 475 MOUNT_DISK_HANDLER MountDiskHelper; 476 } DISK_MOUNT_INFO, *PDISK_MOUNT_INFO; 477 478 static DISK_MOUNT_INFO DiskMountInfo[MAX_DISK_TYPE] = 479 { 480 {XDCFloppyDrive, _ARRAYSIZE(XDCFloppyDrive), MountFDI}, 481 {XDCHardDrive , _ARRAYSIZE(XDCHardDrive) , MountHDD}, 482 }; 483 484 PDISK_IMAGE 485 RetrieveDisk(IN DISK_TYPE DiskType, 486 IN ULONG DiskNumber) 487 { 488 ASSERT(DiskType < MAX_DISK_TYPE); 489 490 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks) 491 { 492 DisplayMessage(L"RetrieveDisk: Disk number %d:%d invalid.", DiskType, DiskNumber); 493 return NULL; 494 } 495 496 return &DiskMountInfo[DiskType].DiskArray[DiskNumber]; 497 } 498 499 BOOLEAN 500 MountDisk(IN DISK_TYPE DiskType, 501 IN ULONG DiskNumber, 502 IN PCWSTR FileName, 503 IN BOOLEAN ReadOnly) 504 { 505 BOOLEAN Success = FALSE; 506 PDISK_IMAGE DiskImage; 507 HANDLE hFile; 508 509 BY_HANDLE_FILE_INFORMATION FileInformation; 510 511 ASSERT(DiskType < MAX_DISK_TYPE); 512 513 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks) 514 { 515 DisplayMessage(L"MountDisk: Disk number %d:%d invalid.", DiskType, DiskNumber); 516 return FALSE; 517 } 518 519 DiskImage = &DiskMountInfo[DiskType].DiskArray[DiskNumber]; 520 if (IsDiskPresent(DiskImage)) 521 { 522 DPRINT1("MountDisk: Disk %d:%d:0x%p already in use, recycling...\n", DiskType, DiskNumber, DiskImage); 523 UnmountDisk(DiskType, DiskNumber); 524 } 525 526 /* Try to open the file */ 527 SetLastError(0); // For debugging purposes 528 if (ReadOnly) 529 { 530 hFile = CreateFileW(FileName, 531 GENERIC_READ, 532 FILE_SHARE_READ, 533 NULL, 534 OPEN_EXISTING, 535 FILE_ATTRIBUTE_NORMAL, 536 NULL); 537 } 538 else 539 { 540 hFile = CreateFileW(FileName, 541 GENERIC_READ | GENERIC_WRITE, 542 0, // No sharing access 543 NULL, 544 OPEN_EXISTING, 545 FILE_ATTRIBUTE_NORMAL, 546 NULL); 547 } 548 DPRINT1("File '%S' opening %s ; GetLastError() = %u\n", 549 FileName, hFile != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError()); 550 551 /* If we failed, bail out */ 552 if (hFile == INVALID_HANDLE_VALUE) 553 { 554 DisplayMessage(L"MountDisk: Error when opening disk file '%s' (Error: %u).", FileName, GetLastError()); 555 return FALSE; 556 } 557 558 /* OK, we have a handle to the file */ 559 560 /* 561 * Check that it is really a file, and not a physical drive. 562 * For obvious security reasons, we do not want to be able to 563 * write directly to physical drives. 564 * 565 * Redundant checks 566 */ 567 SetLastError(0); 568 if (!GetFileInformationByHandle(hFile, &FileInformation) && 569 GetLastError() == ERROR_INVALID_FUNCTION) 570 { 571 /* Objects other than real files are not supported */ 572 DisplayMessage(L"MountDisk: '%s' is not a valid disk file.", FileName); 573 goto Quit; 574 } 575 SetLastError(0); 576 if (GetFileSize(hFile, NULL) == INVALID_FILE_SIZE && 577 GetLastError() == ERROR_INVALID_FUNCTION) 578 { 579 /* Objects other than real files are not supported */ 580 DisplayMessage(L"MountDisk: '%s' is not a valid disk file.", FileName); 581 goto Quit; 582 } 583 584 /* Success, mount the image */ 585 if (!DiskMountInfo[DiskType].MountDiskHelper(DiskImage, hFile)) 586 { 587 DisplayMessage(L"MountDisk: Failed to mount disk file '%s' in 0x%p.", FileName, DiskImage); 588 goto Quit; 589 } 590 591 /* Update its read/write state */ 592 DiskImage->ReadOnly = ReadOnly; 593 594 Success = TRUE; 595 596 Quit: 597 if (!Success) FileClose(hFile); 598 return Success; 599 } 600 601 BOOLEAN 602 UnmountDisk(IN DISK_TYPE DiskType, 603 IN ULONG DiskNumber) 604 { 605 PDISK_IMAGE DiskImage; 606 607 ASSERT(DiskType < MAX_DISK_TYPE); 608 609 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks) 610 { 611 DisplayMessage(L"UnmountDisk: Disk number %d:%d invalid.", DiskType, DiskNumber); 612 return FALSE; 613 } 614 615 DiskImage = &DiskMountInfo[DiskType].DiskArray[DiskNumber]; 616 if (!IsDiskPresent(DiskImage)) 617 { 618 DPRINT1("UnmountDisk: Disk %d:%d:0x%p is already unmounted\n", DiskType, DiskNumber, DiskImage); 619 return FALSE; 620 } 621 622 /* Flush the image and unmount it */ 623 FlushFileBuffers(DiskImage->hDisk); 624 FileClose(DiskImage->hDisk); 625 DiskImage->hDisk = NULL; 626 return TRUE; 627 } 628 629 630 /* PUBLIC FUNCTIONS ***********************************************************/ 631 632 BOOLEAN DiskCtrlInitialize(VOID) 633 { 634 return TRUE; 635 } 636 637 VOID DiskCtrlCleanup(VOID) 638 { 639 ULONG DiskNumber; 640 641 /* Unmount all the floppy disk drives */ 642 for (DiskNumber = 0; DiskNumber < DiskMountInfo[FLOPPY_DISK].NumDisks; ++DiskNumber) 643 UnmountDisk(FLOPPY_DISK, DiskNumber); 644 645 /* Unmount all the hard disk drives */ 646 for (DiskNumber = 0; DiskNumber < DiskMountInfo[HARD_DISK].NumDisks; ++DiskNumber) 647 UnmountDisk(HARD_DISK, DiskNumber); 648 } 649 650 /* EOF */ 651