Bring back ext2 code from branch
[reactos.git] / reactos / boot / freeldr / freeldr / arch / i386 / pcdisk.c
1 /*
2 * FreeLoader
3 * Copyright (C) 1998-2003 Brian Palmer <brianp@sginet.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <freeldr.h>
21
22 #define NDEBUG
23 #include <debug.h>
24
25 typedef struct
26 {
27 UCHAR PacketSize; // 00h - Size of packet (10h or 18h)
28 UCHAR Reserved; // 01h - Reserved (0)
29 USHORT LBABlockCount; // 02h - Number of blocks to transfer (max 007Fh for Phoenix EDD)
30 USHORT TransferBufferOffset; // 04h - Transfer buffer offset (seg:off)
31 USHORT TransferBufferSegment; // Transfer buffer segment (seg:off)
32 ULONGLONG LBAStartBlock; // 08h - Starting absolute block number
33 //ULONGLONG TransferBuffer64; // 10h - (EDD-3.0, optional) 64-bit flat address of transfer buffer
34 // used if DWORD at 04h is FFFFh:FFFFh
35 // Commented since some earlier BIOSes refuse to work with
36 // such extended structure
37 } PACKED I386_DISK_ADDRESS_PACKET, *PI386_DISK_ADDRESS_PACKET;
38
39 /////////////////////////////////////////////////////////////////////////////////////////////
40 // FUNCTIONS
41 /////////////////////////////////////////////////////////////////////////////////////////////
42
43 static BOOLEAN PcDiskResetController(ULONG DriveNumber)
44 {
45 REGS RegsIn;
46 REGS RegsOut;
47
48 DbgPrint((DPRINT_DISK, "PcDiskResetController(0x%x) DISK OPERATION FAILED -- RESETTING CONTROLLER\n", DriveNumber));
49
50 // BIOS Int 13h, function 0 - Reset disk system
51 // AH = 00h
52 // DL = drive (if bit 7 is set both hard disks and floppy disks reset)
53 // Return:
54 // AH = status
55 // CF clear if successful
56 // CF set on error
57 RegsIn.b.ah = 0x00;
58 RegsIn.b.dl = DriveNumber;
59
60 // Reset the disk controller
61 Int386(0x13, &RegsIn, &RegsOut);
62
63 return INT386_SUCCESS(RegsOut);
64 }
65
66 static BOOLEAN PcDiskReadLogicalSectorsLBA(ULONG DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
67 {
68 REGS RegsIn;
69 REGS RegsOut;
70 ULONG RetryCount;
71 PI386_DISK_ADDRESS_PACKET Packet = (PI386_DISK_ADDRESS_PACKET)(BIOSCALLBUFFER);
72
73 DbgPrint((DPRINT_DISK, "PcDiskReadLogicalSectorsLBA() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n", DriveNumber, SectorNumber, SectorCount, Buffer));
74
75 // BIOS int 0x13, function 42h - IBM/MS INT 13 Extensions - EXTENDED READ
76 RegsIn.b.ah = 0x42; // Subfunction 42h
77 RegsIn.b.dl = DriveNumber; // Drive number in DL (0 - floppy, 0x80 - harddisk)
78 RegsIn.x.ds = BIOSCALLBUFSEGMENT; // DS:SI -> disk address packet
79 RegsIn.w.si = BIOSCALLBUFOFFSET;
80
81 // Setup disk address packet
82 RtlZeroMemory(Packet, sizeof(I386_DISK_ADDRESS_PACKET));
83 Packet->PacketSize = sizeof(I386_DISK_ADDRESS_PACKET);
84 Packet->Reserved = 0;
85 Packet->LBABlockCount = SectorCount;
86 Packet->TransferBufferOffset = ((ULONG)Buffer) & 0x0F;
87 Packet->TransferBufferSegment = ((ULONG)Buffer) >> 4;
88 Packet->LBAStartBlock = SectorNumber;
89
90 // BIOS int 0x13, function 42h - IBM/MS INT 13 Extensions - EXTENDED READ
91 // Return:
92 // CF clear if successful
93 // AH = 00h
94 // CF set on error
95 // AH = error code
96 // disk address packet's block count field set to the
97 // number of blocks successfully transferred
98
99 // Retry 3 times
100 for (RetryCount=0; RetryCount<3; RetryCount++)
101 {
102 Int386(0x13, &RegsIn, &RegsOut);
103
104 // If it worked return TRUE
105 if (INT386_SUCCESS(RegsOut))
106 {
107 return TRUE;
108 }
109 // If it was a corrected ECC error then the data is still good
110 else if (RegsOut.b.ah == 0x11)
111 {
112 return TRUE;
113 }
114 // If it failed the do the next retry
115 else
116 {
117 PcDiskResetController(DriveNumber);
118
119 continue;
120 }
121 }
122
123 // If we get here then the read failed
124 DiskError("Disk Read Failed in LBA mode", RegsOut.b.ah);
125
126 return FALSE;
127 }
128
129 static BOOLEAN PcDiskReadLogicalSectorsCHS(ULONG DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
130 {
131 ULONG PhysicalSector;
132 ULONG PhysicalHead;
133 ULONG PhysicalTrack;
134 GEOMETRY DriveGeometry;
135 ULONG NumberOfSectorsToRead;
136 REGS RegsIn;
137 REGS RegsOut;
138 ULONG RetryCount;
139
140 DbgPrint((DPRINT_DISK, "PcDiskReadLogicalSectorsCHS()\n"));
141
142 //
143 // Get the drive geometry
144 //
145 if (!MachDiskGetDriveGeometry(DriveNumber, &DriveGeometry) ||
146 DriveGeometry.Sectors == 0 ||
147 DriveGeometry.Heads == 0)
148 {
149 return FALSE;
150 }
151
152 while (SectorCount)
153 {
154
155 //
156 // Calculate the physical disk offsets
157 //
158 PhysicalSector = 1 + (SectorNumber % DriveGeometry.Sectors);
159 PhysicalHead = (SectorNumber / DriveGeometry.Sectors) % DriveGeometry.Heads;
160 PhysicalTrack = (SectorNumber / DriveGeometry.Sectors) / DriveGeometry.Heads;
161
162 //
163 // Calculate how many sectors we need to read this round
164 //
165 if (PhysicalSector > 1)
166 {
167 if (SectorCount >= (DriveGeometry.Sectors - (PhysicalSector - 1)))
168 NumberOfSectorsToRead = (DriveGeometry.Sectors - (PhysicalSector - 1));
169 else
170 NumberOfSectorsToRead = SectorCount;
171 }
172 else
173 {
174 if (SectorCount >= DriveGeometry.Sectors)
175 NumberOfSectorsToRead = DriveGeometry.Sectors;
176 else
177 NumberOfSectorsToRead = SectorCount;
178 }
179
180 //
181 // Make sure the read is within the geometry boundaries
182 //
183 if ((PhysicalHead >= DriveGeometry.Heads) ||
184 (PhysicalTrack >= DriveGeometry.Cylinders) ||
185 ((NumberOfSectorsToRead + PhysicalSector) > (DriveGeometry.Sectors + 1)) ||
186 (PhysicalSector > DriveGeometry.Sectors))
187 {
188 DiskError("Disk read exceeds drive geometry limits.", 0);
189 return FALSE;
190 }
191
192 // BIOS Int 13h, function 2 - Read Disk Sectors
193 // AH = 02h
194 // AL = number of sectors to read (must be nonzero)
195 // CH = low eight bits of cylinder number
196 // CL = sector number 1-63 (bits 0-5)
197 // high two bits of cylinder (bits 6-7, hard disk only)
198 // DH = head number
199 // DL = drive number (bit 7 set for hard disk)
200 // ES:BX -> data buffer
201 // Return:
202 // CF set on error
203 // if AH = 11h (corrected ECC error), AL = burst length
204 // CF clear if successful
205 // AH = status
206 // AL = number of sectors transferred
207 // (only valid if CF set for some BIOSes)
208 RegsIn.b.ah = 0x02;
209 RegsIn.b.al = NumberOfSectorsToRead;
210 RegsIn.b.ch = (PhysicalTrack & 0xFF);
211 RegsIn.b.cl = (PhysicalSector + ((PhysicalTrack & 0x300) >> 2));
212 RegsIn.b.dh = PhysicalHead;
213 RegsIn.b.dl = DriveNumber;
214 RegsIn.w.es = ((ULONG)Buffer) >> 4;
215 RegsIn.w.bx = ((ULONG)Buffer) & 0x0F;
216
217 //
218 // Perform the read
219 // Retry 3 times
220 //
221 for (RetryCount=0; RetryCount<3; RetryCount++)
222 {
223 Int386(0x13, &RegsIn, &RegsOut);
224
225 // If it worked break out
226 if (INT386_SUCCESS(RegsOut))
227 {
228 break;
229 }
230 // If it was a corrected ECC error then the data is still good
231 else if (RegsOut.b.ah == 0x11)
232 {
233 break;
234 }
235 // If it failed the do the next retry
236 else
237 {
238 PcDiskResetController(DriveNumber);
239
240 continue;
241 }
242 }
243
244 // If we retried 3 times then fail
245 if (RetryCount >= 3)
246 {
247 DiskError("Disk Read Failed in CHS mode, after retrying 3 times", RegsOut.b.ah);
248 return FALSE;
249 }
250
251 // I have learned that not all bioses return
252 // the sector read count in the AL register (at least mine doesn't)
253 // even if the sectors were read correctly. So instead
254 // of checking the sector read count we will rely solely
255 // on the carry flag being set on error
256
257 Buffer = (PVOID)((ULONG_PTR)Buffer + (NumberOfSectorsToRead * DriveGeometry.BytesPerSector));
258 SectorCount -= NumberOfSectorsToRead;
259 SectorNumber += NumberOfSectorsToRead;
260 }
261
262 return TRUE;
263 }
264
265 static BOOLEAN PcDiskInt13ExtensionsSupported(ULONG DriveNumber)
266 {
267 static ULONG LastDriveNumber = 0xffffffff;
268 static BOOLEAN LastSupported;
269 REGS RegsIn;
270 REGS RegsOut;
271
272 DbgPrint((DPRINT_DISK, "PcDiskInt13ExtensionsSupported()\n"));
273
274 if (DriveNumber == LastDriveNumber)
275 {
276 DbgPrint((DPRINT_DISK, "Using cached value %s for drive 0x%x\n", LastSupported ? "TRUE" : "FALSE", DriveNumber));
277 return LastSupported;
278 }
279
280 // Some BIOSes report that extended disk access functions are not supported
281 // when booting from a CD (e.g. Phoenix BIOS v6.00PG and Insyde BIOS shipping
282 // with Intel Macs). Therefore we just return TRUE if we're booting from a CD -
283 // we can assume that all El Torito capable BIOSes support INT 13 extensions.
284 // We simply detect whether we're booting from CD by checking whether the drive
285 // number is >= 0x90. It's 0x90 on the Insyde BIOS, and 0x9F on most other BIOSes.
286 if (DriveNumber >= 0x90)
287 {
288 LastSupported = TRUE;
289 return TRUE;
290 }
291
292 LastDriveNumber = DriveNumber;
293
294 // IBM/MS INT 13 Extensions - INSTALLATION CHECK
295 // AH = 41h
296 // BX = 55AAh
297 // DL = drive (80h-FFh)
298 // Return:
299 // CF set on error (extensions not supported)
300 // AH = 01h (invalid function)
301 // CF clear if successful
302 // BX = AA55h if installed
303 // AH = major version of extensions
304 // 01h = 1.x
305 // 20h = 2.0 / EDD-1.0
306 // 21h = 2.1 / EDD-1.1
307 // 30h = EDD-3.0
308 // AL = internal use
309 // CX = API subset support bitmap
310 // DH = extension version (v2.0+ ??? -- not present in 1.x)
311 //
312 // Bitfields for IBM/MS INT 13 Extensions API support bitmap
313 // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
314 // Bit 1, removable drive controller functions (AH=45h,46h,48h,49h,INT 15/AH=52h) supported
315 // Bit 2, enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported
316 // extended drive parameter table is valid
317 // Bits 3-15 reserved
318 RegsIn.b.ah = 0x41;
319 RegsIn.w.bx = 0x55AA;
320 RegsIn.b.dl = DriveNumber;
321
322 // Reset the disk controller
323 Int386(0x13, &RegsIn, &RegsOut);
324
325 if (!INT386_SUCCESS(RegsOut))
326 {
327 // CF set on error (extensions not supported)
328 LastSupported = FALSE;
329 return FALSE;
330 }
331
332 if (RegsOut.w.bx != 0xAA55)
333 {
334 // BX = AA55h if installed
335 LastSupported = FALSE;
336 return FALSE;
337 }
338
339 if (!(RegsOut.w.cx & 0x0001))
340 {
341 // CX = API subset support bitmap
342 // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
343 printf("Suspicious API subset support bitmap 0x%x on device 0x%lx\n", RegsOut.w.cx, DriveNumber);
344 LastSupported = FALSE;
345 return FALSE;
346 }
347
348 LastSupported = TRUE;
349 return TRUE;
350 }
351
352 BOOLEAN PcDiskReadLogicalSectors(ULONG DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
353 {
354
355 DbgPrint((DPRINT_DISK, "PcDiskReadLogicalSectors() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n", DriveNumber, SectorNumber, SectorCount, Buffer));
356
357 //
358 // Check to see if it is a fixed disk drive
359 // If so then check to see if Int13 extensions work
360 // If they do then use them, otherwise default back to BIOS calls
361 //
362 if ((DriveNumber >= 0x80) && PcDiskInt13ExtensionsSupported(DriveNumber))
363 {
364 DbgPrint((DPRINT_DISK, "Using Int 13 Extensions for read. PcDiskInt13ExtensionsSupported(%d) = %s\n", DriveNumber, PcDiskInt13ExtensionsSupported(DriveNumber) ? "TRUE" : "FALSE"));
365
366 //
367 // LBA is easy, nothing to calculate
368 // Just do the read
369 //
370 return PcDiskReadLogicalSectorsLBA(DriveNumber, SectorNumber, SectorCount, Buffer);
371 }
372 else
373 {
374 // LBA is not supported default to the CHS calls
375 return PcDiskReadLogicalSectorsCHS(DriveNumber, SectorNumber, SectorCount, Buffer);
376 }
377
378 return TRUE;
379 }
380
381 BOOLEAN
382 PcDiskGetDriveGeometry(ULONG DriveNumber, PGEOMETRY Geometry)
383 {
384 REGS RegsIn;
385 REGS RegsOut;
386 ULONG Cylinders;
387
388 DbgPrint((DPRINT_DISK, "DiskGetDriveGeometry()\n"));
389
390 /* BIOS Int 13h, function 08h - Get drive parameters
391 * AH = 08h
392 * DL = drive (bit 7 set for hard disk)
393 * ES:DI = 0000h:0000h to guard against BIOS bugs
394 * Return:
395 * CF set on error
396 * AH = status (07h)
397 * CF clear if successful
398 * AH = 00h
399 * AL = 00h on at least some BIOSes
400 * BL = drive type (AT/PS2 floppies only)
401 * CH = low eight bits of maximum cylinder number
402 * CL = maximum sector number (bits 5-0)
403 * high two bits of maximum cylinder number (bits 7-6)
404 * DH = maximum head number
405 * DL = number of drives
406 * ES:DI -> drive parameter table (floppies only)
407 */
408 RegsIn.b.ah = 0x08;
409 RegsIn.b.dl = DriveNumber;
410 RegsIn.w.es = 0x0000;
411 RegsIn.w.di = 0x0000;
412
413 /* Get drive parameters */
414 Int386(0x13, &RegsIn, &RegsOut);
415
416 if (! INT386_SUCCESS(RegsOut))
417 {
418 return FALSE;
419 }
420
421 Cylinders = (RegsOut.b.cl & 0xC0) << 2;
422 Cylinders += RegsOut.b.ch;
423 Cylinders++;
424 Geometry->Cylinders = Cylinders;
425 Geometry->Heads = RegsOut.b.dh + 1;
426 Geometry->Sectors = RegsOut.b.cl & 0x3F;
427 Geometry->BytesPerSector = 512; /* Just assume 512 bytes per sector */
428
429 return TRUE;
430 }
431
432 ULONG
433 PcDiskGetCacheableBlockCount(ULONG DriveNumber)
434 {
435 GEOMETRY Geometry;
436
437 /* If LBA is supported then the block size will be 64 sectors (32k)
438 * If not then the block size is the size of one track */
439 if (DiskInt13ExtensionsSupported(DriveNumber))
440 {
441 return 64;
442 }
443 /* Get the disk geometry
444 * If this fails then we will just return 1 sector to be safe */
445 else if (! PcDiskGetDriveGeometry(DriveNumber, &Geometry))
446 {
447 return 1;
448 }
449 else
450 {
451 return Geometry.Sectors;
452 }
453 }
454
455 /* EOF */