xref: /reactos/subsystems/mvdm/ntvdm/hardware/disk.c (revision 4561998a)
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