[FREELDR] Fix remaining hwdisk and FATX bugs (#1766)
[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
26 DBG_DEFAULT_CHANNEL(HWDETECT);
27
28 /*
29 * This is the common code for harddisk for both the PC and the XBOX.
30 */
31
32 typedef struct tagDISKCONTEXT
33 {
34 UCHAR DriveNumber;
35 ULONG SectorSize;
36 ULONGLONG SectorOffset;
37 ULONGLONG SectorCount;
38 ULONGLONG SectorNumber;
39 } DISKCONTEXT;
40
41 static const CHAR Hex[] = "0123456789abcdef";
42
43 /* Data cache for BIOS disks pre-enumeration */
44 UCHAR PcBiosDiskCount = 0;
45 static CHAR PcDiskIdentifier[32][20];
46
47 PVOID DiskReadBuffer;
48 SIZE_T DiskReadBufferSize;
49
50
51 /* FUNCTIONS *****************************************************************/
52
53 static ARC_STATUS
54 DiskClose(ULONG FileId)
55 {
56 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
57
58 FrLdrTempFree(Context, TAG_HW_DISK_CONTEXT);
59 return ESUCCESS;
60 }
61
62 static ARC_STATUS
63 DiskGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
64 {
65 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
66
67 RtlZeroMemory(Information, sizeof(FILEINFORMATION));
68 Information->EndingAddress.QuadPart = Context->SectorCount * Context->SectorSize;
69 Information->CurrentAddress.QuadPart = Context->SectorNumber * Context->SectorSize;
70
71 return ESUCCESS;
72 }
73
74 static ARC_STATUS
75 DiskOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
76 {
77 DISKCONTEXT* Context;
78 UCHAR DriveNumber;
79 ULONG DrivePartition, SectorSize;
80 ULONGLONG SectorOffset = 0;
81 ULONGLONG SectorCount = 0;
82 PARTITION_TABLE_ENTRY PartitionTableEntry;
83 CHAR FileName[1];
84
85 if (DiskReadBufferSize == 0)
86 {
87 ERR("DiskOpen(): DiskReadBufferSize is 0, something is wrong.\n");
88 ASSERT(FALSE);
89 return ENOMEM;
90 }
91
92 if (!DissectArcPath(Path, FileName, &DriveNumber, &DrivePartition))
93 return EINVAL;
94
95 if (DrivePartition == 0xff)
96 {
97 /* This is a CD-ROM device */
98 SectorSize = 2048;
99 }
100 else
101 {
102 /*
103 * This is either a floppy disk device (DrivePartition == 0) or
104 * a hard disk device (DrivePartition != 0 && DrivePartition != 0xFF)
105 * but it doesn't matter which one because they both have 512 bytes
106 * per sector.
107 */
108 SectorSize = 512;
109 }
110
111 if (DrivePartition != 0xff && DrivePartition != 0)
112 {
113 if (!DiskGetPartitionEntry(DriveNumber, DrivePartition, &PartitionTableEntry))
114 return EINVAL;
115
116 SectorOffset = PartitionTableEntry.SectorCountBeforePartition;
117 SectorCount = PartitionTableEntry.PartitionSectorCount;
118 }
119 #if 0 // FIXME: Investigate
120 else
121 {
122 SectorCount = 0; /* FIXME */
123 }
124 #endif
125
126 Context = FrLdrTempAlloc(sizeof(DISKCONTEXT), TAG_HW_DISK_CONTEXT);
127 if (!Context)
128 return ENOMEM;
129
130 Context->DriveNumber = DriveNumber;
131 Context->SectorSize = SectorSize;
132 Context->SectorOffset = SectorOffset;
133 Context->SectorCount = SectorCount;
134 Context->SectorNumber = 0;
135 FsSetDeviceSpecific(*FileId, Context);
136
137 return ESUCCESS;
138 }
139
140 static ARC_STATUS
141 DiskRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
142 {
143 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
144 UCHAR* Ptr = (UCHAR*)Buffer;
145 ULONG Length, TotalSectors, MaxSectors, ReadSectors;
146 BOOLEAN ret;
147 ULONGLONG SectorOffset;
148
149 ASSERT(DiskReadBufferSize > 0);
150
151 TotalSectors = (N + Context->SectorSize - 1) / Context->SectorSize;
152 MaxSectors = DiskReadBufferSize / Context->SectorSize;
153 SectorOffset = Context->SectorNumber + Context->SectorOffset;
154
155 // If MaxSectors is 0, this will lead to infinite loop
156 // In release builds assertions are disabled, however we also have sanity checks in DiskOpen()
157 ASSERT(MaxSectors > 0);
158
159 ret = TRUE;
160
161 while (TotalSectors)
162 {
163 ReadSectors = TotalSectors;
164 if (ReadSectors > MaxSectors)
165 ReadSectors = MaxSectors;
166
167 ret = MachDiskReadLogicalSectors(Context->DriveNumber,
168 SectorOffset,
169 ReadSectors,
170 DiskReadBuffer);
171 if (!ret)
172 break;
173
174 Length = ReadSectors * Context->SectorSize;
175 if (Length > N)
176 Length = N;
177
178 RtlCopyMemory(Ptr, DiskReadBuffer, Length);
179
180 Ptr += Length;
181 N -= Length;
182 SectorOffset += ReadSectors;
183 TotalSectors -= ReadSectors;
184 }
185
186 *Count = (ULONG)(Ptr - (UCHAR*)Buffer);
187
188 return (!ret) ? EIO : ESUCCESS;
189 }
190
191 static ARC_STATUS
192 DiskSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
193 {
194 DISKCONTEXT* Context = FsGetDeviceSpecific(FileId);
195
196 if (SeekMode != SeekAbsolute)
197 return EINVAL;
198 if (Position->LowPart & (Context->SectorSize - 1))
199 return EINVAL;
200
201 Context->SectorNumber = Position->QuadPart / Context->SectorSize;
202 return ESUCCESS;
203 }
204
205 static const DEVVTBL DiskVtbl =
206 {
207 DiskClose,
208 DiskGetFileInformation,
209 DiskOpen,
210 DiskRead,
211 DiskSeek,
212 };
213
214
215 PCHAR
216 GetHarddiskIdentifier(UCHAR DriveNumber)
217 {
218 return PcDiskIdentifier[DriveNumber - 0x80];
219 }
220
221 static VOID
222 GetHarddiskInformation(UCHAR DriveNumber)
223 {
224 PMASTER_BOOT_RECORD Mbr;
225 PULONG Buffer;
226 ULONG i;
227 ULONG Checksum;
228 ULONG Signature;
229 BOOLEAN ValidPartitionTable;
230 CHAR ArcName[MAX_PATH];
231 PARTITION_TABLE_ENTRY PartitionTableEntry;
232 PCHAR Identifier = PcDiskIdentifier[DriveNumber - 0x80];
233
234 /* Detect disk partition type */
235 DiskDetectPartitionType(DriveNumber);
236
237 /* Read the MBR */
238 if (!MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, DiskReadBuffer))
239 {
240 ERR("Reading MBR failed\n");
241 /* We failed, use a default identifier */
242 sprintf(Identifier, "BIOSDISK%d", DriveNumber - 0x80 + 1);
243 return;
244 }
245
246 Buffer = (ULONG*)DiskReadBuffer;
247 Mbr = (PMASTER_BOOT_RECORD)DiskReadBuffer;
248
249 Signature = Mbr->Signature;
250 TRACE("Signature: %x\n", Signature);
251
252 /* Calculate the MBR checksum */
253 Checksum = 0;
254 for (i = 0; i < 512 / sizeof(ULONG); i++)
255 {
256 Checksum += Buffer[i];
257 }
258 Checksum = ~Checksum + 1;
259 TRACE("Checksum: %x\n", Checksum);
260
261 ValidPartitionTable = (Mbr->MasterBootRecordMagic == 0xAA55);
262
263 /* Fill out the ARC disk block */
264 sprintf(ArcName, "multi(0)disk(0)rdisk(%u)", DriveNumber - 0x80);
265 AddReactOSArcDiskInfo(ArcName, Signature, Checksum, ValidPartitionTable);
266
267 sprintf(ArcName, "multi(0)disk(0)rdisk(%u)partition(0)", DriveNumber - 0x80);
268 FsRegisterDevice(ArcName, &DiskVtbl);
269
270 /* Add partitions */
271 i = 1;
272 DiskReportError(FALSE);
273 while (DiskGetPartitionEntry(DriveNumber, i, &PartitionTableEntry))
274 {
275 if (PartitionTableEntry.SystemIndicator != PARTITION_ENTRY_UNUSED)
276 {
277 sprintf(ArcName, "multi(0)disk(0)rdisk(%u)partition(%lu)", DriveNumber - 0x80, i);
278 FsRegisterDevice(ArcName, &DiskVtbl);
279 }
280 i++;
281 }
282 DiskReportError(TRUE);
283
284 /* Convert checksum and signature to identifier string */
285 Identifier[0] = Hex[(Checksum >> 28) & 0x0F];
286 Identifier[1] = Hex[(Checksum >> 24) & 0x0F];
287 Identifier[2] = Hex[(Checksum >> 20) & 0x0F];
288 Identifier[3] = Hex[(Checksum >> 16) & 0x0F];
289 Identifier[4] = Hex[(Checksum >> 12) & 0x0F];
290 Identifier[5] = Hex[(Checksum >> 8) & 0x0F];
291 Identifier[6] = Hex[(Checksum >> 4) & 0x0F];
292 Identifier[7] = Hex[Checksum & 0x0F];
293 Identifier[8] = '-';
294 Identifier[9] = Hex[(Signature >> 28) & 0x0F];
295 Identifier[10] = Hex[(Signature >> 24) & 0x0F];
296 Identifier[11] = Hex[(Signature >> 20) & 0x0F];
297 Identifier[12] = Hex[(Signature >> 16) & 0x0F];
298 Identifier[13] = Hex[(Signature >> 12) & 0x0F];
299 Identifier[14] = Hex[(Signature >> 8) & 0x0F];
300 Identifier[15] = Hex[(Signature >> 4) & 0x0F];
301 Identifier[16] = Hex[Signature & 0x0F];
302 Identifier[17] = '-';
303 Identifier[18] = (ValidPartitionTable ? 'A' : 'X');
304 Identifier[19] = 0;
305 TRACE("Identifier: %s\n", Identifier);
306 }
307
308 static UCHAR
309 EnumerateHarddisks(OUT PBOOLEAN BootDriveReported)
310 {
311 UCHAR DiskCount, DriveNumber;
312 ULONG i;
313 BOOLEAN Changed;
314
315 *BootDriveReported = FALSE;
316
317 /* Count the number of visible harddisk drives */
318 DiskReportError(FALSE);
319 DiskCount = 0;
320 DriveNumber = 0x80;
321
322 /*
323 * There are some really broken BIOSes out there. There are even BIOSes
324 * that happily report success when you ask them to read from non-existent
325 * harddisks. So, we set the buffer to known contents first, then try to
326 * read. If the BIOS reports success but the buffer contents haven't
327 * changed then we fail anyway.
328 */
329 memset(DiskReadBuffer, 0xcd, DiskReadBufferSize);
330 while (MachDiskReadLogicalSectors(DriveNumber, 0ULL, 1, DiskReadBuffer))
331 {
332 Changed = FALSE;
333 for (i = 0; !Changed && i < DiskReadBufferSize; i++)
334 {
335 Changed = ((PUCHAR)DiskReadBuffer)[i] != 0xcd;
336 }
337 if (!Changed)
338 {
339 TRACE("BIOS reports success for disk %d (0x%02X) but data didn't change\n",
340 (int)DiskCount, DriveNumber);
341 break;
342 }
343
344 /* Cache the BIOS hard disk information for later use */
345 GetHarddiskInformation(DriveNumber);
346
347 /* Check if we have seen the boot drive */
348 if (FrldrBootDrive == DriveNumber)
349 *BootDriveReported = TRUE;
350
351 DiskCount++;
352 DriveNumber++;
353 memset(DiskReadBuffer, 0xcd, DiskReadBufferSize);
354 }
355 DiskReportError(TRUE);
356
357 PcBiosDiskCount = DiskCount;
358 TRACE("BIOS reports %d harddisk%s\n",
359 (int)DiskCount, (DiskCount == 1) ? "" : "s");
360
361 return DiskCount;
362 }
363
364 BOOLEAN
365 PcInitializeBootDevices(VOID)
366 {
367 UCHAR DiskCount;
368 BOOLEAN BootDriveReported = FALSE;
369 ULONG i;
370 CHAR BootPath[MAX_PATH];
371
372 DiskCount = EnumerateHarddisks(&BootDriveReported);
373
374 /* Get the drive we're booting from */
375 MachDiskGetBootPath(BootPath, sizeof(BootPath));
376
377 /* Add it, if it's a floppy or cdrom */
378 if ((FrldrBootDrive >= 0x80 && !BootDriveReported) ||
379 DiskIsDriveRemovable(FrldrBootDrive))
380 {
381 /* TODO: Check if it's really a CDROM drive */
382
383 PMASTER_BOOT_RECORD Mbr;
384 PULONG Buffer;
385 ULONG Checksum = 0;
386 ULONG Signature;
387
388 /* Read the MBR */
389 if (!MachDiskReadLogicalSectors(FrldrBootDrive, 16ULL, 1, DiskReadBuffer))
390 {
391 ERR("Reading MBR failed\n");
392 return FALSE;
393 }
394
395 Buffer = (ULONG*)DiskReadBuffer;
396 Mbr = (PMASTER_BOOT_RECORD)DiskReadBuffer;
397
398 Signature = Mbr->Signature;
399 TRACE("Signature: %x\n", Signature);
400
401 /* Calculate the MBR checksum */
402 for (i = 0; i < 2048 / sizeof(ULONG); i++)
403 {
404 Checksum += Buffer[i];
405 }
406 Checksum = ~Checksum + 1;
407 TRACE("Checksum: %x\n", Checksum);
408
409 /* Fill out the ARC disk block */
410 AddReactOSArcDiskInfo(BootPath, Signature, Checksum, TRUE);
411
412 FsRegisterDevice(BootPath, &DiskVtbl);
413 DiskCount++; // This is not accounted for in the number of pre-enumerated BIOS drives!
414 TRACE("Additional boot drive detected: 0x%02X\n", (int)FrldrBootDrive);
415 }
416
417 return (DiskCount != 0);
418 }