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)
8 * NOTE 1: This file is meant to be splitted into FDC and HDC
9 * when its code will grow out of control!
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.
17 * FIXME: The big endian support (which is hardcoded here for machines
18 * in little endian) *MUST* be fixed!
21 /* INCLUDES *******************************************************************/
35 /**************** HARD DRIVES -- VHD FIXED DISK FORMAT SUPPORT ****************/
37 // http://citrixblogger.org/2008/12/01/dynamic-vhd-walkthrough/
38 // http://www.microsoft.com/en-us/download/details.aspx?id=23850
39 // https://projects.honeynet.org/svn/sebek/virtualization/qebek/trunk/block/vpc.c
40 // https://git.virtualopensystems.com/trescca/qemu/raw/40645c7bfd7c4d45381927e1e80081fa827c368a/block/vpc.c
41 // https://gitweb.gentoo.org/proj/qemu-kvm.git/tree/block/vpc.c?h=qemu-kvm-0.12.4-gentoo&id=827dccd6740639c64732418539bf17e6e4c99d77
52 // Seconds since Jan 1, 2000 0:00:00 (UTC)
53 #define VHD_TIMESTAMP_BASE 946684800
55 // Always in BIG-endian format!
56 typedef struct _VHD_FOOTER
58 CHAR creator
[8]; // "conectix"
62 // Offset of next header structure, 0xFFFFFFFF if none
65 // Seconds since Jan 1, 2000 0:00:00 (UTC)
68 CHAR creator_app
[4]; // "vpc "; "win"
71 CHAR creator_os
[4]; // "Wi2k"
80 ULONG type
; // VHD_TYPE
82 // Checksum of the Hard Disk Footer ("one's complement of the sum of all
83 // the bytes in the footer without the checksum field")
86 // UUID used to identify a parent hard disk (backing file)
91 BYTE Padding
[0x200-0x55];
93 } VHD_FOOTER
, *PVHD_FOOTER
;
94 C_ASSERT(sizeof(VHD_FOOTER
) == 0x200);
100 * Calculates the number of cylinders, heads and sectors per cylinder
101 * based on a given number of sectors. This is the algorithm described
102 * in the VHD specification.
104 * Note that the geometry doesn't always exactly match total_sectors but
107 * Returns TRUE on success, FALSE if the size is larger than 127 GB
110 calculate_geometry(ULONG64 total_sectors
, PUSHORT cyls
,
111 PBYTE heads
, PBYTE secs_per_cyl
)
113 ULONG cyls_times_heads
;
115 if (total_sectors
> 65535 * 16 * 255)
118 if (total_sectors
> 65535 * 16 * 63)
122 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
127 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
128 *heads
= (cyls_times_heads
+ 1023) / 1024;
133 if (cyls_times_heads
>= (*heads
* 1024) || *heads
> 16)
137 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
140 if (cyls_times_heads
>= (*heads
* 1024))
144 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
148 *cyls
= cyls_times_heads
/ *heads
;
156 /*************************** FLOPPY DISK CONTROLLER ***************************/
158 // A Floppy Controller can support up to 4 floppy drives.
159 static DISK_IMAGE XDCFloppyDrive
[4];
162 typedef struct _DISK_GEO
164 DWORD ksize
; /* Size in kilobytes */
165 WORD secttrack
; /* Sectors per track */
166 WORD headscyl
; /* Heads per cylinder */
167 WORD cylcount
; /* Cylinders per side */
168 WORD biosval
; /* Type to return from BIOS & CMOS */
169 } DISK_GEO
, *PDISK_GEO
;
171 // FIXME: At the moment, all of our diskettes have 512 bytes per sector...
172 static WORD HackSectorSize
= 512;
173 static DISK_GEO DiskGeometryList
[] =
177 { 200, 10, 1, 40, 0},
180 { 400, 10, 2, 40, 1},
182 {1200, 15, 2, 80, 2},
183 {1440, 18, 2, 80, 4},
184 {2880, 36, 2, 80, 6},
188 MountFDI(IN PDISK_IMAGE DiskImage
,
195 * Retrieve the size of the file. In NTVDM we will handle files
196 * of maximum 1Mb so we can largely use GetFileSize only.
198 FileSize
= GetFileSize(hFile
, NULL
);
199 if (FileSize
== INVALID_FILE_SIZE
&& GetLastError() != ERROR_SUCCESS
)
201 /* We failed, bail out */
202 DisplayMessage(L
"Error when retrieving file size, or size too large (%d).", FileSize
);
206 /* Convert the size in kB */
209 /* Find the floppy format in the list, and mount it if found */
210 for (i
= 0; i
< ARRAYSIZE(DiskGeometryList
); ++i
)
212 if (DiskGeometryList
[i
].ksize
== FileSize
||
213 DiskGeometryList
[i
].ksize
+ 1 == FileSize
)
215 /* Found, mount it */
216 DiskImage
->DiskType
= DiskGeometryList
[i
].biosval
;
217 DiskImage
->DiskInfo
.Cylinders
= DiskGeometryList
[i
].cylcount
;
218 DiskImage
->DiskInfo
.Heads
= DiskGeometryList
[i
].headscyl
;
219 DiskImage
->DiskInfo
.Sectors
= DiskGeometryList
[i
].secttrack
;
220 DiskImage
->DiskInfo
.SectorSize
= HackSectorSize
;
222 /* Set the file pointer to the beginning */
223 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
225 DiskImage
->hDisk
= hFile
;
230 /* If we are here, we failed to find a suitable format. Bail out. */
231 DisplayMessage(L
"MountFDI: Floppy image of invalid size %d.", FileSize
);
236 /************************** IDE HARD DISK CONTROLLER **************************/
238 // An IDE Hard Disk Controller can support up to 4 drives:
239 // Primary Master Drive, Primary Slave Drive,
240 // Secondary Master Drive, Secondary Slave Drive.
241 static DISK_IMAGE XDCHardDrive
[4];
244 MountHDD(IN PDISK_IMAGE DiskImage
,
247 /**** Support for VHD fixed disks ****/
248 DWORD FilePointer
, BytesToRead
;
249 VHD_FOOTER vhd_footer
;
251 /* Go to the end of the file and retrieve the footer */
252 FilePointer
= SetFilePointer(hFile
, -(LONG
)sizeof(VHD_FOOTER
), NULL
, FILE_END
);
253 if (FilePointer
== INVALID_SET_FILE_POINTER
)
255 DPRINT1("MountHDD: Error when seeking HDD footer, last error = %d\n", GetLastError());
260 // FIXME: We may consider just mapping section to the file...
261 BytesToRead
= sizeof(VHD_FOOTER
);
262 if (!ReadFile(hFile
, &vhd_footer
, BytesToRead
, &BytesToRead
, NULL
))
264 DPRINT1("MountHDD: Error when reading HDD footer, last error = %d\n", GetLastError());
268 /* Perform validity checks */
269 if (RtlCompareMemory(vhd_footer
.creator
, "conectix",
270 sizeof(vhd_footer
.creator
)) != sizeof(vhd_footer
.creator
))
272 DisplayMessage(L
"MountHDD: Invalid HDD image (expected VHD).");
275 if (vhd_footer
.version
!= 0x00000100 &&
276 vhd_footer
.version
!= 0x00000500) // FIXME: Big endian!
278 DisplayMessage(L
"MountHDD: VHD HDD image of unexpected version %d.", vhd_footer
.version
);
281 if (RtlUlongByteSwap(vhd_footer
.type
) != VHD_FIXED
) // FIXME: Big endian!
283 DisplayMessage(L
"MountHDD: Only VHD HDD fixed images are supported.");
286 if (vhd_footer
.data_offset
!= 0xFFFFFFFFFFFFFFFF)
288 DisplayMessage(L
"MountHDD: Unexpected data offset for VHD HDD fixed image.");
291 if (vhd_footer
.orig_size
!= vhd_footer
.size
)
293 DisplayMessage(L
"MountHDD: VHD HDD fixed image size should be the same as its original size.");
298 /* Found, mount it */
299 DiskImage
->DiskType
= 0;
300 DiskImage
->DiskInfo
.Cylinders
= RtlUshortByteSwap(vhd_footer
.cyls
); // FIXME: Big endian!
301 DiskImage
->DiskInfo
.Heads
= vhd_footer
.heads
;
302 DiskImage
->DiskInfo
.Sectors
= vhd_footer
.secs_per_cyl
;
303 DiskImage
->DiskInfo
.SectorSize
= RtlUlonglongByteSwap(vhd_footer
.size
) / // FIXME: Big endian!
304 DiskImage
->DiskInfo
.Cylinders
/
305 DiskImage
->DiskInfo
.Heads
/ DiskImage
->DiskInfo
.Sectors
;
307 /* Set the file pointer to the beginning */
308 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
310 DiskImage
->hDisk
= hFile
;
316 /************************ GENERIC DISK CONTROLLER API *************************/
319 IsDiskPresent(IN PDISK_IMAGE DiskImage
)
322 return (DiskImage
->hDisk
!= INVALID_HANDLE_VALUE
&& DiskImage
->hDisk
!= NULL
);
326 SeekDisk(IN PDISK_IMAGE DiskImage
,
333 /* Check that the sector number is 1-based */
334 // FIXME: Or do it in the caller?
342 // Compute the offset
343 FilePointer
= ((Cylinder
* DiskImage
->DiskInfo
.Heads
+ Head
)
344 * DiskImage
->DiskInfo
.Sectors
+ (Sector
- 1))
345 * DiskImage
->DiskInfo
.SectorSize
;
346 FilePointer
= SetFilePointer(DiskImage
->hDisk
, FilePointer
, NULL
, FILE_BEGIN
);
347 if (FilePointer
== INVALID_SET_FILE_POINTER
)
357 ReadDisk(IN PDISK_IMAGE DiskImage
,
367 BYTE StaticBuffer
[1024];
369 /* Read the sectors */
370 Result
= SeekDisk(DiskImage
, Cylinder
, Head
, Sector
);
374 BytesToRead
= NumSectors
* DiskImage
->DiskInfo
.SectorSize
;
376 // FIXME: Consider just looping around filling each time the buffer...
378 if (BytesToRead
<= sizeof(StaticBuffer
))
380 LocalBuffer
= StaticBuffer
;
384 LocalBuffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToRead
);
385 ASSERT(LocalBuffer
!= NULL
);
388 if (ReadFile(DiskImage
->hDisk
, LocalBuffer
, BytesToRead
, &BytesToRead
, NULL
))
390 /* Write to the memory */
391 EmulatorWriteMemory(&EmulatorContext
,
392 TO_LINEAR(getES(), getBX()),
403 if (LocalBuffer
!= StaticBuffer
)
404 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer
);
406 /* Return success or error */
411 WriteDisk(IN PDISK_IMAGE DiskImage
,
421 BYTE StaticBuffer
[1024];
423 /* Check for write protection */
424 if (DiskImage
->ReadOnly
)
430 /* Write the sectors */
431 Result
= SeekDisk(DiskImage
, Cylinder
, Head
, Sector
);
435 BytesToWrite
= NumSectors
* DiskImage
->DiskInfo
.SectorSize
;
437 // FIXME: Consider just looping around filling each time the buffer...
439 if (BytesToWrite
<= sizeof(StaticBuffer
))
441 LocalBuffer
= StaticBuffer
;
445 LocalBuffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToWrite
);
446 ASSERT(LocalBuffer
!= NULL
);
449 /* Read from the memory */
450 EmulatorReadMemory(&EmulatorContext
,
451 TO_LINEAR(getES(), getBX()),
455 if (WriteFile(DiskImage
->hDisk
, LocalBuffer
, BytesToWrite
, &BytesToWrite
, NULL
))
460 if (LocalBuffer
!= StaticBuffer
)
461 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer
);
463 /* Return success or error */
467 typedef BOOLEAN (*MOUNT_DISK_HANDLER
)(IN PDISK_IMAGE DiskImage
, IN HANDLE hFile
);
469 typedef struct _DISK_MOUNT_INFO
471 PDISK_IMAGE DiskArray
;
473 MOUNT_DISK_HANDLER MountDiskHelper
;
474 } DISK_MOUNT_INFO
, *PDISK_MOUNT_INFO
;
476 static DISK_MOUNT_INFO DiskMountInfo
[MAX_DISK_TYPE
] =
478 {XDCFloppyDrive
, _ARRAYSIZE(XDCFloppyDrive
), MountFDI
},
479 {XDCHardDrive
, _ARRAYSIZE(XDCHardDrive
) , MountHDD
},
483 RetrieveDisk(IN DISK_TYPE DiskType
,
486 ASSERT(DiskType
< MAX_DISK_TYPE
);
488 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
490 DisplayMessage(L
"RetrieveDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
494 return &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
498 MountDisk(IN DISK_TYPE DiskType
,
503 BOOLEAN Success
= FALSE
;
504 PDISK_IMAGE DiskImage
;
507 BY_HANDLE_FILE_INFORMATION FileInformation
;
509 ASSERT(DiskType
< MAX_DISK_TYPE
);
511 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
513 DisplayMessage(L
"MountDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
517 DiskImage
= &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
518 if (IsDiskPresent(DiskImage
))
520 DPRINT1("MountDisk: Disk %d:%d:0x%p already in use, recycling...\n", DiskType
, DiskNumber
, DiskImage
);
521 UnmountDisk(DiskType
, DiskNumber
);
524 /* Try to open the file */
525 SetLastError(0); // For debugging purposes
528 hFile
= CreateFileA(FileName
,
533 FILE_ATTRIBUTE_NORMAL
,
538 hFile
= CreateFileA(FileName
,
539 GENERIC_READ
| GENERIC_WRITE
,
540 0, // No sharing access
543 FILE_ATTRIBUTE_NORMAL
,
546 DPRINT1("File '%s' opening %s ; GetLastError() = %u\n",
547 FileName
, hFile
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
549 /* If we failed, bail out */
550 if (hFile
== INVALID_HANDLE_VALUE
) return FALSE
;
552 /* OK, we have a handle to the file */
555 * Check that it is really a file, and not a physical drive.
556 * For obvious security reasons, we do not want to be able to
557 * write directly to physical drives.
562 if (!GetFileInformationByHandle(hFile
, &FileInformation
) &&
563 GetLastError() == ERROR_INVALID_FUNCTION
)
565 /* Objects other than real files are not supported */
566 DisplayMessage(L
"'%S' is not a valid disk file.", FileName
);
570 if (GetFileSize(hFile
, NULL
) == INVALID_FILE_SIZE
&&
571 GetLastError() == ERROR_INVALID_FUNCTION
)
573 /* Objects other than real files are not supported */
574 DisplayMessage(L
"'%S' is not a valid disk file.", FileName
);
578 /* Success, mount the image */
579 if (!DiskMountInfo
[DiskType
].MountDiskHelper(DiskImage
, hFile
))
581 DisplayMessage(L
"MountDisk: Failed to mount disk file '%S' in 0x%p.", FileName
, DiskImage
);
587 if (!Success
) FileClose(hFile
);
592 UnmountDisk(IN DISK_TYPE DiskType
,
595 PDISK_IMAGE DiskImage
;
597 ASSERT(DiskType
< MAX_DISK_TYPE
);
599 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
601 DisplayMessage(L
"UnmountDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
605 DiskImage
= &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
606 if (!IsDiskPresent(DiskImage
))
608 DPRINT1("UnmountDisk: Disk %d:%d:0x%p is already unmounted\n", DiskType
, DiskNumber
, DiskImage
);
612 /* Flush the image and unmount it */
613 FlushFileBuffers(DiskImage
->hDisk
);
614 FileClose(DiskImage
->hDisk
);
615 DiskImage
->hDisk
= NULL
;
620 /* PUBLIC FUNCTIONS ***********************************************************/
622 BOOLEAN
DiskCtrlInitialize(VOID
)
627 VOID
DiskCtrlCleanup(VOID
)
631 /* Unmount all the floppy disk drives */
632 for (DiskNumber
= 0; DiskNumber
< DiskMountInfo
[FLOPPY_DISK
].NumDisks
; ++DiskNumber
)
633 UnmountDisk(FLOPPY_DISK
, DiskNumber
);
635 /* Unmount all the hard disk drives */
636 for (DiskNumber
= 0; DiskNumber
< DiskMountInfo
[HARD_DISK
].NumDisks
; ++DiskNumber
)
637 UnmountDisk(HARD_DISK
, DiskNumber
);