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!
26 /**************** HARD DRIVES -- VHD FIXED DISK FORMAT SUPPORT ****************/
28 // http://citrixblogger.org/2008/12/01/dynamic-vhd-walkthrough/
29 // http://www.microsoft.com/en-us/download/details.aspx?id=23850
30 // https://projects.honeynet.org/svn/sebek/virtualization/qebek/trunk/block/vpc.c
31 // https://git.virtualopensystems.com/trescca/qemu/raw/40645c7bfd7c4d45381927e1e80081fa827c368a/block/vpc.c
32 // https://gitweb.gentoo.org/proj/qemu-kvm.git/tree/block/vpc.c?h=qemu-kvm-0.12.4-gentoo&id=827dccd6740639c64732418539bf17e6e4c99d77
43 // Seconds since Jan 1, 2000 0:00:00 (UTC)
44 #define VHD_TIMESTAMP_BASE 946684800
46 // Always in BIG-endian format!
47 typedef struct _VHD_FOOTER
49 CHAR creator
[8]; // "conectix"
53 // Offset of next header structure, 0xFFFFFFFF if none
56 // Seconds since Jan 1, 2000 0:00:00 (UTC)
59 CHAR creator_app
[4]; // "vpc "; "win"
62 CHAR creator_os
[4]; // "Wi2k"
71 ULONG type
; // VHD_TYPE
73 // Checksum of the Hard Disk Footer ("one's complement of the sum of all
74 // the bytes in the footer without the checksum field")
77 // UUID used to identify a parent hard disk (backing file)
82 BYTE Padding
[0x200-0x55];
84 } VHD_FOOTER
, *PVHD_FOOTER
;
85 C_ASSERT(sizeof(VHD_FOOTER
) == 0x200);
91 * Calculates the number of cylinders, heads and sectors per cylinder
92 * based on a given number of sectors. This is the algorithm described
93 * in the VHD specification.
95 * Note that the geometry doesn't always exactly match total_sectors but
98 * Returns TRUE on success, FALSE if the size is larger than 127 GB
101 calculate_geometry(ULONG64 total_sectors
, PUSHORT cyls
,
102 PBYTE heads
, PBYTE secs_per_cyl
)
104 ULONG cyls_times_heads
;
106 if (total_sectors
> 65535 * 16 * 255)
109 if (total_sectors
> 65535 * 16 * 63)
113 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
118 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
119 *heads
= (cyls_times_heads
+ 1023) / 1024;
124 if (cyls_times_heads
>= (*heads
* 1024) || *heads
> 16)
128 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
131 if (cyls_times_heads
>= (*heads
* 1024))
135 cyls_times_heads
= total_sectors
/ *secs_per_cyl
;
139 *cyls
= cyls_times_heads
/ *heads
;
147 /*************************** FLOPPY DISK CONTROLLER ***************************/
149 // A Floppy Controller can support up to 4 floppy drives.
150 static DISK_IMAGE XDCFloppyDrive
[4];
153 typedef struct _DISK_GEO
155 DWORD ksize
; /* Size in kilobytes */
156 WORD secttrack
; /* Sectors per track */
157 WORD headscyl
; /* Heads per cylinder */
158 WORD cylcount
; /* Cylinders per side */
159 WORD biosval
; /* Type to return from BIOS & CMOS */
160 } DISK_GEO
, *PDISK_GEO
;
162 // FIXME: At the moment, all of our diskettes have 512 bytes per sector...
163 static WORD HackSectorSize
= 512;
164 static DISK_GEO DiskGeometryList
[] =
168 { 200, 10, 1, 40, 0},
171 { 400, 10, 2, 40, 1},
173 {1200, 15, 2, 80, 2},
174 {1440, 18, 2, 80, 4},
175 {2880, 36, 2, 80, 6},
179 MountFDI(IN PDISK_IMAGE DiskImage
,
186 * Retrieve the size of the file. In NTVDM we will handle files
187 * of maximum 1Mb so we can largely use GetFileSize only.
189 FileSize
= GetFileSize(hFile
, NULL
);
190 if (FileSize
== INVALID_FILE_SIZE
&& GetLastError() != ERROR_SUCCESS
)
192 /* We failed, bail out */
193 DisplayMessage(L
"MountFDI: Error when retrieving file size, or size too large (%d).", FileSize
);
197 /* Convert the size in kB */
200 /* Find the floppy format in the list, and mount it if found */
201 for (i
= 0; i
< ARRAYSIZE(DiskGeometryList
); ++i
)
203 if (DiskGeometryList
[i
].ksize
== FileSize
||
204 DiskGeometryList
[i
].ksize
+ 1 == FileSize
)
206 /* Found, mount it */
207 DiskImage
->DiskType
= DiskGeometryList
[i
].biosval
;
208 DiskImage
->DiskInfo
.Cylinders
= DiskGeometryList
[i
].cylcount
;
209 DiskImage
->DiskInfo
.Heads
= DiskGeometryList
[i
].headscyl
;
210 DiskImage
->DiskInfo
.Sectors
= DiskGeometryList
[i
].secttrack
;
211 DiskImage
->DiskInfo
.SectorSize
= HackSectorSize
;
213 /* Set the file pointer to the beginning */
214 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
216 DiskImage
->hDisk
= hFile
;
221 /* If we are here, we failed to find a suitable format. Bail out. */
222 DisplayMessage(L
"MountFDI: Floppy image of invalid size %d.", FileSize
);
227 /************************** IDE HARD DISK CONTROLLER **************************/
229 // An IDE Hard Disk Controller can support up to 4 drives:
230 // Primary Master Drive, Primary Slave Drive,
231 // Secondary Master Drive, Secondary Slave Drive.
232 static DISK_IMAGE XDCHardDrive
[4];
235 MountHDD(IN PDISK_IMAGE DiskImage
,
238 /**** Support for VHD fixed disks ****/
239 DWORD FilePointer
, BytesToRead
;
240 VHD_FOOTER vhd_footer
;
242 /* Go to the end of the file and retrieve the footer */
243 FilePointer
= SetFilePointer(hFile
, -(LONG
)sizeof(VHD_FOOTER
), NULL
, FILE_END
);
244 if (FilePointer
== INVALID_SET_FILE_POINTER
)
246 DPRINT1("MountHDD: Error when seeking HDD footer, last error = %d\n", GetLastError());
251 // FIXME: We may consider just mapping section to the file...
252 BytesToRead
= sizeof(VHD_FOOTER
);
253 if (!ReadFile(hFile
, &vhd_footer
, BytesToRead
, &BytesToRead
, NULL
))
255 DPRINT1("MountHDD: Error when reading HDD footer, last error = %d\n", GetLastError());
259 /* Perform validity checks */
260 if (RtlCompareMemory(vhd_footer
.creator
, "conectix",
261 sizeof(vhd_footer
.creator
)) != sizeof(vhd_footer
.creator
))
263 DisplayMessage(L
"MountHDD: Invalid HDD image (expected VHD).");
266 if (vhd_footer
.version
!= 0x00000100 &&
267 vhd_footer
.version
!= 0x00000500) // FIXME: Big endian!
269 DisplayMessage(L
"MountHDD: VHD HDD image of unexpected version %d.", vhd_footer
.version
);
272 if (RtlUlongByteSwap(vhd_footer
.type
) != VHD_FIXED
) // FIXME: Big endian!
274 DisplayMessage(L
"MountHDD: Only VHD HDD fixed images are supported.");
277 if (vhd_footer
.data_offset
!= 0xFFFFFFFFFFFFFFFF)
279 DisplayMessage(L
"MountHDD: Unexpected data offset for VHD HDD fixed image.");
282 if (vhd_footer
.orig_size
!= vhd_footer
.size
)
284 DisplayMessage(L
"MountHDD: VHD HDD fixed image size should be the same as its original size.");
289 /* Found, mount it */
290 DiskImage
->DiskType
= 0;
291 DiskImage
->DiskInfo
.Cylinders
= RtlUshortByteSwap(vhd_footer
.cyls
); // FIXME: Big endian!
292 DiskImage
->DiskInfo
.Heads
= vhd_footer
.heads
;
293 DiskImage
->DiskInfo
.Sectors
= vhd_footer
.secs_per_cyl
;
294 DiskImage
->DiskInfo
.SectorSize
= RtlUlonglongByteSwap(vhd_footer
.size
) / // FIXME: Big endian!
295 DiskImage
->DiskInfo
.Cylinders
/
296 DiskImage
->DiskInfo
.Heads
/ DiskImage
->DiskInfo
.Sectors
;
298 /* Set the file pointer to the beginning */
299 SetFilePointer(hFile
, 0, NULL
, FILE_BEGIN
);
301 DiskImage
->hDisk
= hFile
;
307 /************************ GENERIC DISK CONTROLLER API *************************/
310 IsDiskPresent(IN PDISK_IMAGE DiskImage
)
313 return (DiskImage
->hDisk
!= INVALID_HANDLE_VALUE
&& DiskImage
->hDisk
!= NULL
);
317 SeekDisk(IN PDISK_IMAGE DiskImage
,
324 /* Check that the sector number is 1-based */
325 // FIXME: Or do it in the caller?
333 // Compute the offset
334 FilePointer
= ((Cylinder
* DiskImage
->DiskInfo
.Heads
+ Head
)
335 * DiskImage
->DiskInfo
.Sectors
+ (Sector
- 1))
336 * DiskImage
->DiskInfo
.SectorSize
;
337 FilePointer
= SetFilePointer(DiskImage
->hDisk
, FilePointer
, NULL
, FILE_BEGIN
);
338 if (FilePointer
== INVALID_SET_FILE_POINTER
)
348 ReadDisk(IN PDISK_IMAGE DiskImage
,
358 BYTE StaticBuffer
[1024];
360 /* Read the sectors */
361 Result
= SeekDisk(DiskImage
, Cylinder
, Head
, Sector
);
365 BytesToRead
= NumSectors
* DiskImage
->DiskInfo
.SectorSize
;
367 // FIXME: Consider just looping around filling each time the buffer...
369 if (BytesToRead
<= sizeof(StaticBuffer
))
371 LocalBuffer
= StaticBuffer
;
375 LocalBuffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToRead
);
376 ASSERT(LocalBuffer
!= NULL
);
379 if (ReadFile(DiskImage
->hDisk
, LocalBuffer
, BytesToRead
, &BytesToRead
, NULL
))
381 /* Write to the memory */
382 EmulatorWriteMemory(&EmulatorContext
,
383 TO_LINEAR(getES(), getBX()),
394 if (LocalBuffer
!= StaticBuffer
)
395 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer
);
397 /* Return success or error */
402 WriteDisk(IN PDISK_IMAGE DiskImage
,
412 BYTE StaticBuffer
[1024];
414 /* Check for write protection */
415 if (DiskImage
->ReadOnly
)
421 /* Write the sectors */
422 Result
= SeekDisk(DiskImage
, Cylinder
, Head
, Sector
);
426 BytesToWrite
= NumSectors
* DiskImage
->DiskInfo
.SectorSize
;
428 // FIXME: Consider just looping around filling each time the buffer...
430 if (BytesToWrite
<= sizeof(StaticBuffer
))
432 LocalBuffer
= StaticBuffer
;
436 LocalBuffer
= RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToWrite
);
437 ASSERT(LocalBuffer
!= NULL
);
440 /* Read from the memory */
441 EmulatorReadMemory(&EmulatorContext
,
442 TO_LINEAR(getES(), getBX()),
446 if (WriteFile(DiskImage
->hDisk
, LocalBuffer
, BytesToWrite
, &BytesToWrite
, NULL
))
451 if (LocalBuffer
!= StaticBuffer
)
452 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer
);
454 /* Return success or error */
458 typedef BOOLEAN (*MOUNT_DISK_HANDLER
)(IN PDISK_IMAGE DiskImage
, IN HANDLE hFile
);
460 typedef struct _DISK_MOUNT_INFO
462 PDISK_IMAGE DiskArray
;
464 MOUNT_DISK_HANDLER MountDiskHelper
;
465 } DISK_MOUNT_INFO
, *PDISK_MOUNT_INFO
;
467 static DISK_MOUNT_INFO DiskMountInfo
[MAX_DISK_TYPE
] =
469 {XDCFloppyDrive
, _ARRAYSIZE(XDCFloppyDrive
), MountFDI
},
470 {XDCHardDrive
, _ARRAYSIZE(XDCHardDrive
) , MountHDD
},
474 RetrieveDisk(IN DISK_TYPE DiskType
,
477 ASSERT(DiskType
< MAX_DISK_TYPE
);
479 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
481 DisplayMessage(L
"RetrieveDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
485 return &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
489 MountDisk(IN DISK_TYPE DiskType
,
494 BOOLEAN Success
= FALSE
;
495 PDISK_IMAGE DiskImage
;
498 BY_HANDLE_FILE_INFORMATION FileInformation
;
500 ASSERT(DiskType
< MAX_DISK_TYPE
);
502 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
504 DisplayMessage(L
"MountDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
508 DiskImage
= &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
509 if (IsDiskPresent(DiskImage
))
511 DPRINT1("MountDisk: Disk %d:%d:0x%p already in use, recycling...\n", DiskType
, DiskNumber
, DiskImage
);
512 UnmountDisk(DiskType
, DiskNumber
);
515 /* Try to open the file */
516 SetLastError(0); // For debugging purposes
519 hFile
= CreateFileA(FileName
,
524 FILE_ATTRIBUTE_NORMAL
,
529 hFile
= CreateFileA(FileName
,
530 GENERIC_READ
| GENERIC_WRITE
,
531 0, // No sharing access
534 FILE_ATTRIBUTE_NORMAL
,
537 DPRINT1("File '%s' opening %s ; GetLastError() = %u\n",
538 FileName
, hFile
!= INVALID_HANDLE_VALUE
? "succeeded" : "failed", GetLastError());
540 /* If we failed, bail out */
541 if (hFile
== INVALID_HANDLE_VALUE
)
543 DisplayMessage(L
"MountDisk: Error when opening disk file '%S' (Error: %u).", FileName
, GetLastError());
547 /* OK, we have a handle to the file */
550 * Check that it is really a file, and not a physical drive.
551 * For obvious security reasons, we do not want to be able to
552 * write directly to physical drives.
557 if (!GetFileInformationByHandle(hFile
, &FileInformation
) &&
558 GetLastError() == ERROR_INVALID_FUNCTION
)
560 /* Objects other than real files are not supported */
561 DisplayMessage(L
"MountDisk: '%S' is not a valid disk file.", FileName
);
565 if (GetFileSize(hFile
, NULL
) == INVALID_FILE_SIZE
&&
566 GetLastError() == ERROR_INVALID_FUNCTION
)
568 /* Objects other than real files are not supported */
569 DisplayMessage(L
"MountDisk: '%S' is not a valid disk file.", FileName
);
573 /* Success, mount the image */
574 if (!DiskMountInfo
[DiskType
].MountDiskHelper(DiskImage
, hFile
))
576 DisplayMessage(L
"MountDisk: Failed to mount disk file '%S' in 0x%p.", FileName
, DiskImage
);
580 /* Update its read/write state */
581 DiskImage
->ReadOnly
= ReadOnly
;
586 if (!Success
) FileClose(hFile
);
591 UnmountDisk(IN DISK_TYPE DiskType
,
594 PDISK_IMAGE DiskImage
;
596 ASSERT(DiskType
< MAX_DISK_TYPE
);
598 if (DiskNumber
>= DiskMountInfo
[DiskType
].NumDisks
)
600 DisplayMessage(L
"UnmountDisk: Disk number %d:%d invalid.", DiskType
, DiskNumber
);
604 DiskImage
= &DiskMountInfo
[DiskType
].DiskArray
[DiskNumber
];
605 if (!IsDiskPresent(DiskImage
))
607 DPRINT1("UnmountDisk: Disk %d:%d:0x%p is already unmounted\n", DiskType
, DiskNumber
, DiskImage
);
611 /* Flush the image and unmount it */
612 FlushFileBuffers(DiskImage
->hDisk
);
613 FileClose(DiskImage
->hDisk
);
614 DiskImage
->hDisk
= NULL
;
619 /* PUBLIC FUNCTIONS ***********************************************************/
621 BOOLEAN
DiskCtrlInitialize(VOID
)
626 VOID
DiskCtrlCleanup(VOID
)
630 /* Unmount all the floppy disk drives */
631 for (DiskNumber
= 0; DiskNumber
< DiskMountInfo
[FLOPPY_DISK
].NumDisks
; ++DiskNumber
)
632 UnmountDisk(FLOPPY_DISK
, DiskNumber
);
634 /* Unmount all the hard disk drives */
635 for (DiskNumber
= 0; DiskNumber
< DiskMountInfo
[HARD_DISK
].NumDisks
; ++DiskNumber
)
636 UnmountDisk(HARD_DISK
, DiskNumber
);