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