Merge PR #283 "[USBPORT] Transaction Translator (TT) support bringup"
[reactos.git] / 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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 // #if defined(__i386__) || defined(_M_AMD64)
21
22 #include <freeldr.h>
23
24 #include <debug.h>
25
26 DBG_DEFAULT_CHANNEL(DISK);
27
28 #include <pshpack2.h>
29
30 typedef struct
31 {
32 UCHAR PacketSize; // 00h - Size of packet (10h or 18h)
33 UCHAR Reserved; // 01h - Reserved (0)
34 USHORT LBABlockCount; // 02h - Number of blocks to transfer (max 007Fh for Phoenix EDD)
35 USHORT TransferBufferOffset; // 04h - Transfer buffer offset (seg:off)
36 USHORT TransferBufferSegment; // Transfer buffer segment (seg:off)
37 ULONGLONG LBAStartBlock; // 08h - Starting absolute block number
38 // ULONGLONG TransferBuffer64; // 10h - (EDD-3.0, optional) 64-bit flat address of transfer buffer
39 // used if DWORD at 04h is FFFFh:FFFFh
40 // Commented since some earlier BIOSes refuse to work with
41 // such extended structure
42 } I386_DISK_ADDRESS_PACKET, *PI386_DISK_ADDRESS_PACKET;
43
44 typedef struct
45 {
46 UCHAR PacketSize; // 00h - Size of packet in bytes (13h)
47 UCHAR MediaType; // 01h - Boot media type (see #00282)
48 UCHAR DriveNumber; /* 02h - Drive number:
49 * 00h Floppy image
50 * 80h Bootable hard disk
51 * 81h-FFh Nonbootable or no emulation
52 */
53 UCHAR Controller; // 03h - CD-ROM controller number
54 ULONG LBAImage; // 04h - Logical Block Address of disk image to emulate
55 USHORT DeviceSpec; /* 08h - Device specification (see also #00282)
56 * (IDE) Bit 0:
57 * Drive is slave instead of master
58 * (SCSI) Bits 7-0:
59 * LUN and PUN
60 * Bits 15-8:
61 * Bus number
62 */
63 USHORT Buffer; // 0Ah - Segment of 3K buffer for caching CD-ROM reads
64 USHORT LoadSeg; // 0Ch - Load segment for initial boot image.
65 // If 0000h, load at segment 07C0h.
66 USHORT SectorCount; // 0Eh - Number of 512-byte virtual sectors to load
67 // (only valid for AH=4Ch).
68 UCHAR CHSGeometry[3]; /* 10h - Low byte of cylinder count (for INT 13/AH=08h)
69 * 11h - Sector count, high bits of cylinder count (for INT 13/AH=08h)
70 * 12h - Head count (for INT 13/AH=08h)
71 */
72 UCHAR Reserved;
73 } I386_CDROM_SPEC_PACKET, *PI386_CDROM_SPEC_PACKET;
74
75 #include <poppack.h>
76
77 /* FUNCTIONS *****************************************************************/
78
79 BOOLEAN DiskResetController(UCHAR DriveNumber)
80 {
81 REGS RegsIn, RegsOut;
82
83 WARN("DiskResetController(0x%x) DISK OPERATION FAILED -- RESETTING CONTROLLER\n", DriveNumber);
84
85 /*
86 * BIOS Int 13h, function 0 - Reset disk system
87 * AH = 00h
88 * DL = drive (if bit 7 is set both hard disks and floppy disks reset)
89 * Return:
90 * AH = status
91 * CF clear if successful
92 * CF set on error
93 */
94 RegsIn.b.ah = 0x00;
95 RegsIn.b.dl = DriveNumber;
96
97 /* Reset the disk controller */
98 Int386(0x13, &RegsIn, &RegsOut);
99
100 return INT386_SUCCESS(RegsOut);
101 }
102
103 static BOOLEAN PcDiskReadLogicalSectorsLBA(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
104 {
105 REGS RegsIn, RegsOut;
106 ULONG RetryCount;
107 PI386_DISK_ADDRESS_PACKET Packet = (PI386_DISK_ADDRESS_PACKET)(BIOSCALLBUFFER);
108
109 TRACE("PcDiskReadLogicalSectorsLBA() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n", DriveNumber, SectorNumber, SectorCount, Buffer);
110 ASSERT(((ULONG_PTR)Buffer) <= 0xFFFFF);
111
112 /* Setup disk address packet */
113 RtlZeroMemory(Packet, sizeof(*Packet));
114 Packet->PacketSize = sizeof(*Packet);
115 Packet->Reserved = 0;
116 Packet->LBABlockCount = (USHORT)SectorCount;
117 ASSERT(Packet->LBABlockCount == SectorCount);
118 Packet->TransferBufferOffset = ((ULONG_PTR)Buffer) & 0x0F;
119 Packet->TransferBufferSegment = (USHORT)(((ULONG_PTR)Buffer) >> 4);
120 Packet->LBAStartBlock = SectorNumber;
121
122 /*
123 * BIOS int 0x13, function 42h - IBM/MS INT 13 Extensions - EXTENDED READ
124 * Return:
125 * CF clear if successful
126 * AH = 00h
127 * CF set on error
128 * AH = error code
129 * Disk address packet's block count field set to the
130 * number of blocks successfully transferred.
131 */
132 RegsIn.b.ah = 0x42; // Subfunction 42h
133 RegsIn.b.dl = DriveNumber; // Drive number in DL (0 - floppy, 0x80 - harddisk)
134 RegsIn.x.ds = BIOSCALLBUFSEGMENT; // DS:SI -> disk address packet
135 RegsIn.w.si = BIOSCALLBUFOFFSET;
136
137 /* Retry 3 times */
138 for (RetryCount=0; RetryCount<3; RetryCount++)
139 {
140 Int386(0x13, &RegsIn, &RegsOut);
141
142 /* If it worked return TRUE */
143 if (INT386_SUCCESS(RegsOut))
144 {
145 return TRUE;
146 }
147 /* If it was a corrected ECC error then the data is still good */
148 else if (RegsOut.b.ah == 0x11)
149 {
150 return TRUE;
151 }
152 /* If it failed then do the next retry */
153 else
154 {
155 DiskResetController(DriveNumber);
156 continue;
157 }
158 }
159
160 /* If we get here then the read failed */
161 ERR("Disk Read Failed in LBA mode: %x (DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d)\n", RegsOut.b.ah, DriveNumber, SectorNumber, SectorCount);
162
163 return FALSE;
164 }
165
166 static BOOLEAN PcDiskReadLogicalSectorsCHS(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
167 {
168 UCHAR PhysicalSector;
169 UCHAR PhysicalHead;
170 ULONG PhysicalTrack;
171 GEOMETRY DriveGeometry;
172 ULONG NumberOfSectorsToRead;
173 REGS RegsIn, RegsOut;
174 ULONG RetryCount;
175
176 TRACE("PcDiskReadLogicalSectorsCHS()\n");
177
178 /* Get the drive geometry */
179 if (!MachDiskGetDriveGeometry(DriveNumber, &DriveGeometry) ||
180 DriveGeometry.Sectors == 0 ||
181 DriveGeometry.Heads == 0)
182 {
183 return FALSE;
184 }
185
186 while (SectorCount)
187 {
188 /*
189 * Calculate the physical disk offsets.
190 * Note: DriveGeometry.Sectors < 64
191 */
192 PhysicalSector = 1 + (UCHAR)(SectorNumber % DriveGeometry.Sectors);
193 PhysicalHead = (UCHAR)((SectorNumber / DriveGeometry.Sectors) % DriveGeometry.Heads);
194 PhysicalTrack = (ULONG)((SectorNumber / DriveGeometry.Sectors) / DriveGeometry.Heads);
195
196 /* Calculate how many sectors we need to read this round */
197 if (PhysicalSector > 1)
198 {
199 if (SectorCount >= (DriveGeometry.Sectors - (PhysicalSector - 1)))
200 NumberOfSectorsToRead = (DriveGeometry.Sectors - (PhysicalSector - 1));
201 else
202 NumberOfSectorsToRead = SectorCount;
203 }
204 else
205 {
206 if (SectorCount >= DriveGeometry.Sectors)
207 NumberOfSectorsToRead = DriveGeometry.Sectors;
208 else
209 NumberOfSectorsToRead = SectorCount;
210 }
211
212 /* Make sure the read is within the geometry boundaries */
213 if ((PhysicalHead >= DriveGeometry.Heads) ||
214 (PhysicalTrack >= DriveGeometry.Cylinders) ||
215 ((NumberOfSectorsToRead + PhysicalSector) > (DriveGeometry.Sectors + 1)) ||
216 (PhysicalSector > DriveGeometry.Sectors))
217 {
218 DiskError("Disk read exceeds drive geometry limits.", 0);
219 return FALSE;
220 }
221
222 /*
223 * BIOS Int 13h, function 2 - Read Disk Sectors
224 * AH = 02h
225 * AL = number of sectors to read (must be nonzero)
226 * CH = low eight bits of cylinder number
227 * CL = sector number 1-63 (bits 0-5)
228 * high two bits of cylinder (bits 6-7, hard disk only)
229 * DH = head number
230 * DL = drive number (bit 7 set for hard disk)
231 * ES:BX -> data buffer
232 * Return:
233 * CF set on error
234 * if AH = 11h (corrected ECC error), AL = burst length
235 * CF clear if successful
236 * AH = status
237 * AL = number of sectors transferred
238 * (only valid if CF set for some BIOSes)
239 */
240 RegsIn.b.ah = 0x02;
241 RegsIn.b.al = (UCHAR)NumberOfSectorsToRead;
242 RegsIn.b.ch = (PhysicalTrack & 0xFF);
243 RegsIn.b.cl = (UCHAR)(PhysicalSector + ((PhysicalTrack & 0x300) >> 2));
244 RegsIn.b.dh = PhysicalHead;
245 RegsIn.b.dl = DriveNumber;
246 RegsIn.w.es = (USHORT)(((ULONG_PTR)Buffer) >> 4);
247 RegsIn.w.bx = ((ULONG_PTR)Buffer) & 0x0F;
248
249 /* Perform the read. Retry 3 times. */
250 for (RetryCount=0; RetryCount<3; RetryCount++)
251 {
252 Int386(0x13, &RegsIn, &RegsOut);
253
254 /* If it worked break out */
255 if (INT386_SUCCESS(RegsOut))
256 {
257 break;
258 }
259 /* If it was a corrected ECC error then the data is still good */
260 else if (RegsOut.b.ah == 0x11)
261 {
262 break;
263 }
264 /* If it failed the do the next retry */
265 else
266 {
267 DiskResetController(DriveNumber);
268 continue;
269 }
270 }
271
272 /* If we retried 3 times then fail */
273 if (RetryCount >= 3)
274 {
275 ERR("Disk Read Failed in CHS mode, after retrying 3 times: %x\n", RegsOut.b.ah);
276 return FALSE;
277 }
278
279 // I have learned that not all BIOSes return
280 // the sector read count in the AL register (at least mine doesn't)
281 // even if the sectors were read correctly. So instead
282 // of checking the sector read count we will rely solely
283 // on the carry flag being set on error
284
285 Buffer = (PVOID)((ULONG_PTR)Buffer + (NumberOfSectorsToRead * DriveGeometry.BytesPerSector));
286 SectorCount -= NumberOfSectorsToRead;
287 SectorNumber += NumberOfSectorsToRead;
288 }
289
290 return TRUE;
291 }
292
293 static BOOLEAN DiskInt13ExtensionsSupported(UCHAR DriveNumber)
294 {
295 static UCHAR LastDriveNumber = 0xff;
296 static BOOLEAN LastSupported;
297 REGS RegsIn, RegsOut;
298
299 TRACE("DiskInt13ExtensionsSupported()\n");
300
301 if (DriveNumber == LastDriveNumber)
302 {
303 TRACE("Using cached value %s for drive 0x%x\n",
304 LastSupported ? "TRUE" : "FALSE", DriveNumber);
305 return LastSupported;
306 }
307
308 /*
309 * Some BIOSes report that extended disk access functions are not supported
310 * when booting from a CD (e.g. Phoenix BIOS v6.00PG and Insyde BIOS shipping
311 * with Intel Macs). Therefore we just return TRUE if we're booting from a CD -
312 * we can assume that all El Torito capable BIOSes support INT 13 extensions.
313 * We simply detect whether we're booting from CD by checking whether the drive
314 * number is >= 0x8A. It's 0x90 on the Insyde BIOS, and 0x9F on most other BIOSes.
315 */
316 if (DriveNumber >= 0x8A)
317 {
318 LastSupported = TRUE;
319 return TRUE;
320 }
321
322 LastDriveNumber = DriveNumber;
323
324 /*
325 * IBM/MS INT 13 Extensions - INSTALLATION CHECK
326 * AH = 41h
327 * BX = 55AAh
328 * DL = drive (80h-FFh)
329 * Return:
330 * CF set on error (extensions not supported)
331 * AH = 01h (invalid function)
332 * CF clear if successful
333 * BX = AA55h if installed
334 * AH = major version of extensions
335 * 01h = 1.x
336 * 20h = 2.0 / EDD-1.0
337 * 21h = 2.1 / EDD-1.1
338 * 30h = EDD-3.0
339 * AL = internal use
340 * CX = API subset support bitmap
341 * DH = extension version (v2.0+ ??? -- not present in 1.x)
342 *
343 * Bitfields for IBM/MS INT 13 Extensions API support bitmap
344 * Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
345 * Bit 1, removable drive controller functions (AH=45h,46h,48h,49h,INT 15/AH=52h) supported
346 * Bit 2, enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported
347 * extended drive parameter table is valid
348 * Bits 3-15 reserved
349 */
350 RegsIn.b.ah = 0x41;
351 RegsIn.w.bx = 0x55AA;
352 RegsIn.b.dl = DriveNumber;
353
354 /* Reset the disk controller */
355 Int386(0x13, &RegsIn, &RegsOut);
356
357 if (!INT386_SUCCESS(RegsOut))
358 {
359 /* CF set on error (extensions not supported) */
360 LastSupported = FALSE;
361 return FALSE;
362 }
363
364 if (RegsOut.w.bx != 0xAA55)
365 {
366 /* BX = AA55h if installed */
367 LastSupported = FALSE;
368 return FALSE;
369 }
370
371 if (!(RegsOut.w.cx & 0x0001))
372 {
373 /*
374 * CX = API subset support bitmap.
375 * Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported.
376 */
377 DbgPrint("Suspicious API subset support bitmap 0x%x on device 0x%lx\n",
378 RegsOut.w.cx, DriveNumber);
379 LastSupported = FALSE;
380 return FALSE;
381 }
382
383 LastSupported = TRUE;
384 return TRUE;
385 }
386
387 BOOLEAN PcDiskReadLogicalSectors(UCHAR DriveNumber, ULONGLONG SectorNumber, ULONG SectorCount, PVOID Buffer)
388 {
389 BOOLEAN ExtensionsSupported;
390
391 TRACE("PcDiskReadLogicalSectors() DriveNumber: 0x%x SectorNumber: %I64d SectorCount: %d Buffer: 0x%x\n",
392 DriveNumber, SectorNumber, SectorCount, Buffer);
393
394 /*
395 * Check to see if it is a fixed disk drive.
396 * If so then check to see if Int13 extensions work.
397 * If they do then use them, otherwise default back to BIOS calls.
398 */
399 ExtensionsSupported = DiskInt13ExtensionsSupported(DriveNumber);
400
401 if ((DriveNumber >= 0x80) && ExtensionsSupported)
402 {
403 TRACE("Using Int 13 Extensions for read. DiskInt13ExtensionsSupported(%d) = %s\n", DriveNumber, ExtensionsSupported ? "TRUE" : "FALSE");
404
405 /* LBA is easy, nothing to calculate. Just do the read. */
406 return PcDiskReadLogicalSectorsLBA(DriveNumber, SectorNumber, SectorCount, Buffer);
407 }
408 else
409 {
410 /* LBA is not supported default to the CHS calls */
411 return PcDiskReadLogicalSectorsCHS(DriveNumber, SectorNumber, SectorCount, Buffer);
412 }
413
414 return TRUE;
415 }
416
417 VOID DiskStopFloppyMotor(VOID)
418 {
419 WRITE_PORT_UCHAR((PUCHAR)0x3F2, 0);
420 }
421
422 BOOLEAN DiskGetExtendedDriveParameters(UCHAR DriveNumber, PVOID Buffer, USHORT BufferSize)
423 {
424 REGS RegsIn, RegsOut;
425 PUSHORT Ptr = (PUSHORT)(BIOSCALLBUFFER);
426
427 TRACE("DiskGetExtendedDriveParameters()\n");
428
429 if (!DiskInt13ExtensionsSupported(DriveNumber))
430 return FALSE;
431
432 /* Initialize transfer buffer */
433 *Ptr = BufferSize;
434
435 /*
436 * BIOS Int 13h, function 48h - Get drive parameters
437 * AH = 48h
438 * DL = drive (bit 7 set for hard disk)
439 * DS:SI = result buffer
440 * Return:
441 * CF set on error
442 * AH = status (07h)
443 * CF clear if successful
444 * AH = 00h
445 * DS:SI -> result buffer
446 */
447 RegsIn.b.ah = 0x48;
448 RegsIn.b.dl = DriveNumber;
449 RegsIn.x.ds = BIOSCALLBUFSEGMENT; // DS:SI -> result buffer
450 RegsIn.w.si = BIOSCALLBUFOFFSET;
451
452 /* Get drive parameters */
453 Int386(0x13, &RegsIn, &RegsOut);
454 if (!INT386_SUCCESS(RegsOut))
455 return FALSE;
456
457 memcpy(Buffer, Ptr, BufferSize);
458
459 #if DBG
460 TRACE("size of buffer: %x\n", Ptr[0]);
461 TRACE("information flags: %x\n", Ptr[1]);
462 TRACE("number of physical cylinders on drive: %u\n", *(PULONG)&Ptr[2]);
463 TRACE("number of physical heads on drive: %u\n", *(PULONG)&Ptr[4]);
464 TRACE("number of physical sectors per track: %u\n", *(PULONG)&Ptr[6]);
465 TRACE("total number of sectors on drive: %I64u\n", *(unsigned long long*)&Ptr[8]);
466 TRACE("bytes per sector: %u\n", Ptr[12]);
467 if (Ptr[0] >= 0x1e)
468 {
469 TRACE("EED configuration parameters: %x:%x\n", Ptr[13], Ptr[14]);
470 if (Ptr[13] != 0xffff && Ptr[14] != 0xffff)
471 {
472 PUCHAR SpecPtr = (PUCHAR)(ULONG_PTR)((Ptr[13] << 4) + Ptr[14]);
473 TRACE("SpecPtr: %x\n", SpecPtr);
474 TRACE("physical I/O port base address: %x\n", *(PUSHORT)&SpecPtr[0]);
475 TRACE("disk-drive control port address: %x\n", *(PUSHORT)&SpecPtr[2]);
476 TRACE("drive flags: %x\n", SpecPtr[4]);
477 TRACE("proprietary information: %x\n", SpecPtr[5]);
478 TRACE("IRQ for drive: %u\n", SpecPtr[6]);
479 TRACE("sector count for multi-sector transfers: %u\n", SpecPtr[7]);
480 TRACE("DMA control: %x\n", SpecPtr[8]);
481 TRACE("programmed I/O control: %x\n", SpecPtr[9]);
482 TRACE("drive options: %x\n", *(PUSHORT)&SpecPtr[10]);
483 }
484 }
485 if (Ptr[0] >= 0x42)
486 {
487 TRACE("signature: %x\n", Ptr[15]);
488 }
489 #endif
490
491 return TRUE;
492 }
493
494 BOOLEAN
495 PcDiskGetDriveGeometry(UCHAR DriveNumber, PGEOMETRY Geometry)
496 {
497 EXTENDED_GEOMETRY ExtGeometry;
498 REGS RegsIn, RegsOut;
499 ULONG Cylinders;
500
501 TRACE("DiskGetDriveGeometry()\n");
502
503 /* Try to get the extended geometry first */
504 ExtGeometry.Size = sizeof(ExtGeometry);
505 if (DiskGetExtendedDriveParameters(DriveNumber, &ExtGeometry, ExtGeometry.Size))
506 {
507 Geometry->Cylinders = ExtGeometry.Cylinders;
508 Geometry->Heads = ExtGeometry.Heads;
509 Geometry->Sectors = ExtGeometry.SectorsPerTrack;
510 Geometry->BytesPerSector = ExtGeometry.BytesPerSector;
511 return TRUE;
512 }
513
514 /*
515 * BIOS Int 13h, function 08h - Get drive parameters
516 * AH = 08h
517 * DL = drive (bit 7 set for hard disk)
518 * ES:DI = 0000h:0000h to guard against BIOS bugs
519 * Return:
520 * CF set on error
521 * AH = status (07h)
522 * CF clear if successful
523 * AH = 00h
524 * AL = 00h on at least some BIOSes
525 * BL = drive type (AT/PS2 floppies only)
526 * CH = low eight bits of maximum cylinder number
527 * CL = maximum sector number (bits 5-0)
528 * high two bits of maximum cylinder number (bits 7-6)
529 * DH = maximum head number
530 * DL = number of drives
531 * ES:DI -> drive parameter table (floppies only)
532 */
533 RegsIn.b.ah = 0x08;
534 RegsIn.b.dl = DriveNumber;
535 RegsIn.w.es = 0x0000;
536 RegsIn.w.di = 0x0000;
537
538 /* Get drive parameters */
539 Int386(0x13, &RegsIn, &RegsOut);
540 if (!INT386_SUCCESS(RegsOut))
541 return FALSE;
542
543 Cylinders = (RegsOut.b.cl & 0xC0) << 2;
544 Cylinders += RegsOut.b.ch;
545 Cylinders++;
546 Geometry->Cylinders = Cylinders;
547 Geometry->Heads = RegsOut.b.dh + 1;
548 Geometry->Sectors = RegsOut.b.cl & 0x3F;
549 Geometry->BytesPerSector = 512; /* Just assume 512 bytes per sector */
550
551 return TRUE;
552 }
553
554 ULONG
555 PcDiskGetCacheableBlockCount(UCHAR DriveNumber)
556 {
557 GEOMETRY Geometry;
558
559 /* If LBA is supported then the block size will be 64 sectors (32k)
560 * If not then the block size is the size of one track. */
561 if (DiskInt13ExtensionsSupported(DriveNumber))
562 {
563 return 64;
564 }
565 /* Get the disk geometry. If this fails then we will
566 * just return 1 sector to be safe. */
567 else if (! PcDiskGetDriveGeometry(DriveNumber, &Geometry))
568 {
569 return 1;
570 }
571 else
572 {
573 return Geometry.Sectors;
574 }
575 }
576
577 BOOLEAN
578 PcDiskGetBootPath(OUT PCHAR BootPath, IN ULONG Size)
579 {
580 // FIXME: Keep it there, or put it in DiskGetBootPath?
581 // Or, abstract the notion of network booting to make
582 // sense for other platforms than the PC (and this idea
583 // already exists), then we would need to check whether
584 // we were booting from network (and: PC --> PXE, etc...)
585 // and if so, set the correct ARC path. But then this new
586 // logic could be moved back to DiskGetBootPath...
587
588 if (*FrldrBootPath)
589 {
590 /* Copy back the buffer */
591 if (Size < strlen(FrldrBootPath) + 1)
592 return FALSE;
593 strncpy(BootPath, FrldrBootPath, Size);
594 return TRUE;
595 }
596
597 // FIXME! FIXME! Do this in some drive recognition procedure!!!!
598 if (PxeInit())
599 {
600 strcpy(BootPath, "net(0)");
601 return TRUE;
602 }
603 return DiskGetBootPath(BootPath, Size);
604 }
605
606 /* EOF */