]>
Commit | Line | Data |
---|---|---|
14c7c974 | 1 | /* |
57c72a9a | 2 | * Copyright (c) 1999-2003 Apple Computer, Inc. All rights reserved. |
14c7c974 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
57c72a9a | 6 | * Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights |
4f6e3300 A |
7 | * Reserved. This file contains Original Code and/or Modifications of |
8 | * Original Code as defined in and that are subject to the Apple Public | |
57c72a9a | 9 | * Source License Version 2.0 (the "License"). You may not use this file |
4f6e3300 A |
10 | * except in compliance with the License. Please obtain a copy of the |
11 | * License at http://www.apple.com/publicsource and read it before using | |
12 | * this file. | |
14c7c974 A |
13 | * |
14 | * The Original Code and all software distributed under the License are | |
4f6e3300 | 15 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
14c7c974 A |
16 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
17 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
4f6e3300 A |
18 | * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the |
19 | * License for the specific language governing rights and limitations | |
20 | * under the License. | |
14c7c974 A |
21 | * |
22 | * @APPLE_LICENSE_HEADER_END@ | |
23 | */ | |
24 | /* | |
25 | * Mach Operating System | |
26 | * Copyright (c) 1990 Carnegie-Mellon University | |
27 | * Copyright (c) 1989 Carnegie-Mellon University | |
28 | * All rights reserved. The CMU software License Agreement specifies | |
29 | * the terms and conditions for use and redistribution. | |
30 | */ | |
31 | ||
32 | /* | |
33 | * INTEL CORPORATION PROPRIETARY INFORMATION | |
34 | * | |
35 | * This software is supplied under the terms of a license agreement or | |
36 | * nondisclosure agreement with Intel Corporation and may not be copied | |
37 | * nor disclosed except in accordance with the terms of that agreement. | |
38 | * | |
39 | * Copyright 1988, 1989 Intel Corporation | |
40 | */ | |
41 | ||
42 | /* | |
43 | * Copyright 1993 NeXT Computer, Inc. | |
44 | * All rights reserved. | |
45 | */ | |
46 | ||
bba600dd A |
47 | #define UFS_SUPPORT 1 |
48 | ||
f083c6c3 | 49 | #include "bootstruct.h" |
14c7c974 | 50 | #include "libsaio.h" |
75b89a82 | 51 | #include "fdisk.h" |
bba600dd | 52 | #if UFS_SUPPORT |
f083c6c3 | 53 | #include "ufs.h" |
bba600dd | 54 | #endif |
f083c6c3 | 55 | #include "hfs.h" |
57c72a9a A |
56 | #include "ntfs.h" |
57 | #include "msdos.h" | |
14c7c974 | 58 | |
f083c6c3 A |
59 | #include <limits.h> |
60 | #include <IOKit/storage/IOApplePartitionScheme.h> | |
14c7c974 | 61 | |
75b89a82 | 62 | #define BPS 512 /* sector size of the device */ |
f083c6c3 A |
63 | #define CD_BPS 2048 /* CD-ROM block size */ |
64 | #define N_CACHE_SECS (BIOS_LEN / BPS) /* Must be a multiple of 4 for CD-ROMs */ | |
75b89a82 | 65 | #define UFS_FRONT_PORCH 0 |
f083c6c3 A |
66 | #define kAPMSector 2 /* Sector number of Apple partition map */ |
67 | #define kAPMCDSector 8 /* Translated sector of Apple partition map on a CD */ | |
14c7c974 A |
68 | |
69 | /* | |
75b89a82 A |
70 | * trackbuf points to the start of the track cache. Biosread() |
71 | * will store the sectors read from disk to this memory area. | |
14c7c974 | 72 | * |
75b89a82 A |
73 | * biosbuf points to a sector within the track cache, and is |
74 | * updated by Biosread(). | |
14c7c974 | 75 | */ |
57c72a9a A |
76 | static char * const trackbuf = (char *) ptov(BIOS_ADDR); |
77 | static char * biosbuf; | |
14c7c974 | 78 | |
75b89a82 A |
79 | /* |
80 | * Map a disk drive to bootable volumes contained within. | |
14c7c974 | 81 | */ |
75b89a82 A |
82 | struct DiskBVMap { |
83 | int biosdev; // BIOS device number (unique) | |
84 | BVRef bvr; // chain of boot volumes on the disk | |
85 | int bvrcnt; // number of boot volumes | |
86 | struct DiskBVMap * next; // linkage to next mapping | |
87 | }; | |
14c7c974 | 88 | |
75b89a82 A |
89 | static struct DiskBVMap * gDiskBVMap = NULL; |
90 | static struct disk_blk0 * gBootSector = NULL; | |
14c7c974 | 91 | |
75b89a82 | 92 | extern void spinActivityIndicator(); |
14c7c974 | 93 | |
75b89a82 | 94 | static void getVolumeDescription(BVRef bvr, char * str, long strMaxLen); |
14c7c974 | 95 | |
75b89a82 | 96 | //========================================================================== |
14c7c974 | 97 | |
f083c6c3 | 98 | static int getDriveInfo( int biosdev, struct driveInfo *dip ) |
14c7c974 | 99 | { |
f083c6c3 A |
100 | static struct driveInfo cached_di; |
101 | int cc; | |
75b89a82 | 102 | |
f083c6c3 | 103 | if ( !cached_di.valid || biosdev != cached_di.biosdev ) |
75b89a82 | 104 | { |
f083c6c3 A |
105 | cc = get_drive_info(biosdev, &cached_di); |
106 | if (cc < 0) { | |
107 | cached_di.valid = 0; | |
bba600dd | 108 | DEBUG_DISK(("get_drive_info returned error\n")); |
f083c6c3 A |
109 | return (-1); // BIOS call error |
110 | } | |
75b89a82 | 111 | } |
14c7c974 | 112 | |
f083c6c3 | 113 | bcopy(&cached_di, dip, sizeof(cached_di)); |
14c7c974 | 114 | |
75b89a82 A |
115 | return 0; |
116 | } | |
14c7c974 | 117 | |
75b89a82 A |
118 | //========================================================================== |
119 | // Maps (E)BIOS return codes to message strings. | |
14c7c974 | 120 | |
75b89a82 A |
121 | struct NamedValue { |
122 | unsigned char value; | |
123 | const char * name; | |
124 | }; | |
14c7c974 | 125 | |
75b89a82 A |
126 | static const char * getNameForValue( const struct NamedValue * nameTable, |
127 | unsigned char value ) | |
128 | { | |
129 | const struct NamedValue * np; | |
14c7c974 | 130 | |
75b89a82 A |
131 | for ( np = nameTable; np->value; np++) |
132 | if (np->value == value) | |
133 | return np->name; | |
14c7c974 | 134 | |
75b89a82 | 135 | return NULL; |
14c7c974 A |
136 | } |
137 | ||
14c7c974 A |
138 | #define ECC_CORRECTED_ERR 0x11 |
139 | ||
75b89a82 A |
140 | static const struct NamedValue bios_errors[] = { |
141 | { 0x10, "Media error" }, | |
142 | { 0x11, "Corrected ECC error" }, | |
143 | { 0x20, "Controller or device error" }, | |
144 | { 0x40, "Seek failed" }, | |
145 | { 0x80, "Device timeout" }, | |
146 | { 0xAA, "Drive not ready" }, | |
147 | { 0x00, 0 } | |
14c7c974 A |
148 | }; |
149 | ||
75b89a82 | 150 | static const char * bios_error(int errnum) |
14c7c974 | 151 | { |
75b89a82 A |
152 | static char errorstr[] = "Error 0x00"; |
153 | const char * errname; | |
154 | ||
155 | errname = getNameForValue( bios_errors, errnum ); | |
156 | if ( errname ) return errname; | |
157 | ||
158 | sprintf(errorstr, "Error 0x%02x", errnum); | |
159 | return errorstr; // No string, print error code only | |
14c7c974 A |
160 | } |
161 | ||
75b89a82 A |
162 | //========================================================================== |
163 | // Use BIOS INT13 calls to read the sector specified. This function will | |
164 | // also perform read-ahead to cache a few subsequent sector to the sector | |
165 | // cache. | |
166 | // | |
167 | // Return: | |
168 | // 0 on success, or an error code from INT13/F2 or INT13/F42 BIOS call. | |
169 | ||
57c72a9a A |
170 | static BOOL cache_valid = FALSE; |
171 | ||
75b89a82 | 172 | static int Biosread( int biosdev, unsigned int secno ) |
14c7c974 | 173 | { |
75b89a82 A |
174 | static int xbiosdev, xcyl, xhead; |
175 | static unsigned int xsec, xnsecs; | |
f083c6c3 | 176 | struct driveInfo di; |
14c7c974 | 177 | |
75b89a82 | 178 | int rc = -1; |
14c7c974 | 179 | int cyl, head, sec; |
14c7c974 | 180 | int tries = 0; |
f083c6c3 | 181 | int bps, divisor; |
14c7c974 | 182 | |
f083c6c3 A |
183 | if (getDriveInfo(biosdev, &di) < 0) { |
184 | return -1; | |
185 | } | |
186 | if (di.no_emulation) { | |
187 | /* Always assume 2k block size; BIOS may lie about geometry */ | |
188 | bps = 2048; | |
189 | } else { | |
190 | bps = di.di.params.phys_nbps; | |
57c72a9a A |
191 | if (bps == 0) { |
192 | return -1; | |
193 | } | |
f083c6c3 A |
194 | } |
195 | divisor = bps / BPS; | |
196 | ||
197 | DEBUG_DISK(("Biosread dev %x sec %d bps %d\n", biosdev, secno, bps)); | |
14c7c974 A |
198 | |
199 | // To read the disk sectors, use EBIOS if we can. Otherwise, | |
200 | // revert to the standard BIOS calls. | |
75b89a82 A |
201 | |
202 | if ((biosdev >= kBIOSDevTypeHardDrive) && | |
f083c6c3 | 203 | (di.uses_ebios & EBIOS_FIXED_DISK_ACCESS)) |
75b89a82 | 204 | { |
14c7c974 A |
205 | if (cache_valid && |
206 | (biosdev == xbiosdev) && | |
207 | (secno >= xsec) && | |
f083c6c3 | 208 | ((unsigned int)secno < (xsec + xnsecs))) |
14c7c974 | 209 | { |
75b89a82 | 210 | biosbuf = trackbuf + (BPS * (secno - xsec)); |
14c7c974 A |
211 | return 0; |
212 | } | |
213 | ||
214 | xnsecs = N_CACHE_SECS; | |
f083c6c3 | 215 | xsec = (secno / divisor) * divisor; |
75b89a82 | 216 | cache_valid = FALSE; |
14c7c974 | 217 | |
f083c6c3 | 218 | while ((rc = ebiosread(biosdev, secno / divisor, xnsecs / divisor)) && (++tries < 5)) |
14c7c974 A |
219 | { |
220 | if (rc == ECC_CORRECTED_ERR) { | |
221 | /* Ignore corrected ECC errors */ | |
75b89a82 | 222 | rc = 0; |
14c7c974 A |
223 | break; |
224 | } | |
225 | error(" EBIOS read error: %s\n", bios_error(rc), rc); | |
226 | error(" Block %d Sectors %d\n", secno, xnsecs); | |
227 | sleep(1); | |
228 | } | |
229 | } | |
f083c6c3 | 230 | else |
75b89a82 | 231 | { |
f083c6c3 A |
232 | /* spc = spt * heads */ |
233 | int spc = (di.di.params.phys_spt * di.di.params.phys_heads); | |
14c7c974 | 234 | cyl = secno / spc; |
f083c6c3 A |
235 | head = (secno % spc) / di.di.params.phys_spt; |
236 | sec = secno % di.di.params.phys_spt; | |
14c7c974 A |
237 | |
238 | if (cache_valid && | |
239 | (biosdev == xbiosdev) && | |
240 | (cyl == xcyl) && | |
241 | (head == xhead) && | |
f083c6c3 A |
242 | ((unsigned int)sec >= xsec) && |
243 | ((unsigned int)sec < (xsec + xnsecs))) | |
14c7c974 | 244 | { |
75b89a82 A |
245 | // this sector is in trackbuf cache |
246 | biosbuf = trackbuf + (BPS * (sec - xsec)); | |
14c7c974 A |
247 | return 0; |
248 | } | |
249 | ||
250 | // Cache up to a track worth of sectors, but do not cross a | |
251 | // track boundary. | |
75b89a82 | 252 | |
14c7c974 A |
253 | xcyl = cyl; |
254 | xhead = head; | |
255 | xsec = sec; | |
f083c6c3 | 256 | xnsecs = ((unsigned int)(sec + N_CACHE_SECS) > di.di.params.phys_spt) ? (di.di.params.phys_spt - sec) : N_CACHE_SECS; |
75b89a82 | 257 | cache_valid = FALSE; |
14c7c974 A |
258 | |
259 | while ((rc = biosread(biosdev, cyl, head, sec, xnsecs)) && | |
260 | (++tries < 5)) | |
261 | { | |
262 | if (rc == ECC_CORRECTED_ERR) { | |
263 | /* Ignore corrected ECC errors */ | |
75b89a82 | 264 | rc = 0; |
14c7c974 A |
265 | break; |
266 | } | |
267 | error(" BIOS read error: %s\n", bios_error(rc), rc); | |
268 | error(" Block %d, Cyl %d Head %d Sector %d\n", | |
269 | secno, cyl, head, sec); | |
270 | sleep(1); | |
271 | } | |
272 | } | |
273 | ||
274 | // If the BIOS reported success, mark the sector cache as valid. | |
75b89a82 | 275 | |
14c7c974 A |
276 | if (rc == 0) { |
277 | cache_valid = TRUE; | |
278 | } | |
f083c6c3 | 279 | biosbuf = trackbuf + (secno % divisor) * BPS; |
14c7c974 A |
280 | xbiosdev = biosdev; |
281 | ||
75b89a82 | 282 | spinActivityIndicator(); |
14c7c974 A |
283 | |
284 | return rc; | |
285 | } | |
286 | ||
75b89a82 A |
287 | //========================================================================== |
288 | ||
289 | static int readBytes( int biosdev, unsigned int blkno, | |
bba600dd | 290 | unsigned int byteoff, |
75b89a82 | 291 | unsigned int byteCount, void * buffer ) |
14c7c974 | 292 | { |
75b89a82 A |
293 | |
294 | char * cbuf = (char *) buffer; | |
295 | int error; | |
296 | int copy_len; | |
297 | ||
298 | DEBUG_DISK(("%s: dev %x block %x [%d] -> 0x%x...", __FUNCTION__, | |
299 | biosdev, blkno, byteCount, (unsigned)cbuf)); | |
300 | ||
bba600dd | 301 | for ( ; byteCount; cbuf += copy_len, blkno++ ) |
75b89a82 A |
302 | { |
303 | error = Biosread( biosdev, blkno ); | |
304 | if ( error ) | |
305 | { | |
306 | DEBUG_DISK(("error\n")); | |
307 | return (-1); | |
308 | } | |
309 | ||
bba600dd A |
310 | copy_len = ((byteCount + byteoff) > BPS) ? (BPS - byteoff) : byteCount; |
311 | bcopy( biosbuf + byteoff, cbuf, copy_len ); | |
75b89a82 | 312 | byteCount -= copy_len; |
bba600dd | 313 | byteoff = 0; |
75b89a82 A |
314 | } |
315 | ||
316 | DEBUG_DISK(("done\n")); | |
317 | ||
318 | return 0; | |
14c7c974 A |
319 | } |
320 | ||
75b89a82 A |
321 | //========================================================================== |
322 | ||
323 | static int isExtendedFDiskPartition( const struct fdisk_part * part ) | |
14c7c974 | 324 | { |
75b89a82 A |
325 | static unsigned char extParts[] = |
326 | { | |
327 | 0x05, /* Extended */ | |
328 | 0x0f, /* Win95 extended */ | |
329 | 0x85, /* Linux extended */ | |
330 | }; | |
331 | ||
f083c6c3 | 332 | unsigned int i; |
75b89a82 A |
333 | |
334 | for (i = 0; i < sizeof(extParts)/sizeof(extParts[0]); i++) | |
335 | { | |
336 | if (extParts[i] == part->systid) return 1; | |
337 | } | |
338 | return 0; | |
14c7c974 A |
339 | } |
340 | ||
75b89a82 A |
341 | //========================================================================== |
342 | ||
343 | static int getNextFDiskPartition( int biosdev, int * partno, | |
344 | const struct fdisk_part ** outPart ) | |
14c7c974 | 345 | { |
75b89a82 A |
346 | static int sBiosdev = -1; |
347 | static int sNextPartNo; | |
57c72a9a | 348 | static unsigned int sFirstBase; |
75b89a82 A |
349 | static unsigned int sExtBase; |
350 | static unsigned int sExtDepth; | |
351 | static struct fdisk_part * sExtPart; | |
352 | struct fdisk_part * part; | |
353 | ||
354 | if ( sBiosdev != biosdev || *partno < 0 ) | |
355 | { | |
356 | // Fetch MBR. | |
357 | if ( readBootSector( biosdev, DISK_BLK0, 0 ) ) return 0; | |
358 | ||
359 | sBiosdev = biosdev; | |
360 | sNextPartNo = 0; | |
57c72a9a | 361 | sFirstBase = 0; |
75b89a82 A |
362 | sExtBase = 0; |
363 | sExtDepth = 0; | |
364 | sExtPart = NULL; | |
365 | } | |
14c7c974 | 366 | |
75b89a82 A |
367 | while (1) |
368 | { | |
369 | part = NULL; | |
14c7c974 | 370 | |
75b89a82 A |
371 | if ( sNextPartNo < FDISK_NPART ) |
372 | { | |
373 | part = (struct fdisk_part *) gBootSector->parts[sNextPartNo]; | |
374 | } | |
375 | else if ( sExtPart ) | |
376 | { | |
57c72a9a | 377 | unsigned int blkno = sExtPart->relsect + sFirstBase; |
14c7c974 | 378 | |
75b89a82 A |
379 | // Save the block offset of the first extended partition. |
380 | ||
57c72a9a A |
381 | if (sExtDepth == 0) { |
382 | sFirstBase = blkno; | |
383 | } | |
384 | sExtBase = blkno; | |
75b89a82 A |
385 | |
386 | // Load extended partition table. | |
387 | ||
388 | if ( readBootSector( biosdev, blkno, 0 ) == 0 ) | |
389 | { | |
390 | sNextPartNo = 0; | |
391 | sExtDepth++; | |
392 | sExtPart = NULL; | |
393 | continue; | |
394 | } | |
57c72a9a | 395 | // Fall through to part == NULL |
75b89a82 A |
396 | } |
397 | ||
398 | if ( part == NULL ) break; // Reached end of partition chain. | |
399 | ||
400 | // Advance to next partition number. | |
401 | ||
402 | sNextPartNo++; | |
403 | ||
75b89a82 A |
404 | if ( isExtendedFDiskPartition(part) ) |
405 | { | |
406 | sExtPart = part; | |
407 | continue; | |
408 | } | |
409 | ||
410 | // Skip empty slots. | |
411 | ||
412 | if ( part->systid == 0x00 ) | |
413 | { | |
414 | continue; | |
415 | } | |
416 | ||
417 | // Change relative offset to an absolute offset. | |
75b89a82 A |
418 | part->relsect += sExtBase; |
419 | ||
420 | *outPart = part; | |
57c72a9a | 421 | *partno = sExtDepth ? (int)(sExtDepth + FDISK_NPART) : sNextPartNo; |
75b89a82 A |
422 | |
423 | break; | |
14c7c974 A |
424 | } |
425 | ||
75b89a82 A |
426 | return (part != NULL); |
427 | } | |
428 | ||
429 | //========================================================================== | |
14c7c974 | 430 | |
75b89a82 A |
431 | static BVRef newFDiskBVRef( int biosdev, int partno, unsigned int blkoff, |
432 | const struct fdisk_part * part, | |
433 | FSInit initFunc, FSLoadFile loadFunc, | |
57c72a9a A |
434 | FSReadFile readFunc, |
435 | FSGetDirEntry getdirFunc, | |
436 | FSGetFileBlock getBlockFunc, | |
bba600dd | 437 | FSGetUUID getUUIDFunc, |
57c72a9a A |
438 | BVGetDescription getDescriptionFunc, |
439 | int probe, int type ) | |
75b89a82 A |
440 | { |
441 | BVRef bvr = (BVRef) malloc( sizeof(*bvr) ); | |
442 | if ( bvr ) | |
14c7c974 | 443 | { |
75b89a82 | 444 | bzero(bvr, sizeof(*bvr)); |
14c7c974 | 445 | |
75b89a82 A |
446 | bvr->biosdev = biosdev; |
447 | bvr->part_no = partno; | |
448 | bvr->part_boff = blkoff; | |
449 | bvr->part_type = part->systid; | |
450 | bvr->fs_loadfile = loadFunc; | |
57c72a9a | 451 | bvr->fs_readfile = readFunc; |
75b89a82 | 452 | bvr->fs_getdirentry = getdirFunc; |
57c72a9a | 453 | bvr->fs_getfileblock= getBlockFunc; |
bba600dd | 454 | bvr->fs_getuuid = getUUIDFunc; |
57c72a9a A |
455 | bvr->description = getDescriptionFunc ? |
456 | getDescriptionFunc : getVolumeDescription; | |
f083c6c3 | 457 | bvr->type = type; |
75b89a82 A |
458 | |
459 | if ( part->bootid & FDISK_ACTIVE ) | |
460 | bvr->flags |= kBVFlagPrimary; | |
461 | ||
462 | // Probe the filesystem. | |
463 | ||
464 | if ( initFunc ) | |
465 | { | |
466 | bvr->flags |= kBVFlagNativeBoot; | |
14c7c974 | 467 | |
75b89a82 A |
468 | if ( probe && initFunc( bvr ) != 0 ) |
469 | { | |
470 | // filesystem probe failed. | |
14c7c974 | 471 | |
75b89a82 A |
472 | DEBUG_DISK(("%s: failed probe on dev %x part %d\n", |
473 | __FUNCTION__, biosdev, partno)); | |
14c7c974 | 474 | |
75b89a82 A |
475 | free(bvr); |
476 | bvr = NULL; | |
477 | } | |
478 | } | |
479 | else if ( readBootSector( biosdev, blkoff, (void *)0x7e00 ) == 0 ) | |
480 | { | |
481 | bvr->flags |= kBVFlagForeignBoot; | |
482 | } | |
483 | else | |
484 | { | |
485 | free(bvr); | |
486 | bvr = NULL; | |
14c7c974 A |
487 | } |
488 | } | |
75b89a82 A |
489 | return bvr; |
490 | } | |
14c7c974 | 491 | |
75b89a82 A |
492 | //========================================================================== |
493 | ||
f083c6c3 A |
494 | BVRef newAPMBVRef( int biosdev, int partno, unsigned int blkoff, |
495 | const DPME * part, | |
496 | FSInit initFunc, FSLoadFile loadFunc, | |
57c72a9a A |
497 | FSReadFile readFunc, |
498 | FSGetDirEntry getdirFunc, | |
499 | FSGetFileBlock getBlockFunc, | |
bba600dd | 500 | FSGetUUID getUUIDFunc, |
57c72a9a A |
501 | BVGetDescription getDescriptionFunc, |
502 | int probe, int type ) | |
f083c6c3 A |
503 | { |
504 | BVRef bvr = (BVRef) malloc( sizeof(*bvr) ); | |
505 | if ( bvr ) | |
506 | { | |
507 | bzero(bvr, sizeof(*bvr)); | |
508 | ||
509 | bvr->biosdev = biosdev; | |
510 | bvr->part_no = partno; | |
511 | bvr->part_boff = blkoff; | |
512 | bvr->fs_loadfile = loadFunc; | |
57c72a9a | 513 | bvr->fs_readfile = readFunc; |
f083c6c3 | 514 | bvr->fs_getdirentry = getdirFunc; |
57c72a9a | 515 | bvr->fs_getfileblock= getBlockFunc; |
bba600dd | 516 | bvr->fs_getuuid = getUUIDFunc; |
57c72a9a A |
517 | bvr->description = getDescriptionFunc ? |
518 | getDescriptionFunc : getVolumeDescription; | |
f083c6c3 A |
519 | bvr->type = type; |
520 | strlcpy(bvr->name, part->dpme_name, DPISTRLEN); | |
521 | strlcpy(bvr->type_name, part->dpme_type, DPISTRLEN); | |
522 | ||
523 | /* | |
524 | if ( part->bootid & FDISK_ACTIVE ) | |
525 | bvr->flags |= kBVFlagPrimary; | |
526 | */ | |
527 | ||
528 | // Probe the filesystem. | |
529 | ||
530 | if ( initFunc ) | |
531 | { | |
532 | bvr->flags |= kBVFlagNativeBoot; | |
533 | ||
534 | if ( probe && initFunc( bvr ) != 0 ) | |
535 | { | |
536 | // filesystem probe failed. | |
537 | ||
538 | DEBUG_DISK(("%s: failed probe on dev %x part %d\n", | |
539 | __FUNCTION__, biosdev, partno)); | |
540 | ||
541 | free(bvr); | |
542 | bvr = NULL; | |
543 | } | |
544 | } | |
545 | /* | |
546 | else if ( readBootSector( biosdev, blkoff, (void *)0x7e00 ) == 0 ) | |
547 | { | |
548 | bvr->flags |= kBVFlagForeignBoot; | |
549 | } | |
550 | */ | |
551 | else | |
552 | { | |
553 | free(bvr); | |
554 | bvr = NULL; | |
555 | } | |
556 | } | |
557 | return bvr; | |
558 | } | |
559 | ||
560 | //========================================================================== | |
561 | ||
57c72a9a A |
562 | /* A note on partition numbers: |
563 | * IOKit makes the primary partitions numbers 1-4, and then | |
564 | * extended partitions are numbered consecutively 5 and up. | |
565 | * So, for example, if you have two primary partitions and | |
566 | * one extended partition they will be numbered 1, 2, 5. | |
567 | */ | |
568 | ||
f083c6c3 | 569 | static BVRef diskScanFDiskBootVolumes( int biosdev, int * countPtr ) |
75b89a82 A |
570 | { |
571 | const struct fdisk_part * part; | |
572 | struct DiskBVMap * map; | |
573 | int partno = -1; | |
574 | BVRef bvr; | |
bba600dd | 575 | #if UFS_SUPPORT |
75b89a82 | 576 | BVRef booterUFS = NULL; |
bba600dd | 577 | #endif |
f083c6c3 A |
578 | int spc; |
579 | struct driveInfo di; | |
580 | boot_drive_info_t *dp; | |
75b89a82 | 581 | |
f083c6c3 A |
582 | /* Initialize disk info */ |
583 | if (getDriveInfo(biosdev, &di) != 0) { | |
584 | return NULL; | |
585 | } | |
586 | dp = &di.di; | |
587 | spc = (dp->params.phys_spt * dp->params.phys_heads); | |
588 | if (spc == 0) { | |
589 | /* This is probably a CD-ROM; punt on the geometry. */ | |
590 | spc = 1; | |
591 | } | |
14c7c974 | 592 | |
f083c6c3 | 593 | do { |
75b89a82 | 594 | // Create a new mapping. |
14c7c974 | 595 | |
75b89a82 A |
596 | map = (struct DiskBVMap *) malloc( sizeof(*map) ); |
597 | if ( map ) | |
598 | { | |
599 | map->biosdev = biosdev; | |
600 | map->bvr = NULL; | |
601 | map->bvrcnt = 0; | |
602 | map->next = gDiskBVMap; | |
603 | gDiskBVMap = map; | |
14c7c974 | 604 | |
75b89a82 A |
605 | // Create a record for each partition found on the disk. |
606 | ||
607 | while ( getNextFDiskPartition( biosdev, &partno, &part ) ) | |
14c7c974 | 608 | { |
75b89a82 A |
609 | DEBUG_DISK(("%s: part %d [%x]\n", __FUNCTION__, |
610 | partno, part->systid)); | |
75b89a82 A |
611 | bvr = 0; |
612 | ||
613 | switch ( part->systid ) | |
614 | { | |
bba600dd | 615 | #if UFS_SUPPORT |
75b89a82 | 616 | case FDISK_UFS: |
f083c6c3 | 617 | bvr = newFDiskBVRef( |
75b89a82 A |
618 | biosdev, partno, |
619 | part->relsect + UFS_FRONT_PORCH/BPS, | |
620 | part, | |
621 | UFSInitPartition, | |
622 | UFSLoadFile, | |
57c72a9a | 623 | UFSReadFile, |
75b89a82 | 624 | UFSGetDirEntry, |
57c72a9a | 625 | UFSGetFileBlock, |
bba600dd | 626 | UFSGetUUID, |
57c72a9a | 627 | UFSGetDescription, |
f083c6c3 A |
628 | 0, |
629 | kBIOSDevTypeHardDrive); | |
75b89a82 | 630 | break; |
bba600dd | 631 | #endif |
75b89a82 A |
632 | |
633 | case FDISK_HFS: | |
634 | bvr = newFDiskBVRef( | |
635 | biosdev, partno, | |
636 | part->relsect, | |
637 | part, | |
638 | HFSInitPartition, | |
639 | HFSLoadFile, | |
57c72a9a | 640 | HFSReadFile, |
75b89a82 | 641 | HFSGetDirEntry, |
57c72a9a | 642 | HFSGetFileBlock, |
bba600dd | 643 | HFSGetUUID, |
57c72a9a | 644 | HFSGetDescription, |
f083c6c3 A |
645 | 0, |
646 | kBIOSDevTypeHardDrive); | |
75b89a82 A |
647 | break; |
648 | ||
bba600dd | 649 | #if UFS_SUPPORT |
75b89a82 | 650 | case FDISK_BOOTER: |
75b89a82 A |
651 | booterUFS = newFDiskBVRef( |
652 | biosdev, partno, | |
653 | ((part->relsect + spc - 1) / spc) * spc, | |
654 | part, | |
655 | UFSInitPartition, | |
656 | UFSLoadFile, | |
57c72a9a | 657 | UFSReadFile, |
75b89a82 | 658 | UFSGetDirEntry, |
57c72a9a | 659 | UFSGetFileBlock, |
bba600dd | 660 | UFSGetUUID, |
57c72a9a | 661 | UFSGetDescription, |
f083c6c3 A |
662 | 0, |
663 | kBIOSDevTypeHardDrive); | |
75b89a82 | 664 | break; |
bba600dd | 665 | #endif |
75b89a82 | 666 | |
57c72a9a A |
667 | case FDISK_NTFS: |
668 | bvr = newFDiskBVRef( | |
669 | biosdev, partno, | |
670 | part->relsect, | |
671 | part, | |
bba600dd | 672 | 0, 0, 0, 0, 0, 0, |
57c72a9a A |
673 | NTFSGetDescription, |
674 | 0, | |
675 | kBIOSDevTypeHardDrive); | |
676 | break; | |
677 | ||
75b89a82 A |
678 | default: |
679 | bvr = newFDiskBVRef( | |
680 | biosdev, partno, | |
681 | part->relsect, | |
682 | part, | |
bba600dd | 683 | 0, 0, 0, 0, 0, 0, 0, 0, |
f083c6c3 | 684 | kBIOSDevTypeHardDrive); |
75b89a82 A |
685 | break; |
686 | } | |
687 | ||
688 | if ( bvr ) | |
689 | { | |
690 | bvr->next = map->bvr; | |
691 | map->bvr = bvr; | |
692 | map->bvrcnt++; | |
693 | } | |
14c7c974 | 694 | } |
14c7c974 | 695 | |
bba600dd | 696 | #if UFS_SUPPORT |
75b89a82 A |
697 | // Booting from a CD with an UFS filesystem embedded |
698 | // in a booter partition. | |
699 | ||
700 | if ( booterUFS ) | |
701 | { | |
702 | if ( map->bvrcnt == 0 ) | |
703 | { | |
704 | map->bvr = booterUFS; | |
705 | map->bvrcnt++; | |
706 | } | |
707 | else free( booterUFS ); | |
708 | } | |
bba600dd | 709 | #endif |
75b89a82 A |
710 | } |
711 | } while (0); | |
712 | ||
f083c6c3 A |
713 | /* |
714 | * If no FDisk partition, then we will check for | |
715 | * an Apple partition map elsewhere. | |
716 | */ | |
57c72a9a | 717 | #if UNUSED |
f083c6c3 A |
718 | if (map->bvrcnt == 0) { |
719 | static struct fdisk_part cdpart; | |
720 | cdpart.systid = 0xCD; | |
721 | ||
722 | /* Let's try assuming we are on a hybrid HFS/ISO9660 CD. */ | |
723 | bvr = newFDiskBVRef( | |
724 | biosdev, 0, | |
725 | 0, | |
726 | &cdpart, | |
727 | HFSInitPartition, | |
728 | HFSLoadFile, | |
57c72a9a | 729 | HFSReadFile, |
f083c6c3 | 730 | HFSGetDirEntry, |
57c72a9a | 731 | HFSGetFileBlock, |
bba600dd | 732 | HFSGetUUID, |
f083c6c3 A |
733 | 0, |
734 | kBIOSDevTypeHardDrive); | |
735 | bvr->next = map->bvr; | |
736 | map->bvr = bvr; | |
737 | map->bvrcnt++; | |
738 | } | |
739 | #endif | |
740 | ||
75b89a82 A |
741 | if (countPtr) *countPtr = map ? map->bvrcnt : 0; |
742 | ||
743 | return map ? map->bvr : NULL; | |
744 | } | |
745 | ||
f083c6c3 A |
746 | //========================================================================== |
747 | ||
748 | static BVRef diskScanAPMBootVolumes( int biosdev, int * countPtr ) | |
749 | { | |
750 | struct DiskBVMap * map; | |
751 | struct Block0 *block0_p; | |
752 | unsigned int blksize; | |
753 | unsigned int factor; | |
754 | void *buffer = malloc(BPS); | |
755 | ||
756 | /* Check for alternate block size */ | |
bba600dd | 757 | if (readBytes( biosdev, 0, 0, BPS, buffer ) != 0) { |
f083c6c3 A |
758 | return NULL; |
759 | } | |
760 | block0_p = buffer; | |
57c72a9a A |
761 | if (OSSwapBigToHostInt16(block0_p->sbSig) == BLOCK0_SIGNATURE) { |
762 | blksize = OSSwapBigToHostInt16(block0_p->sbBlkSize); | |
f083c6c3 A |
763 | if (blksize != BPS) { |
764 | free(buffer); | |
765 | buffer = malloc(blksize); | |
766 | } | |
767 | factor = blksize / BPS; | |
768 | } else { | |
769 | blksize = BPS; | |
770 | factor = 1; | |
771 | } | |
772 | ||
773 | do { | |
774 | // Create a new mapping. | |
775 | ||
776 | map = (struct DiskBVMap *) malloc( sizeof(*map) ); | |
777 | if ( map ) | |
778 | { | |
779 | int error; | |
780 | DPME *dpme_p = (DPME *)buffer; | |
781 | UInt32 i, npart = UINT_MAX; | |
782 | BVRef bvr; | |
783 | ||
784 | map->biosdev = biosdev; | |
785 | map->bvr = NULL; | |
786 | map->bvrcnt = 0; | |
787 | map->next = gDiskBVMap; | |
788 | gDiskBVMap = map; | |
789 | ||
790 | for (i=0; i<npart; i++) { | |
bba600dd | 791 | error = readBytes( biosdev, (kAPMSector + i) * factor, 0, blksize, buffer ); |
f083c6c3 | 792 | |
57c72a9a | 793 | if (error || OSSwapBigToHostInt16(dpme_p->dpme_signature) != DPME_SIGNATURE) { |
f083c6c3 A |
794 | break; |
795 | } | |
796 | ||
797 | if (i==0) { | |
57c72a9a | 798 | npart = OSSwapBigToHostInt32(dpme_p->dpme_map_entries); |
f083c6c3 A |
799 | } |
800 | /* | |
801 | printf("name = %s, %s%s %d -> %d [%d -> %d] {%d}\n", | |
802 | dpme.dpme_name, dpme.dpme_type, (dpme.dpme_flags & DPME_FLAGS_BOOTABLE) ? "(bootable)" : "", | |
803 | dpme.dpme_pblock_start, dpme.dpme_pblocks, | |
804 | dpme.dpme_lblock_start, dpme.dpme_lblocks, | |
805 | dpme.dpme_boot_block); | |
806 | */ | |
807 | ||
808 | if (strcmp(dpme_p->dpme_type, "Apple_HFS") == 0) { | |
809 | bvr = newAPMBVRef(biosdev, | |
810 | i, | |
57c72a9a | 811 | OSSwapBigToHostInt32(dpme_p->dpme_pblock_start) * factor, |
f083c6c3 A |
812 | dpme_p, |
813 | HFSInitPartition, | |
814 | HFSLoadFile, | |
57c72a9a | 815 | HFSReadFile, |
f083c6c3 | 816 | HFSGetDirEntry, |
57c72a9a | 817 | HFSGetFileBlock, |
bba600dd | 818 | HFSGetUUID, |
57c72a9a | 819 | HFSGetDescription, |
f083c6c3 A |
820 | 0, |
821 | kBIOSDevTypeHardDrive); | |
822 | bvr->next = map->bvr; | |
823 | map->bvr = bvr; | |
824 | map->bvrcnt++; | |
825 | } | |
826 | } | |
827 | } | |
828 | } while (0); | |
829 | ||
830 | free(buffer); | |
831 | ||
832 | if (countPtr) *countPtr = map ? map->bvrcnt : 0; | |
833 | ||
834 | return map ? map->bvr : NULL; | |
835 | } | |
836 | ||
837 | //========================================================================== | |
838 | ||
839 | BVRef diskScanBootVolumes( int biosdev, int * countPtr ) | |
840 | { | |
841 | struct DiskBVMap * map; | |
842 | BVRef bvr; | |
843 | int count = 0; | |
844 | ||
845 | // Find an existing mapping for this device. | |
846 | ||
847 | for ( map = gDiskBVMap; map; map = map->next ) { | |
848 | if ( biosdev == map->biosdev ) { | |
849 | count = map->bvrcnt; | |
850 | break; | |
851 | } | |
852 | } | |
853 | ||
854 | if (map == NULL) { | |
855 | bvr = diskScanFDiskBootVolumes(biosdev, &count); | |
856 | if (bvr == NULL) { | |
857 | bvr = diskScanAPMBootVolumes(biosdev, &count); | |
858 | } | |
859 | } else { | |
860 | bvr = map->bvr; | |
861 | } | |
862 | if (countPtr) *countPtr = count; | |
863 | return bvr; | |
864 | } | |
865 | ||
866 | ||
75b89a82 A |
867 | //========================================================================== |
868 | ||
869 | static const struct NamedValue fdiskTypes[] = | |
870 | { | |
57c72a9a A |
871 | { FDISK_NTFS, "Windows NTFS" }, |
872 | { FDISK_FAT32, "Windows FAT32" }, | |
75b89a82 A |
873 | { 0x83, "Linux" }, |
874 | { FDISK_UFS, "Apple UFS" }, | |
875 | { FDISK_HFS, "Apple HFS" }, | |
876 | { FDISK_BOOTER, "Apple Boot/UFS" }, | |
f083c6c3 | 877 | { 0xCD, "CD-ROM" }, |
75b89a82 A |
878 | { 0x00, 0 } /* must be last */ |
879 | }; | |
880 | ||
57c72a9a A |
881 | //========================================================================== |
882 | ||
883 | void getBootVolumeDescription( BVRef bvr, char * str, long strMaxLen, BOOL verbose ) | |
75b89a82 A |
884 | { |
885 | unsigned char type = (unsigned char) bvr->part_type; | |
886 | const char * name = getNameForValue( fdiskTypes, type ); | |
57c72a9a A |
887 | char *p; |
888 | ||
889 | if (name == NULL) | |
890 | name = bvr->type_name; | |
891 | ||
892 | p = str; | |
893 | if ( name && verbose ) { | |
894 | sprintf( str, "hd(%d,%d) ", | |
895 | BIOS_DEV_UNIT(bvr), bvr->part_no); | |
896 | for (; strMaxLen > 0 && *p != '\0'; p++, strMaxLen--); | |
897 | } else { | |
898 | *p = '\0'; | |
899 | } | |
900 | bvr->description(bvr, p, strMaxLen); | |
901 | if (*p == '\0') { | |
902 | const char * name = getNameForValue( fdiskTypes, type ); | |
903 | if (name == NULL) { | |
904 | name = bvr->type_name; | |
905 | } | |
906 | if (name == NULL) { | |
907 | sprintf(p, "TYPE %02x", type); | |
908 | } else { | |
909 | strncpy(p, name, strMaxLen); | |
910 | } | |
911 | } | |
912 | } | |
913 | ||
914 | #if UNUSED | |
915 | //========================================================================== | |
916 | ||
917 | static int | |
918 | getFAT32VolumeDescription( BVRef bvr, char *str, long strMaxLen) | |
919 | { | |
920 | struct fat32_header { | |
921 | unsigned char code[3]; | |
922 | unsigned char oem_id[8]; | |
923 | unsigned char data[56]; | |
924 | unsigned long serial; | |
925 | unsigned char label[11]; | |
926 | unsigned char fsid[8]; | |
927 | unsigned char reserved[420]; | |
928 | unsigned short signature; | |
929 | } __attribute__((packed)); | |
930 | ||
931 | char *buf, *name; | |
932 | struct fat32_header *fat32_p; | |
933 | int label_len = sizeof(fat32_p->label); | |
934 | int error; | |
935 | ||
936 | buf = (char *)malloc(BPS); | |
937 | name = (char *)malloc(label_len + 1); | |
938 | fat32_p = (struct fat32_header *)buf; | |
939 | ||
940 | diskSeek(bvr, 0ULL); | |
941 | error = diskRead(bvr, (long)buf, BPS); | |
942 | if ( error ) return 0; | |
943 | ||
944 | if (fat32_p->signature != 0xaa55) return 0; | |
945 | ||
946 | if (strMaxLen < label_len) label_len = strMaxLen; | |
947 | strncpy(str, fat32_p->label, label_len); | |
948 | str[label_len] = '\0'; | |
949 | return 1; | |
950 | } | |
951 | #endif | |
952 | ||
953 | //========================================================================== | |
954 | ||
955 | static void getVolumeDescription( BVRef bvr, char * str, long strMaxLen ) | |
956 | { | |
957 | unsigned char type = (unsigned char) bvr->part_type; | |
958 | const char * name = NULL; | |
959 | ||
960 | /* First try a few types that we can figure out the | |
961 | * volume description. | |
962 | */ | |
963 | switch(type) { | |
964 | case FDISK_FAT32: | |
965 | str[0] = '\0'; | |
966 | MSDOSGetDescription(bvr, str, strMaxLen); | |
967 | if (str[0] != '\0') | |
968 | return; | |
969 | break; | |
970 | ||
971 | default: // Not one of our known types | |
972 | break; | |
973 | } | |
974 | ||
975 | if (name == NULL) | |
976 | name = getNameForValue( fdiskTypes, type ); | |
75b89a82 | 977 | |
f083c6c3 A |
978 | if (name == NULL) |
979 | name = bvr->type_name; | |
980 | ||
75b89a82 | 981 | if ( name ) |
57c72a9a | 982 | strncpy( str, name, strMaxLen); |
75b89a82 | 983 | else |
57c72a9a | 984 | sprintf( str, "TYPE %02x", type); |
75b89a82 A |
985 | } |
986 | ||
f083c6c3 | 987 | |
75b89a82 A |
988 | //========================================================================== |
989 | ||
990 | int readBootSector( int biosdev, unsigned int secno, void * buffer ) | |
991 | { | |
992 | struct disk_blk0 * bootSector = (struct disk_blk0 *) buffer; | |
993 | int error; | |
994 | ||
995 | if ( bootSector == NULL ) | |
996 | { | |
997 | if ( gBootSector == NULL ) | |
998 | { | |
999 | gBootSector = (struct disk_blk0 *) malloc(sizeof(*gBootSector)); | |
1000 | if ( gBootSector == NULL ) return -1; | |
14c7c974 | 1001 | } |
75b89a82 | 1002 | bootSector = gBootSector; |
14c7c974 | 1003 | } |
75b89a82 | 1004 | |
bba600dd | 1005 | error = readBytes( biosdev, secno, 0, BPS, bootSector ); |
75b89a82 A |
1006 | if ( error || bootSector->signature != DISK_SIGNATURE ) |
1007 | return -1; | |
1008 | ||
1009 | return 0; | |
1010 | } | |
1011 | ||
1012 | //========================================================================== | |
1013 | // Handle seek request from filesystem modules. | |
1014 | ||
1015 | void diskSeek( BVRef bvr, long long position ) | |
1016 | { | |
1017 | bvr->fs_boff = position / BPS; | |
bba600dd | 1018 | bvr->fs_byteoff = position % BPS; |
75b89a82 A |
1019 | } |
1020 | ||
1021 | //========================================================================== | |
1022 | // Handle read request from filesystem modules. | |
1023 | ||
1024 | int diskRead( BVRef bvr, long addr, long length ) | |
1025 | { | |
1026 | return readBytes( bvr->biosdev, | |
1027 | bvr->fs_boff + bvr->part_boff, | |
bba600dd | 1028 | bvr->fs_byteoff, |
75b89a82 A |
1029 | length, |
1030 | (void *) addr ); | |
14c7c974 | 1031 | } |
f083c6c3 | 1032 | |
57c72a9a A |
1033 | int rawDiskRead( BVRef bvr, unsigned int secno, void *buffer, unsigned int len ) |
1034 | { | |
1035 | int secs; | |
1036 | unsigned char *cbuf = (unsigned char *)buffer; | |
1037 | unsigned int copy_len; | |
1038 | int rc; | |
1039 | ||
1040 | if ((len & (BPS-1)) != 0) { | |
1041 | error("raw disk read not sector aligned"); | |
1042 | return -1; | |
1043 | } | |
1044 | secno += bvr->part_boff; | |
1045 | ||
1046 | cache_valid = FALSE; | |
1047 | ||
1048 | while (len > 0) { | |
1049 | secs = len / BPS; | |
1050 | if (secs > N_CACHE_SECS) secs = N_CACHE_SECS; | |
1051 | copy_len = secs * BPS; | |
1052 | ||
1053 | //printf("rdr: ebiosread(%d, %d, %d)\n", bvr->biosdev, secno, secs); | |
1054 | if ((rc = ebiosread(bvr->biosdev, secno, secs)) != 0) { | |
1055 | /* Ignore corrected ECC errors */ | |
1056 | if (rc != ECC_CORRECTED_ERR) { | |
1057 | error(" EBIOS read error: %s\n", bios_error(rc), rc); | |
1058 | error(" Block %d Sectors %d\n", secno, secs); | |
1059 | return rc; | |
1060 | } | |
1061 | } | |
1062 | bcopy( trackbuf, cbuf, copy_len ); | |
1063 | len -= copy_len; | |
1064 | cbuf += copy_len; | |
1065 | secno += secs; | |
1066 | spinActivityIndicator(); | |
1067 | } | |
1068 | ||
1069 | return 0; | |
1070 | } | |
1071 | ||
1072 | int rawDiskWrite( BVRef bvr, unsigned int secno, void *buffer, unsigned int len ) | |
1073 | { | |
1074 | int secs; | |
1075 | unsigned char *cbuf = (unsigned char *)buffer; | |
1076 | unsigned int copy_len; | |
1077 | int rc; | |
1078 | ||
1079 | if ((len & (BPS-1)) != 0) { | |
1080 | error("raw disk write not sector aligned"); | |
1081 | return -1; | |
1082 | } | |
1083 | secno += bvr->part_boff; | |
1084 | ||
1085 | cache_valid = FALSE; | |
1086 | ||
1087 | while (len > 0) { | |
1088 | secs = len / BPS; | |
1089 | if (secs > N_CACHE_SECS) secs = N_CACHE_SECS; | |
1090 | copy_len = secs * BPS; | |
1091 | ||
1092 | bcopy( cbuf, trackbuf, copy_len ); | |
1093 | //printf("rdr: ebioswrite(%d, %d, %d)\n", bvr->biosdev, secno, secs); | |
1094 | if ((rc = ebioswrite(bvr->biosdev, secno, secs)) != 0) { | |
1095 | error(" EBIOS write error: %s\n", bios_error(rc), rc); | |
1096 | error(" Block %d Sectors %d\n", secno, secs); | |
1097 | return rc; | |
1098 | } | |
1099 | len -= copy_len; | |
1100 | cbuf += copy_len; | |
1101 | secno += secs; | |
1102 | spinActivityIndicator(); | |
1103 | } | |
1104 | ||
1105 | return 0; | |
1106 | } | |
1107 | ||
bba600dd A |
1108 | |
1109 | int diskIsCDROM(BVRef bvr) | |
f083c6c3 | 1110 | { |
bba600dd A |
1111 | struct driveInfo di; |
1112 | ||
1113 | if (getDriveInfo(bvr->biosdev, &di) == 0 && di.no_emulation) { | |
1114 | return 1; | |
1115 | } | |
1116 | return 0; | |
f083c6c3 | 1117 | } |