[FREELDR] Advance the file pointers every time a read operation is performed, in...
[reactos.git] / boot / freeldr / freeldr / arch / i386 / hwdisk.c
1 /*
2 * FreeLoader
3 *
4 * Copyright (C) 2003, 2004 Eric Kohl
5 * Copyright (C) 2009 Hervé Poussineau
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include <freeldr.h>
23
24 #include <debug.h>
25 DBG_DEFAULT_CHANNEL(HWDETECT);
26
27 /*
28 * This is the common code for harddisk for both the PC and the XBOX.
29 */
30
31 typedef struct tagDISKCONTEXT
32 {
33 UCHAR DriveNumber;
34 ULONG SectorSize;
35 ULONGLONG SectorOffset;
36 ULONGLONG SectorCount;
37 ULONGLONG SectorNumber;
38 } DISKCONTEXT;
39
40 static const CHAR Hex[] = "0123456789abcdef";
41
42 /* Data cache for BIOS disks pre-enumeration */
43 UCHAR PcBiosDiskCount = 0;
44 static CHAR PcDiskIdentifier[32][20];
45
46 PVOID DiskReadBuffer;
47 SIZE_T DiskReadBufferSize;
48
49
50 /* FUNCTIONS *****************************************************************/
51
52 static ARC_STATUS
53 DiskClose(ULONG FileId)
54 {
55 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
56 FrLdrTempFree(Context, TAG_HW_DISK_CONTEXT);
57 return ESUCCESS;
58 }
59
60 static ARC_STATUS
61 DiskGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
62 {
63 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
64
65 RtlZeroMemory(Information, sizeof(*Information));
66
67 /*
68 * The ARC specification mentions that for partitions, StartingAddress and
69 * EndingAddress are the start and end positions of the partition in terms
70 * of byte offsets from the start of the disk.
71 * CurrentAddress is the current offset into (i.e. relative to) the partition.
72 */
73 Information->StartingAddress.QuadPart = Context->SectorOffset * Context->SectorSize;
74 Information->EndingAddress.QuadPart = (Context->SectorOffset + Context->SectorCount) * Context->SectorSize;
75 Information->CurrentAddress.QuadPart = Context->SectorNumber * Context->SectorSize;
76
77 return ESUCCESS;
78 }
79
80 static ARC_STATUS
81 DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
82 {
83 DISKCONTEXT* Context;
84 UCHAR DriveNumber;
85 ULONG DrivePartition, SectorSize;
86 ULONGLONG SectorOffset = 0;
87 ULONGLONG SectorCount = 0;
88 PARTITION_TABLE_ENTRY PartitionTableEntry;
89
90 if (DiskReadBufferSize == 0)
91 {
92 ERR("DiskOpen(): DiskReadBufferSize is 0, something is wrong.\n");
93 ASSERT(FALSE);
94 return ENOMEM;
95 }
96
97 if (!DissectArcPath(Path, NULL, &DriveNumber, &DrivePartition))
98 return EINVAL;
99
100 if (DrivePartition == 0xff)
101 {
102 /* This is a CD-ROM device */
103 SectorSize = 2048;
104 }
105 else
106 {
107 /*
108 * This is either a floppy disk device (DrivePartition == 0) or
109 * a hard disk device (DrivePartition != 0 && DrivePartition != 0xFF)
110 * but it doesn't matter which one because they both have 512 bytes
111 * per sector.
112 */
113 SectorSize = 512;
114 }
115
116 if (DrivePartition != 0xff && DrivePartition != 0)
117 {
118 if (!DiskGetPartitionEntry(DriveNumber, DrivePartition, &PartitionTableEntry))
119 return EINVAL;
120
121 SectorOffset = PartitionTableEntry.SectorCountBeforePartition;
122 SectorCount = PartitionTableEntry.PartitionSectorCount;
123 }
124 else
125 {
126 GEOMETRY Geometry;
127 if (!MachDiskGetDriveGeometry(DriveNumber, &Geometry))
128 return EINVAL;
129
130 if (SectorSize != Geometry.BytesPerSector)
131 {
132 ERR("SectorSize (%lu) != Geometry.BytesPerSector (%lu), expect problems!\n",
133 SectorSize, Geometry.BytesPerSector);
134 }
135
136 SectorOffset = 0;
137 SectorCount = (ULONGLONG)Geometry.Cylinders * Geometry.Heads * Geometry.Sectors;
138 }
139
140 Context = FrLdrTempAlloc(sizeof(DISKCONTEXT), TAG_HW_DISK_CONTEXT);
141 if (!Context)
142 return ENOMEM;
143
144 Context->DriveNumber = DriveNumber;
145 Context->SectorSize = SectorSize;
146 Context->SectorOffset = SectorOffset;
147 Context->SectorCount = SectorCount;
148 Context->SectorNumber = 0;
149 FsSetDeviceSpecific(*FileId, Context);
150
151 return ESUCCESS;
152 }
153
154 static ARC_STATUS
155 DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
156 {
157 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
158 UCHAR* Ptr = (UCHAR*)Buffer;
159 ULONG Length, TotalSectors, MaxSectors, ReadSectors;
160 ULONGLONG SectorOffset;
161 BOOLEAN ret;
162
163 ASSERT(DiskReadBufferSize > 0);
164
165 TotalSectors = (N + Context->SectorSize - 1) / Context->SectorSize;
166 MaxSectors = DiskReadBufferSize / Context->SectorSize;
167 SectorOffset = Context->SectorOffset + Context->SectorNumber;
168
169 // If MaxSectors is 0, this will lead to infinite loop.
170 // In release builds assertions are disabled, however we also have sanity checks in DiskOpen()
171 ASSERT(MaxSectors > 0);
172
173 ret = TRUE;
174
175 while (TotalSectors)
176 {
177 ReadSectors = TotalSectors;
178 if (ReadSectors > MaxSectors)
179 ReadSectors = MaxSectors;
180
181 ret = MachDiskReadLogicalSectors(Context->DriveNumber,
182 SectorOffset,
183 ReadSectors,
184 DiskReadBuffer);
185 if (!ret)
186 break;
187
188 Length = ReadSectors * Context->SectorSize;
189 if (Length > N)
190 Length = N;
191
192 RtlCopyMemory(Ptr, DiskReadBuffer, Length);
193
194 Ptr += Length;
195 N -= Length;
196 SectorOffset += ReadSectors;
197 TotalSectors -= ReadSectors;
198 }
199
200 *Count = (ULONG)((ULONG_PTR)Ptr - (ULONG_PTR)Buffer);
201 Context->SectorNumber = SectorOffset - Context->SectorOffset;
202
203 return (!ret) ? EIO : ESUCCESS;
204 }
205
206 static ARC_STATUS
207 DiskSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
208 {
209 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
210 LARGE_INTEGER NewPosition = *Position;
211
212 switch (SeekMode)
213 {
214 case SeekAbsolute:
215 break;
216 case SeekRelative:
217 NewPosition.QuadPart += (Context->SectorNumber * Context->SectorSize);
218 break;
219 default:
220 ASSERT(FALSE);
221 return EINVAL;
222 }
223
224 if (NewPosition.QuadPart & (Context->SectorSize - 1))
225 return EINVAL;
226
227 /* Convert in number of sectors */
228 NewPosition.QuadPart /= Context->SectorSize;
229
230 /* HACK: CDROMs may have a SectorCount of 0 */
231 if (Context->SectorCount != 0 && NewPosition.QuadPart >= Context->SectorCount)
232 return EINVAL;
233
234 Context->SectorNumber = NewPosition.QuadPart;
235 return ESUCCESS;
236 }
237
238 static const DEVVTBL DiskVtbl =
239 {
240 DiskClose,
241 DiskGetFileInformation,
242 DiskOpen,
243 DiskRead,
244 DiskSeek,
245 };
246
247
248 PCHAR
249 GetHarddiskIdentifier(UCHAR DriveNumber)
250 {
251 return PcDiskIdentifier[DriveNumber - 0x80];
252 }
253
254 static VOID
255 GetHarddiskInformation(UCHAR DriveNumber)
256 {
257 PMASTER_BOOT_RECORD Mbr;
258 PULONG Buffer;
259 ULONG i;
260 ULONG Checksum;
261 ULONG Signature;
262 BOOLEAN ValidPartitionTable;
263 CHAR ArcName[MAX_PATH];
264 PARTITION_TABLE_ENTRY PartitionTableEntry;
265 PCHAR Identifier = PcDiskIdentifier[DriveNumber - 0x80];
266
267 /* Detect disk partition type */
268 DiskDetectPartitionType(DriveNumber);
269
270 /* Read the MBR */
271 if (!MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, DiskReadBuffer))
272 {
273 ERR("Reading MBR failed\n");
274 /* We failed, use a default identifier */
275 sprintf(Identifier, "BIOSDISK%d", DriveNumber - 0x80 + 1);
276 return;
277 }
278
279 Buffer = (ULONG*)DiskReadBuffer;
280 Mbr = (PMASTER_BOOT_RECORD)DiskReadBuffer;
281
282 Signature = Mbr->Signature;
283 TRACE("Signature: %x\n", Signature);
284
285 /* Calculate the MBR checksum */
286 Checksum = 0;
287 for (i = 0; i < 512 / sizeof(ULONG); i++)
288 {
289 Checksum += Buffer[i];
290 }
291 Checksum = ~Checksum + 1;
292 TRACE("Checksum: %x\n", Checksum);
293
294 ValidPartitionTable = (Mbr->MasterBootRecordMagic == 0xAA55);
295
296 /* Fill out the ARC disk block */
297 sprintf(ArcName, "multi(0)disk(0)rdisk(%u)", DriveNumber - 0x80);
298 AddReactOSArcDiskInfo(ArcName, Signature, Checksum, ValidPartitionTable);
299
300 sprintf(ArcName, "multi(0)disk(0)rdisk(%u)partition(0)", DriveNumber - 0x80);
301 FsRegisterDevice(ArcName, &DiskVtbl);
302
303 /* Add partitions */
304 i = 1;
305 DiskReportError(FALSE);
306 while (DiskGetPartitionEntry(DriveNumber, i, &PartitionTableEntry))
307 {
308 if (PartitionTableEntry.SystemIndicator != PARTITION_ENTRY_UNUSED)
309 {
310 sprintf(ArcName, "multi(0)disk(0)rdisk(%u)partition(%lu)", DriveNumber - 0x80, i);
311 FsRegisterDevice(ArcName, &DiskVtbl);
312 }
313 i++;
314 }
315 DiskReportError(TRUE);
316
317 /* Convert checksum and signature to identifier string */
318 Identifier[0] = Hex[(Checksum >> 28) & 0x0F];
319 Identifier[1] = Hex[(Checksum >> 24) & 0x0F];
320 Identifier[2] = Hex[(Checksum >> 20) & 0x0F];
321 Identifier[3] = Hex[(Checksum >> 16) & 0x0F];
322 Identifier[4] = Hex[(Checksum >> 12) & 0x0F];
323 Identifier[5] = Hex[(Checksum >> 8) & 0x0F];
324 Identifier[6] = Hex[(Checksum >> 4) & 0x0F];
325 Identifier[7] = Hex[Checksum & 0x0F];
326 Identifier[8] = '-';
327 Identifier[9] = Hex[(Signature >> 28) & 0x0F];
328 Identifier[10] = Hex[(Signature >> 24) & 0x0F];
329 Identifier[11] = Hex[(Signature >> 20) & 0x0F];
330 Identifier[12] = Hex[(Signature >> 16) & 0x0F];
331 Identifier[13] = Hex[(Signature >> 12) & 0x0F];
332 Identifier[14] = Hex[(Signature >> 8) & 0x0F];
333 Identifier[15] = Hex[(Signature >> 4) & 0x0F];
334 Identifier[16] = Hex[Signature & 0x0F];
335 Identifier[17] = '-';
336 Identifier[18] = (ValidPartitionTable ? 'A' : 'X');
337 Identifier[19] = 0;
338 TRACE("Identifier: %s\n", Identifier);
339 }
340
341 static UCHAR
342 EnumerateHarddisks(OUT PBOOLEAN BootDriveReported)
343 {
344 UCHAR DiskCount, DriveNumber;
345 ULONG i;
346 BOOLEAN Changed;
347
348 *BootDriveReported = FALSE;
349
350 /* Count the number of visible harddisk drives */
351 DiskReportError(FALSE);
352 DiskCount = 0;
353 DriveNumber = 0x80;
354
355 ASSERT(DiskReadBufferSize > 0);
356
357 /*
358 * There are some really broken BIOSes out there. There are even BIOSes
359 * that happily report success when you ask them to read from non-existent
360 * harddisks. So, we set the buffer to known contents first, then try to
361 * read. If the BIOS reports success but the buffer contents haven't
362 * changed then we fail anyway.
363 */
364 memset(DiskReadBuffer, 0xcd, DiskReadBufferSize);
365 while (MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, DiskReadBuffer))
366 {
367 Changed = FALSE;
368 for (i = 0; !Changed && i < DiskReadBufferSize; i++)
369 {
370 Changed = ((PUCHAR)DiskReadBuffer)[i] != 0xcd;
371 }
372 if (!Changed)
373 {
374 TRACE("BIOS reports success for disk %d (0x%02X) but data didn't change\n",
375 (int)DiskCount, DriveNumber);
376 break;
377 }
378
379 /* Cache the BIOS hard disk information for later use */
380 GetHarddiskInformation(DriveNumber);
381
382 /* Check if we have seen the boot drive */
383 if (FrldrBootDrive == DriveNumber)
384 *BootDriveReported = TRUE;
385
386 DiskCount++;
387 DriveNumber++;
388 memset(DiskReadBuffer, 0xcd, DiskReadBufferSize);
389 }
390 DiskReportError(TRUE);
391
392 PcBiosDiskCount = DiskCount;
393 TRACE("BIOS reports %d harddisk%s\n",
394 (int)DiskCount, (DiskCount == 1) ? "" : "s");
395
396 return DiskCount;
397 }
398
399 static BOOLEAN
400 DiskIsDriveRemovable(UCHAR DriveNumber)
401 {
402 /*
403 * Hard disks use drive numbers >= 0x80 . So if the drive number
404 * indicates a hard disk then return FALSE.
405 * 0x49 is our magic ramdisk drive, so return FALSE for that too.
406 */
407 if ((DriveNumber >= 0x80) || (DriveNumber == 0x49))
408 return FALSE;
409
410 /* The drive is a floppy diskette so return TRUE */
411 return TRUE;
412 }
413
414 static BOOLEAN
415 DiskGetBootPath(BOOLEAN IsPxe)
416 {
417 if (*FrLdrBootPath)
418 return TRUE;
419
420 // FIXME! FIXME! Do this in some drive recognition procedure!!!!
421 if (IsPxe)
422 {
423 RtlStringCbCopyA(FrLdrBootPath, sizeof(FrLdrBootPath), "net(0)");
424 }
425 else
426 /* 0x49 is our magic ramdisk drive, so try to detect it first */
427 if (FrldrBootDrive == 0x49)
428 {
429 /* This is the ramdisk. See ArmInitializeBootDevices() too... */
430 // RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath), "ramdisk(%u)", 0);
431 RtlStringCbCopyA(FrLdrBootPath, sizeof(FrLdrBootPath), "ramdisk(0)");
432 }
433 else if (FrldrBootDrive < 0x80)
434 {
435 /* This is a floppy */
436 RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath),
437 "multi(0)disk(0)fdisk(%u)", FrldrBootDrive);
438 }
439 else if (FrldrBootPartition == 0xFF)
440 {
441 /* Boot Partition 0xFF is the magic value that indicates booting from CD-ROM (see isoboot.S) */
442 RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath),
443 "multi(0)disk(0)cdrom(%u)", FrldrBootDrive - 0x80);
444 }
445 else
446 {
447 ULONG BootPartition;
448 PARTITION_TABLE_ENTRY PartitionEntry;
449
450 /* This is a hard disk */
451 if (!DiskGetBootPartitionEntry(FrldrBootDrive, &PartitionEntry, &BootPartition))
452 {
453 ERR("Failed to get boot partition entry\n");
454 return FALSE;
455 }
456
457 FrldrBootPartition = BootPartition;
458
459 RtlStringCbPrintfA(FrLdrBootPath, sizeof(FrLdrBootPath),
460 "multi(0)disk(0)rdisk(%u)partition(%lu)",
461 FrldrBootDrive - 0x80, FrldrBootPartition);
462 }
463
464 return TRUE;
465 }
466
467 BOOLEAN
468 PcInitializeBootDevices(VOID)
469 {
470 UCHAR DiskCount;
471 BOOLEAN BootDriveReported = FALSE;
472 ULONG i;
473
474 DiskCount = EnumerateHarddisks(&BootDriveReported);
475
476 /* Initialize FrLdrBootPath, the boot path we're booting from (the "SystemPartition") */
477 DiskGetBootPath(PxeInit());
478
479 /* Add it, if it's a floppy or cdrom */
480 if ((FrldrBootDrive >= 0x80 && !BootDriveReported) ||
481 DiskIsDriveRemovable(FrldrBootDrive))
482 {
483 /* TODO: Check if it's really a CDROM drive */
484
485 PMASTER_BOOT_RECORD Mbr;
486 PULONG Buffer;
487 ULONG Checksum = 0;
488 ULONG Signature;
489
490 /* Read the MBR */
491 if (!MachDiskReadLogicalSectors(FrldrBootDrive, 16ULL, 1, DiskReadBuffer))
492 {
493 ERR("Reading MBR failed\n");
494 return FALSE;
495 }
496
497 Buffer = (ULONG*)DiskReadBuffer;
498 Mbr = (PMASTER_BOOT_RECORD)DiskReadBuffer;
499
500 Signature = Mbr->Signature;
501 TRACE("Signature: %x\n", Signature);
502
503 /* Calculate the MBR checksum */
504 for (i = 0; i < 2048 / sizeof(ULONG); i++)
505 {
506 Checksum += Buffer[i];
507 }
508 Checksum = ~Checksum + 1;
509 TRACE("Checksum: %x\n", Checksum);
510
511 /* Fill out the ARC disk block */
512 AddReactOSArcDiskInfo(FrLdrBootPath, Signature, Checksum, TRUE);
513
514 FsRegisterDevice(FrLdrBootPath, &DiskVtbl);
515 DiskCount++; // This is not accounted for in the number of pre-enumerated BIOS drives!
516 TRACE("Additional boot drive detected: 0x%02X\n", (int)FrldrBootDrive);
517 }
518
519 return (DiskCount != 0);
520 }