[NTVDM] Improve the PCH situation.
[reactos.git] / reactos / subsystems / mvdm / ntvdm / hardware / disk.c
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 #include "ntvdm.h"
22
23 #define NDEBUG
24 #include <debug.h>
25
26 /**************** HARD DRIVES -- VHD FIXED DISK FORMAT SUPPORT ****************/
27
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
33
34 #pragma pack(push, 1)
35
36 enum VHD_TYPE
37 {
38 VHD_FIXED = 2,
39 VHD_DYNAMIC = 3,
40 VHD_DIFFERENCING = 4,
41 };
42
43 // Seconds since Jan 1, 2000 0:00:00 (UTC)
44 #define VHD_TIMESTAMP_BASE 946684800
45
46 // Always in BIG-endian format!
47 typedef struct _VHD_FOOTER
48 {
49 CHAR creator[8]; // "conectix"
50 ULONG features;
51 ULONG version;
52
53 // Offset of next header structure, 0xFFFFFFFF if none
54 ULONG64 data_offset;
55
56 // Seconds since Jan 1, 2000 0:00:00 (UTC)
57 ULONG timestamp;
58
59 CHAR creator_app[4]; // "vpc "; "win"
60 USHORT major;
61 USHORT minor;
62 CHAR creator_os[4]; // "Wi2k"
63
64 ULONG64 orig_size;
65 ULONG64 size;
66
67 USHORT cyls;
68 BYTE heads;
69 BYTE secs_per_cyl;
70
71 ULONG type; // VHD_TYPE
72
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")
75 ULONG checksum;
76
77 // UUID used to identify a parent hard disk (backing file)
78 BYTE uuid[16];
79
80 BYTE in_saved_state;
81
82 BYTE Padding[0x200-0x55];
83
84 } VHD_FOOTER, *PVHD_FOOTER;
85 C_ASSERT(sizeof(VHD_FOOTER) == 0x200);
86
87 #pragma pack(pop)
88
89 #if 0
90 /*
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.
94 *
95 * Note that the geometry doesn't always exactly match total_sectors but
96 * may round it down.
97 *
98 * Returns TRUE on success, FALSE if the size is larger than 127 GB
99 */
100 static BOOLEAN
101 calculate_geometry(ULONG64 total_sectors, PUSHORT cyls,
102 PBYTE heads, PBYTE secs_per_cyl)
103 {
104 ULONG cyls_times_heads;
105
106 if (total_sectors > 65535 * 16 * 255)
107 return FALSE;
108
109 if (total_sectors > 65535 * 16 * 63)
110 {
111 *secs_per_cyl = 255;
112 *heads = 16;
113 cyls_times_heads = total_sectors / *secs_per_cyl;
114 }
115 else
116 {
117 *secs_per_cyl = 17;
118 cyls_times_heads = total_sectors / *secs_per_cyl;
119 *heads = (cyls_times_heads + 1023) / 1024;
120
121 if (*heads < 4)
122 *heads = 4;
123
124 if (cyls_times_heads >= (*heads * 1024) || *heads > 16)
125 {
126 *secs_per_cyl = 31;
127 *heads = 16;
128 cyls_times_heads = total_sectors / *secs_per_cyl;
129 }
130
131 if (cyls_times_heads >= (*heads * 1024))
132 {
133 *secs_per_cyl = 63;
134 *heads = 16;
135 cyls_times_heads = total_sectors / *secs_per_cyl;
136 }
137 }
138
139 *cyls = cyls_times_heads / *heads;
140
141 return TRUE;
142 }
143 #endif
144
145
146
147 /*************************** FLOPPY DISK CONTROLLER ***************************/
148
149 // A Floppy Controller can support up to 4 floppy drives.
150 static DISK_IMAGE XDCFloppyDrive[4];
151
152 // Taken from DOSBox
153 typedef struct _DISK_GEO
154 {
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;
161
162 // FIXME: At the moment, all of our diskettes have 512 bytes per sector...
163 static WORD HackSectorSize = 512;
164 static DISK_GEO DiskGeometryList[] =
165 {
166 { 160, 8, 1, 40, 0},
167 { 180, 9, 1, 40, 0},
168 { 200, 10, 1, 40, 0},
169 { 320, 8, 2, 40, 1},
170 { 360, 9, 2, 40, 1},
171 { 400, 10, 2, 40, 1},
172 { 720, 9, 2, 80, 3},
173 {1200, 15, 2, 80, 2},
174 {1440, 18, 2, 80, 4},
175 {2880, 36, 2, 80, 6},
176 };
177
178 BOOLEAN
179 MountFDI(IN PDISK_IMAGE DiskImage,
180 IN HANDLE hFile)
181 {
182 ULONG FileSize;
183 USHORT i;
184
185 /*
186 * Retrieve the size of the file. In NTVDM we will handle files
187 * of maximum 1Mb so we can largely use GetFileSize only.
188 */
189 FileSize = GetFileSize(hFile, NULL);
190 if (FileSize == INVALID_FILE_SIZE && GetLastError() != ERROR_SUCCESS)
191 {
192 /* We failed, bail out */
193 DisplayMessage(L"MountFDI: Error when retrieving file size, or size too large (%d).", FileSize);
194 return FALSE;
195 }
196
197 /* Convert the size in kB */
198 FileSize /= 1024;
199
200 /* Find the floppy format in the list, and mount it if found */
201 for (i = 0; i < ARRAYSIZE(DiskGeometryList); ++i)
202 {
203 if (DiskGeometryList[i].ksize == FileSize ||
204 DiskGeometryList[i].ksize + 1 == FileSize)
205 {
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;
212
213 /* Set the file pointer to the beginning */
214 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
215
216 DiskImage->hDisk = hFile;
217 return TRUE;
218 }
219 }
220
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);
223 return FALSE;
224 }
225
226
227 /************************** IDE HARD DISK CONTROLLER **************************/
228
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];
233
234 BOOLEAN
235 MountHDD(IN PDISK_IMAGE DiskImage,
236 IN HANDLE hFile)
237 {
238 /**** Support for VHD fixed disks ****/
239 DWORD FilePointer, BytesToRead;
240 VHD_FOOTER vhd_footer;
241
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)
245 {
246 DPRINT1("MountHDD: Error when seeking HDD footer, last error = %d\n", GetLastError());
247 return FALSE;
248 }
249
250 /* Read footer */
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))
254 {
255 DPRINT1("MountHDD: Error when reading HDD footer, last error = %d\n", GetLastError());
256 return FALSE;
257 }
258
259 /* Perform validity checks */
260 if (RtlCompareMemory(vhd_footer.creator, "conectix",
261 sizeof(vhd_footer.creator)) != sizeof(vhd_footer.creator))
262 {
263 DisplayMessage(L"MountHDD: Invalid HDD image (expected VHD).");
264 return FALSE;
265 }
266 if (vhd_footer.version != 0x00000100 &&
267 vhd_footer.version != 0x00000500) // FIXME: Big endian!
268 {
269 DisplayMessage(L"MountHDD: VHD HDD image of unexpected version %d.", vhd_footer.version);
270 return FALSE;
271 }
272 if (RtlUlongByteSwap(vhd_footer.type) != VHD_FIXED) // FIXME: Big endian!
273 {
274 DisplayMessage(L"MountHDD: Only VHD HDD fixed images are supported.");
275 return FALSE;
276 }
277 if (vhd_footer.data_offset != 0xFFFFFFFFFFFFFFFF)
278 {
279 DisplayMessage(L"MountHDD: Unexpected data offset for VHD HDD fixed image.");
280 return FALSE;
281 }
282 if (vhd_footer.orig_size != vhd_footer.size)
283 {
284 DisplayMessage(L"MountHDD: VHD HDD fixed image size should be the same as its original size.");
285 return FALSE;
286 }
287 // FIXME: Checksum!
288
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;
297
298 /* Set the file pointer to the beginning */
299 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
300
301 DiskImage->hDisk = hFile;
302 return TRUE;
303 }
304
305
306
307 /************************ GENERIC DISK CONTROLLER API *************************/
308
309 BOOLEAN
310 IsDiskPresent(IN PDISK_IMAGE DiskImage)
311 {
312 ASSERT(DiskImage);
313 return (DiskImage->hDisk != INVALID_HANDLE_VALUE && DiskImage->hDisk != NULL);
314 }
315
316 BYTE
317 SeekDisk(IN PDISK_IMAGE DiskImage,
318 IN WORD Cylinder,
319 IN BYTE Head,
320 IN BYTE Sector)
321 {
322 DWORD FilePointer;
323
324 /* Check that the sector number is 1-based */
325 // FIXME: Or do it in the caller?
326 if (Sector == 0)
327 {
328 /* Return error */
329 return 0x01;
330 }
331
332 /* Set position */
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)
339 {
340 /* Return error */
341 return 0x40;
342 }
343
344 return 0x00;
345 }
346
347 BYTE
348 ReadDisk(IN PDISK_IMAGE DiskImage,
349 IN WORD Cylinder,
350 IN BYTE Head,
351 IN BYTE Sector,
352 IN BYTE NumSectors)
353 {
354 BYTE Result;
355 DWORD BytesToRead;
356
357 PVOID LocalBuffer;
358 BYTE StaticBuffer[1024];
359
360 /* Read the sectors */
361 Result = SeekDisk(DiskImage, Cylinder, Head, Sector);
362 if (Result != 0x00)
363 return Result;
364
365 BytesToRead = NumSectors * DiskImage->DiskInfo.SectorSize;
366
367 // FIXME: Consider just looping around filling each time the buffer...
368
369 if (BytesToRead <= sizeof(StaticBuffer))
370 {
371 LocalBuffer = StaticBuffer;
372 }
373 else
374 {
375 LocalBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToRead);
376 ASSERT(LocalBuffer != NULL);
377 }
378
379 if (ReadFile(DiskImage->hDisk, LocalBuffer, BytesToRead, &BytesToRead, NULL))
380 {
381 /* Write to the memory */
382 EmulatorWriteMemory(&EmulatorContext,
383 TO_LINEAR(getES(), getBX()),
384 LocalBuffer,
385 BytesToRead);
386
387 Result = 0x00;
388 }
389 else
390 {
391 Result = 0x04;
392 }
393
394 if (LocalBuffer != StaticBuffer)
395 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer);
396
397 /* Return success or error */
398 return Result;
399 }
400
401 BYTE
402 WriteDisk(IN PDISK_IMAGE DiskImage,
403 IN WORD Cylinder,
404 IN BYTE Head,
405 IN BYTE Sector,
406 IN BYTE NumSectors)
407 {
408 BYTE Result;
409 DWORD BytesToWrite;
410
411 PVOID LocalBuffer;
412 BYTE StaticBuffer[1024];
413
414 /* Check for write protection */
415 if (DiskImage->ReadOnly)
416 {
417 /* Return error */
418 return 0x03;
419 }
420
421 /* Write the sectors */
422 Result = SeekDisk(DiskImage, Cylinder, Head, Sector);
423 if (Result != 0x00)
424 return Result;
425
426 BytesToWrite = NumSectors * DiskImage->DiskInfo.SectorSize;
427
428 // FIXME: Consider just looping around filling each time the buffer...
429
430 if (BytesToWrite <= sizeof(StaticBuffer))
431 {
432 LocalBuffer = StaticBuffer;
433 }
434 else
435 {
436 LocalBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, BytesToWrite);
437 ASSERT(LocalBuffer != NULL);
438 }
439
440 /* Read from the memory */
441 EmulatorReadMemory(&EmulatorContext,
442 TO_LINEAR(getES(), getBX()),
443 LocalBuffer,
444 BytesToWrite);
445
446 if (WriteFile(DiskImage->hDisk, LocalBuffer, BytesToWrite, &BytesToWrite, NULL))
447 Result = 0x00;
448 else
449 Result = 0x04;
450
451 if (LocalBuffer != StaticBuffer)
452 RtlFreeHeap(RtlGetProcessHeap(), 0, LocalBuffer);
453
454 /* Return success or error */
455 return Result;
456 }
457
458 typedef BOOLEAN (*MOUNT_DISK_HANDLER)(IN PDISK_IMAGE DiskImage, IN HANDLE hFile);
459
460 typedef struct _DISK_MOUNT_INFO
461 {
462 PDISK_IMAGE DiskArray;
463 ULONG NumDisks;
464 MOUNT_DISK_HANDLER MountDiskHelper;
465 } DISK_MOUNT_INFO, *PDISK_MOUNT_INFO;
466
467 static DISK_MOUNT_INFO DiskMountInfo[MAX_DISK_TYPE] =
468 {
469 {XDCFloppyDrive, _ARRAYSIZE(XDCFloppyDrive), MountFDI},
470 {XDCHardDrive , _ARRAYSIZE(XDCHardDrive) , MountHDD},
471 };
472
473 PDISK_IMAGE
474 RetrieveDisk(IN DISK_TYPE DiskType,
475 IN ULONG DiskNumber)
476 {
477 ASSERT(DiskType < MAX_DISK_TYPE);
478
479 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks)
480 {
481 DisplayMessage(L"RetrieveDisk: Disk number %d:%d invalid.", DiskType, DiskNumber);
482 return NULL;
483 }
484
485 return &DiskMountInfo[DiskType].DiskArray[DiskNumber];
486 }
487
488 BOOLEAN
489 MountDisk(IN DISK_TYPE DiskType,
490 IN ULONG DiskNumber,
491 IN PCSTR FileName,
492 IN BOOLEAN ReadOnly)
493 {
494 BOOLEAN Success = FALSE;
495 PDISK_IMAGE DiskImage;
496 HANDLE hFile;
497
498 BY_HANDLE_FILE_INFORMATION FileInformation;
499
500 ASSERT(DiskType < MAX_DISK_TYPE);
501
502 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks)
503 {
504 DisplayMessage(L"MountDisk: Disk number %d:%d invalid.", DiskType, DiskNumber);
505 return FALSE;
506 }
507
508 DiskImage = &DiskMountInfo[DiskType].DiskArray[DiskNumber];
509 if (IsDiskPresent(DiskImage))
510 {
511 DPRINT1("MountDisk: Disk %d:%d:0x%p already in use, recycling...\n", DiskType, DiskNumber, DiskImage);
512 UnmountDisk(DiskType, DiskNumber);
513 }
514
515 /* Try to open the file */
516 SetLastError(0); // For debugging purposes
517 if (ReadOnly)
518 {
519 hFile = CreateFileA(FileName,
520 GENERIC_READ,
521 FILE_SHARE_READ,
522 NULL,
523 OPEN_EXISTING,
524 FILE_ATTRIBUTE_NORMAL,
525 NULL);
526 }
527 else
528 {
529 hFile = CreateFileA(FileName,
530 GENERIC_READ | GENERIC_WRITE,
531 0, // No sharing access
532 NULL,
533 OPEN_EXISTING,
534 FILE_ATTRIBUTE_NORMAL,
535 NULL);
536 }
537 DPRINT1("File '%s' opening %s ; GetLastError() = %u\n",
538 FileName, hFile != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
539
540 /* If we failed, bail out */
541 if (hFile == INVALID_HANDLE_VALUE)
542 {
543 DisplayMessage(L"MountDisk: Error when opening disk file '%S' (Error: %u).", FileName, GetLastError());
544 return FALSE;
545 }
546
547 /* OK, we have a handle to the file */
548
549 /*
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.
553 *
554 * Redundant checks
555 */
556 SetLastError(0);
557 if (!GetFileInformationByHandle(hFile, &FileInformation) &&
558 GetLastError() == ERROR_INVALID_FUNCTION)
559 {
560 /* Objects other than real files are not supported */
561 DisplayMessage(L"MountDisk: '%S' is not a valid disk file.", FileName);
562 goto Quit;
563 }
564 SetLastError(0);
565 if (GetFileSize(hFile, NULL) == INVALID_FILE_SIZE &&
566 GetLastError() == ERROR_INVALID_FUNCTION)
567 {
568 /* Objects other than real files are not supported */
569 DisplayMessage(L"MountDisk: '%S' is not a valid disk file.", FileName);
570 goto Quit;
571 }
572
573 /* Success, mount the image */
574 if (!DiskMountInfo[DiskType].MountDiskHelper(DiskImage, hFile))
575 {
576 DisplayMessage(L"MountDisk: Failed to mount disk file '%S' in 0x%p.", FileName, DiskImage);
577 goto Quit;
578 }
579
580 /* Update its read/write state */
581 DiskImage->ReadOnly = ReadOnly;
582
583 Success = TRUE;
584
585 Quit:
586 if (!Success) FileClose(hFile);
587 return Success;
588 }
589
590 BOOLEAN
591 UnmountDisk(IN DISK_TYPE DiskType,
592 IN ULONG DiskNumber)
593 {
594 PDISK_IMAGE DiskImage;
595
596 ASSERT(DiskType < MAX_DISK_TYPE);
597
598 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks)
599 {
600 DisplayMessage(L"UnmountDisk: Disk number %d:%d invalid.", DiskType, DiskNumber);
601 return FALSE;
602 }
603
604 DiskImage = &DiskMountInfo[DiskType].DiskArray[DiskNumber];
605 if (!IsDiskPresent(DiskImage))
606 {
607 DPRINT1("UnmountDisk: Disk %d:%d:0x%p is already unmounted\n", DiskType, DiskNumber, DiskImage);
608 return FALSE;
609 }
610
611 /* Flush the image and unmount it */
612 FlushFileBuffers(DiskImage->hDisk);
613 FileClose(DiskImage->hDisk);
614 DiskImage->hDisk = NULL;
615 return TRUE;
616 }
617
618
619 /* PUBLIC FUNCTIONS ***********************************************************/
620
621 BOOLEAN DiskCtrlInitialize(VOID)
622 {
623 return TRUE;
624 }
625
626 VOID DiskCtrlCleanup(VOID)
627 {
628 ULONG DiskNumber;
629
630 /* Unmount all the floppy disk drives */
631 for (DiskNumber = 0; DiskNumber < DiskMountInfo[FLOPPY_DISK].NumDisks; ++DiskNumber)
632 UnmountDisk(FLOPPY_DISK, DiskNumber);
633
634 /* Unmount all the hard disk drives */
635 for (DiskNumber = 0; DiskNumber < DiskMountInfo[HARD_DISK].NumDisks; ++DiskNumber)
636 UnmountDisk(HARD_DISK, DiskNumber);
637 }
638
639 /* EOF */