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 *******************************************************************/
37 /**************** HARD DRIVES -- VHD FIXED DISK FORMAT SUPPORT ****************/
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
54 // Seconds since Jan 1, 2000 0:00:00 (UTC)
55 #define VHD_TIMESTAMP_BASE 946684800
57 // Always in BIG-endian format!
58 typedef struct _VHD_FOOTER
60 CHAR creator
[8]; // "conectix"
64 // Offset of next header structure, 0xFFFFFFFF if none
67 // Seconds since Jan 1, 2000 0:00:00 (UTC)
70 CHAR creator_app
[4]; // "vpc "; "win"
73 CHAR creator_os
[4]; // "Wi2k"
82 ULONG type
; // VHD_TYPE
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")
88 // UUID used to identify a parent hard disk (backing file)
93 BYTE Padding
[0x200-0x55];
95 } VHD_FOOTER
, *PVHD_FOOTER
;
96 C_ASSERT(sizeof(VHD_FOOTER
) == 0x200);
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.
106 * Note that the geometry doesn't always exactly match total_sectors but
109 * Returns TRUE on success, FALSE if the size is larger than 127 GB
112 calculate_geometry(ULONG64 total_sectors
, PUSHORT cyls
,
113 PBYTE heads
, PBYTE secs_per_cyl
)
115 ULONG cyls_times_heads
;
117 if (total_sectors
> 65535 * 16 * 255)
120 if (total_sectors
> 65535 * 16 * 63)
124 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
129 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
130 *heads
= (cyls_times_heads
+ 1023) / 1024;
135 if (cyls_times_heads
>= (*heads
* 1024) || *heads
> 16)
139 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
142 if (cyls_times_heads
>= (*heads
* 1024))
146 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
150 *cyls
= cyls_times_heads
/ *heads
;
158 /*************************** FLOPPY DISK CONTROLLER ***************************/
160 // A Floppy Controller can support up to 4 floppy drives.
161 static DISK_IMAGE XDCFloppyDrive
[4];
164 typedef struct _DISK_GEO
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
;
173 // FIXME: At the moment, all of our diskettes have 512 bytes per sector...
174 static WORD HackSectorSize
= 512;
175 static DISK_GEO DiskGeometryList
[] =
179 { 200, 10, 1, 40, 0},
182 { 400, 10, 2, 40, 1},
184 {1200, 15, 2, 80, 2},
185 {1440, 18, 2, 80, 4},
186 {2880, 36, 2, 80, 6},
190 MountFDI(IN PDISK_IMAGE DiskImage
,
197 * Retrieve the size of the file. In NTVDM we will handle files
198 * of maximum 1Mb so we can largely use GetFileSize only.
200 FileSize
= GetFileSize(hFile
, NULL
);
201 if (FileSize
== INVALID_FILE_SIZE
&& GetLastError() != ERROR_SUCCESS
)
203 /* We failed, bail out */
204 DisplayMessage(L
"MountFDI: Error when retrieving file size, or size too large (%d).", FileSize
);
208 /* Convert the size in kB */
211 /* Find the floppy format in the list, and mount it if found */
212 for (i
= 0; i
< ARRAYSIZE(DiskGeometryList
); ++i
)
214 if (DiskGeometryList
[i
].ksize
== FileSize
||
215 DiskGeometryList
[i
].ksize
+ 1 == FileSize
)
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
;
224 /* Set the file pointer to the beginning */
225 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
227 DiskImage
->hDisk
= hFile
;
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
);
238 /************************** IDE HARD DISK CONTROLLER **************************/
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];
246 MountHDD(IN PDISK_IMAGE DiskImage
,
249 /**** Support for VHD fixed disks ****/
250 DWORD FilePointer
, BytesToRead
;
251 VHD_FOOTER vhd_footer
;
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
)
257 DPRINT1("MountHDD: Error when seeking HDD footer, last error = %d\n", GetLastError());
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
))
266 DPRINT1("MountHDD: Error when reading HDD footer, last error = %d\n", GetLastError());
270 /* Perform validity checks */
271 if (RtlCompareMemory(vhd_footer
.creator
, "conectix",
272 sizeof(vhd_footer
.creator
)) != sizeof(vhd_footer
.creator
))
274 DisplayMessage(L
"MountHDD: Invalid HDD image (expected VHD).");
277 if (vhd_footer
.version
!= 0x00000100 &&
278 vhd_footer
.version
!= 0x00000500) // FIXME: Big endian!
280 DisplayMessage(L
"MountHDD: VHD HDD image of unexpected version %d.", vhd_footer
.version
);
283 if (RtlUlongByteSwap(vhd_footer
.type
) != VHD_FIXED
) // FIXME: Big endian!
285 DisplayMessage(L
"MountHDD: Only VHD HDD fixed images are supported.");
288 if (vhd_footer
.data_offset
!= 0xFFFFFFFFFFFFFFFF)
290 DisplayMessage(L
"MountHDD: Unexpected data offset for VHD HDD fixed image.");
293 if (vhd_footer
.orig_size
!= vhd_footer
.size
)
295 DisplayMessage(L
"MountHDD: VHD HDD fixed image size should be the same as its original size.");
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
;
309 /* Set the file pointer to the beginning */
310 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
312 DiskImage
->hDisk
= hFile
;
318 /************************ GENERIC DISK CONTROLLER API *************************/
321 IsDiskPresent(IN PDISK_IMAGE DiskImage
)
324 return (DiskImage
->hDisk
!= INVALID_HANDLE_VALUE
&& DiskImage
->hDisk
!= NULL
);
328 SeekDisk(IN PDISK_IMAGE DiskImage
,
335 /* Check that the sector number is 1-based */
336 // FIXME: Or do it in the caller?
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
)
359 ReadDisk(IN PDISK_IMAGE DiskImage
,
369 BYTE StaticBuffer
[1024];
371 /* Read the sectors */
372 Result
= SeekDisk(DiskImage
, Cylinder
, Head
, Sector
);
376 BytesToRead
= (DWORD
)NumSectors
* DiskImage
->DiskInfo
.SectorSize
;
378 // FIXME: Consider just looping around filling each time the buffer...
380 if (BytesToRead
<= sizeof(StaticBuffer
))
382 LocalBuffer
= StaticBuffer
;
386 LocalBuffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToRead
);
387 ASSERT(LocalBuffer
!= NULL
);
390 if (ReadFile(DiskImage
->hDisk
, LocalBuffer
, BytesToRead
, &BytesToRead
, NULL
))
392 /* Write to the memory */
393 EmulatorWriteMemory(&EmulatorContext
,
394 TO_LINEAR(getES(), getBX()),
405 if (LocalBuffer
!= StaticBuffer
)
406 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer
);
408 /* Return success or error */
413 WriteDisk(IN PDISK_IMAGE DiskImage
,
423 BYTE StaticBuffer
[1024];
425 /* Check for write protection */
426 if (DiskImage
->ReadOnly
)
432 /* Write the sectors */
433 Result
= SeekDisk(DiskImage
, Cylinder
, Head
, Sector
);
437 BytesToWrite
= (DWORD
)NumSectors
* DiskImage
->DiskInfo
.SectorSize
;
439 // FIXME: Consider just looping around filling each time the buffer...
441 if (BytesToWrite
<= sizeof(StaticBuffer
))
443 LocalBuffer
= StaticBuffer
;
447 LocalBuffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToWrite
);
448 ASSERT(LocalBuffer
!= NULL
);
451 /* Read from the memory */
452 EmulatorReadMemory(&EmulatorContext
,
453 TO_LINEAR(getES(), getBX()),
457 if (WriteFile(DiskImage
->hDisk
, LocalBuffer
, BytesToWrite
, &BytesToWrite
, NULL
))
462 if (LocalBuffer
!= StaticBuffer
)
463 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer
);
465 /* Return success or error */
469 typedef BOOLEAN (*MOUNT_DISK_HANDLER
)(IN PDISK_IMAGE DiskImage
, IN HANDLE hFile
);
471 typedef struct _DISK_MOUNT_INFO
473 PDISK_IMAGE DiskArray
;
475 MOUNT_DISK_HANDLER MountDiskHelper
;
476 } DISK_MOUNT_INFO
, *PDISK_MOUNT_INFO
;
478 static DISK_MOUNT_INFO DiskMountInfo
[MAX_DISK_TYPE
] =
480 {XDCFloppyDrive
, _ARRAYSIZE(XDCFloppyDrive
), MountFDI
},
481 {XDCHardDrive
, _ARRAYSIZE(XDCHardDrive
) , MountHDD
},
485 RetrieveDisk(IN DISK_TYPE DiskType
,
488 ASSERT(DiskType
< MAX_DISK_TYPE
);
490 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
492 DisplayMessage(L
"RetrieveDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
496 return &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
500 MountDisk(IN DISK_TYPE DiskType
,
505 BOOLEAN Success
= FALSE
;
506 PDISK_IMAGE DiskImage
;
509 BY_HANDLE_FILE_INFORMATION FileInformation
;
511 ASSERT(DiskType
< MAX_DISK_TYPE
);
513 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
515 DisplayMessage(L
"MountDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
519 DiskImage
= &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
520 if (IsDiskPresent(DiskImage
))
522 DPRINT1("MountDisk: Disk %d:%d:0x%p already in use, recycling...\n", DiskType
, DiskNumber
, DiskImage
);
523 UnmountDisk(DiskType
, DiskNumber
);
526 /* Try to open the file */
527 SetLastError(0); // For debugging purposes
530 hFile
= CreateFileW(FileName
,
535 FILE_ATTRIBUTE_NORMAL
,
540 hFile
= CreateFileW(FileName
,
541 GENERIC_READ
| GENERIC_WRITE
,
542 0, // No sharing access
545 FILE_ATTRIBUTE_NORMAL
,
548 DPRINT1("File '%S' opening %s ; GetLastError() = %u\n",
549 FileName
, hFile
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
551 /* If we failed, bail out */
552 if (hFile
== INVALID_HANDLE_VALUE
)
554 DisplayMessage(L
"MountDisk: Error when opening disk file '%s' (Error: %u).", FileName
, GetLastError());
558 /* OK, we have a handle to the file */
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.
568 if (!GetFileInformationByHandle(hFile
, &FileInformation
) &&
569 GetLastError() == ERROR_INVALID_FUNCTION
)
571 /* Objects other than real files are not supported */
572 DisplayMessage(L
"MountDisk: '%s' is not a valid disk file.", FileName
);
576 if (GetFileSize(hFile
, NULL
) == INVALID_FILE_SIZE
&&
577 GetLastError() == ERROR_INVALID_FUNCTION
)
579 /* Objects other than real files are not supported */
580 DisplayMessage(L
"MountDisk: '%s' is not a valid disk file.", FileName
);
584 /* Success, mount the image */
585 if (!DiskMountInfo
[DiskType
].MountDiskHelper(DiskImage
, hFile
))
587 DisplayMessage(L
"MountDisk: Failed to mount disk file '%s' in 0x%p.", FileName
, DiskImage
);
591 /* Update its read/write state */
592 DiskImage
->ReadOnly
= ReadOnly
;
597 if (!Success
) FileClose(hFile
);
602 UnmountDisk(IN DISK_TYPE DiskType
,
605 PDISK_IMAGE DiskImage
;
607 ASSERT(DiskType
< MAX_DISK_TYPE
);
609 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
611 DisplayMessage(L
"UnmountDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
615 DiskImage
= &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
616 if (!IsDiskPresent(DiskImage
))
618 DPRINT1("UnmountDisk: Disk %d:%d:0x%p is already unmounted\n", DiskType
, DiskNumber
, DiskImage
);
622 /* Flush the image and unmount it */
623 FlushFileBuffers(DiskImage
->hDisk
);
624 FileClose(DiskImage
->hDisk
);
625 DiskImage
->hDisk
= NULL
;
630 /* PUBLIC FUNCTIONS ***********************************************************/
632 BOOLEAN
DiskCtrlInitialize(VOID
)
637 VOID
DiskCtrlCleanup(VOID
)
641 /* Unmount all the floppy disk drives */
642 for (DiskNumber
= 0; DiskNumber
< DiskMountInfo
[FLOPPY_DISK
].NumDisks
; ++DiskNumber
)
643 UnmountDisk(FLOPPY_DISK
, DiskNumber
);
645 /* Unmount all the hard disk drives */
646 for (DiskNumber
= 0; DiskNumber
< DiskMountInfo
[HARD_DISK
].NumDisks
; ++DiskNumber
)
647 UnmountDisk(HARD_DISK
, DiskNumber
);