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