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 static BOOLEAN
MountFDI(IN PDISK_IMAGE DiskImage,IN HANDLE hFile)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 static BOOLEAN
MountHDD(IN PDISK_IMAGE DiskImage,IN HANDLE hFile)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
IsDiskPresent(IN PDISK_IMAGE DiskImage)321 IsDiskPresent(IN PDISK_IMAGE DiskImage)
322 {
323 ASSERT(DiskImage);
324 return (DiskImage->hDisk != INVALID_HANDLE_VALUE && DiskImage->hDisk != NULL);
325 }
326
327 BYTE
SeekDisk(IN PDISK_IMAGE DiskImage,IN WORD Cylinder,IN BYTE Head,IN BYTE Sector)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
ReadDisk(IN PDISK_IMAGE DiskImage,IN WORD Cylinder,IN BYTE Head,IN BYTE Sector,IN BYTE NumSectors)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
WriteDisk(IN PDISK_IMAGE DiskImage,IN WORD Cylinder,IN BYTE Head,IN BYTE Sector,IN BYTE NumSectors)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
RetrieveDisk(IN DISK_TYPE DiskType,IN ULONG DiskNumber)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
MountDisk(IN DISK_TYPE DiskType,IN ULONG DiskNumber,IN PCWSTR FileName,IN BOOLEAN ReadOnly)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 hFile = CreateFileW(FileName,
529 GENERIC_READ | (ReadOnly ? 0 : GENERIC_WRITE),
530 (ReadOnly ? FILE_SHARE_READ : 0),
531 NULL,
532 OPEN_EXISTING,
533 FILE_ATTRIBUTE_NORMAL,
534 NULL);
535 DPRINT1("File '%S' opening %s ; GetLastError() = %u\n",
536 FileName, hFile != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
537
538 /* If we failed, bail out */
539 if (hFile == INVALID_HANDLE_VALUE)
540 {
541 DisplayMessage(L"MountDisk: Error when opening disk file '%s' (Error: %u).", FileName, GetLastError());
542 return FALSE;
543 }
544
545 /* OK, we have a handle to the file */
546
547 /*
548 * Check that it is really a file, and not a physical drive.
549 * For obvious security reasons, we do not want to be able to
550 * write directly to physical drives.
551 *
552 * Redundant checks
553 */
554 SetLastError(0);
555 if (!GetFileInformationByHandle(hFile, &FileInformation) &&
556 GetLastError() == ERROR_INVALID_FUNCTION)
557 {
558 /* Objects other than real files are not supported */
559 DisplayMessage(L"MountDisk: '%s' is not a valid disk file.", FileName);
560 goto Quit;
561 }
562 SetLastError(0);
563 if (GetFileSize(hFile, NULL) == INVALID_FILE_SIZE &&
564 GetLastError() == ERROR_INVALID_FUNCTION)
565 {
566 /* Objects other than real files are not supported */
567 DisplayMessage(L"MountDisk: '%s' is not a valid disk file.", FileName);
568 goto Quit;
569 }
570
571 /* Success, mount the image */
572 if (!DiskMountInfo[DiskType].MountDiskHelper(DiskImage, hFile))
573 {
574 DisplayMessage(L"MountDisk: Failed to mount disk file '%s' in 0x%p.", FileName, DiskImage);
575 goto Quit;
576 }
577
578 /* Update its read/write state */
579 DiskImage->ReadOnly = ReadOnly;
580
581 Success = TRUE;
582
583 Quit:
584 if (!Success) FileClose(hFile);
585 return Success;
586 }
587
588 BOOLEAN
UnmountDisk(IN DISK_TYPE DiskType,IN ULONG DiskNumber)589 UnmountDisk(IN DISK_TYPE DiskType,
590 IN ULONG DiskNumber)
591 {
592 PDISK_IMAGE DiskImage;
593
594 ASSERT(DiskType < MAX_DISK_TYPE);
595
596 if (DiskNumber >= DiskMountInfo[DiskType].NumDisks)
597 {
598 DisplayMessage(L"UnmountDisk: Disk number %d:%d invalid.", DiskType, DiskNumber);
599 return FALSE;
600 }
601
602 DiskImage = &DiskMountInfo[DiskType].DiskArray[DiskNumber];
603 if (!IsDiskPresent(DiskImage))
604 {
605 DPRINT1("UnmountDisk: Disk %d:%d:0x%p is already unmounted\n", DiskType, DiskNumber, DiskImage);
606 return FALSE;
607 }
608
609 /* Flush the image and unmount it */
610 FlushFileBuffers(DiskImage->hDisk);
611 FileClose(DiskImage->hDisk);
612 DiskImage->hDisk = NULL;
613 return TRUE;
614 }
615
616
617 /* PUBLIC FUNCTIONS ***********************************************************/
618
DiskCtrlInitialize(VOID)619 BOOLEAN DiskCtrlInitialize(VOID)
620 {
621 return TRUE;
622 }
623
DiskCtrlCleanup(VOID)624 VOID DiskCtrlCleanup(VOID)
625 {
626 ULONG DiskNumber;
627
628 /* Unmount all the present floppy disk drives */
629 for (DiskNumber = 0; DiskNumber < DiskMountInfo[FLOPPY_DISK].NumDisks; ++DiskNumber)
630 {
631 if (IsDiskPresent(&DiskMountInfo[FLOPPY_DISK].DiskArray[DiskNumber]))
632 UnmountDisk(FLOPPY_DISK, DiskNumber);
633 }
634
635 /* Unmount all the present hard disk drives */
636 for (DiskNumber = 0; DiskNumber < DiskMountInfo[HARD_DISK].NumDisks; ++DiskNumber)
637 {
638 if (IsDiskPresent(&DiskMountInfo[HARD_DISK].DiskArray[DiskNumber]))
639 UnmountDisk(HARD_DISK, DiskNumber);
640 }
641 }
642
643 /* EOF */
644