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