| /* bios.c - implement C part of low-level BIOS disk input and output */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 1999,2000,2003,2004 Free Software Foundation, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include "shared.h" |
| |
| |
| /* These are defined in asm.S, and never be used elsewhere, so declare the |
| prototypes here. */ |
| extern int biosdisk_int13_extensions (int ax, int drive, void *dap); |
| extern int biosdisk_standard (int ah, int drive, |
| int coff, int hoff, int soff, |
| int nsec, int segment); |
| extern int check_int13_extensions (int drive); |
| extern int get_diskinfo_standard (int drive, |
| unsigned long *cylinders, |
| unsigned long *heads, |
| unsigned long *sectors); |
| #if 0 |
| extern int get_diskinfo_floppy (int drive, |
| unsigned long *cylinders, |
| unsigned long *heads, |
| unsigned long *sectors); |
| #endif |
| |
| |
| /* Read/write NSEC sectors starting from SECTOR in DRIVE disk with GEOMETRY |
| from/into SEGMENT segment. If READ is BIOSDISK_READ, then read it, |
| else if READ is BIOSDISK_WRITE, then write it. If an geometry error |
| occurs, return BIOSDISK_ERROR_GEOMETRY, and if other error occurs, then |
| return the error number. Otherwise, return 0. */ |
| int |
| biosdisk (int read, int drive, struct geometry *geometry, |
| int sector, int nsec, int segment) |
| { |
| int err; |
| |
| if (geometry->flags & BIOSDISK_FLAG_LBA_EXTENSION) |
| { |
| struct disk_address_packet |
| { |
| unsigned char length; |
| unsigned char reserved; |
| unsigned short blocks; |
| unsigned long buffer; |
| unsigned long long block; |
| } __attribute__ ((packed)) dap; |
| |
| /* XXX: Don't check the geometry by default, because some buggy |
| BIOSes don't return the number of total sectors correctly, |
| even if they have working LBA support. Hell. */ |
| #ifdef NO_BUGGY_BIOS_IN_THE_WORLD |
| if (sector >= geometry->total_sectors) |
| return BIOSDISK_ERROR_GEOMETRY; |
| #endif /* NO_BUGGY_BIOS_IN_THE_WORLD */ |
| |
| /* FIXME: sizeof (DAP) must be 0x10. Should assert that the compiler |
| can't add any padding. */ |
| dap.length = sizeof (dap); |
| dap.block = sector; |
| dap.blocks = nsec; |
| dap.reserved = 0; |
| /* This is undocumented part. The address is formated in |
| SEGMENT:ADDRESS. */ |
| dap.buffer = segment << 16; |
| |
| err = biosdisk_int13_extensions ((read + 0x42) << 8, drive, &dap); |
| |
| /* #undef NO_INT13_FALLBACK */ |
| #ifndef NO_INT13_FALLBACK |
| if (err) |
| { |
| if (geometry->flags & BIOSDISK_FLAG_CDROM) |
| return err; |
| |
| geometry->flags &= ~BIOSDISK_FLAG_LBA_EXTENSION; |
| geometry->total_sectors = (geometry->cylinders |
| * geometry->heads |
| * geometry->sectors); |
| return biosdisk (read, drive, geometry, sector, nsec, segment); |
| } |
| #endif /* ! NO_INT13_FALLBACK */ |
| |
| } |
| else |
| { |
| int cylinder_offset, head_offset, sector_offset; |
| int head; |
| |
| /* SECTOR_OFFSET is counted from one, while HEAD_OFFSET and |
| CYLINDER_OFFSET are counted from zero. */ |
| sector_offset = sector % geometry->sectors + 1; |
| head = sector / geometry->sectors; |
| head_offset = head % geometry->heads; |
| cylinder_offset = head / geometry->heads; |
| |
| if (cylinder_offset >= geometry->cylinders) |
| return BIOSDISK_ERROR_GEOMETRY; |
| |
| err = biosdisk_standard (read + 0x02, drive, |
| cylinder_offset, head_offset, sector_offset, |
| nsec, segment); |
| } |
| |
| return err; |
| } |
| |
| /* Check bootable CD-ROM emulation status. */ |
| static int |
| get_cdinfo (int drive, struct geometry *geometry) |
| { |
| int err; |
| struct iso_spec_packet |
| { |
| unsigned char size; |
| unsigned char media_type; |
| unsigned char drive_no; |
| unsigned char controller_no; |
| unsigned long image_lba; |
| unsigned short device_spec; |
| unsigned short cache_seg; |
| unsigned short load_seg; |
| unsigned short length_sec512; |
| unsigned char cylinders; |
| unsigned char sectors; |
| unsigned char heads; |
| |
| unsigned char dummy[16]; |
| } __attribute__ ((packed)) cdrp; |
| |
| grub_memset (&cdrp, 0, sizeof (cdrp)); |
| cdrp.size = sizeof (cdrp) - sizeof (cdrp.dummy); |
| err = biosdisk_int13_extensions (0x4B01, drive, &cdrp); |
| if (! err && cdrp.drive_no == drive) |
| { |
| if ((cdrp.media_type & 0x0F) == 0) |
| { |
| /* No emulation bootable CD-ROM */ |
| geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION | BIOSDISK_FLAG_CDROM; |
| geometry->cylinders = 0; |
| geometry->heads = 1; |
| geometry->sectors = 15; |
| geometry->sector_size = 2048; |
| geometry->total_sectors = MAXINT; |
| return 1; |
| } |
| else |
| { |
| /* Floppy or hard-disk emulation */ |
| geometry->cylinders |
| = ((unsigned int) cdrp.cylinders |
| + (((unsigned int) (cdrp.sectors & 0xC0)) << 2)); |
| geometry->heads = cdrp.heads; |
| geometry->sectors = cdrp.sectors & 0x3F; |
| geometry->sector_size = SECTOR_SIZE; |
| geometry->total_sectors = (geometry->cylinders |
| * geometry->heads |
| * geometry->sectors); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| /* Return the geometry of DRIVE in GEOMETRY. If an error occurs, return |
| non-zero, otherwise zero. */ |
| int |
| get_diskinfo (int drive, struct geometry *geometry) |
| { |
| int err; |
| |
| /* Clear the flags. */ |
| geometry->flags = 0; |
| |
| if (drive & 0x80) |
| { |
| /* hard disk or CD-ROM */ |
| int version; |
| unsigned long total_sectors = 0; |
| |
| version = check_int13_extensions (drive); |
| |
| if (drive >= 0x88 || version) |
| { |
| /* Possible CD-ROM - check the status. */ |
| if (get_cdinfo (drive, geometry)) |
| return 0; |
| } |
| |
| if (version) |
| { |
| struct drive_parameters |
| { |
| unsigned short size; |
| unsigned short flags; |
| unsigned long cylinders; |
| unsigned long heads; |
| unsigned long sectors; |
| unsigned long long total_sectors; |
| unsigned short bytes_per_sector; |
| /* ver 2.0 or higher */ |
| unsigned long EDD_configuration_parameters; |
| /* ver 3.0 or higher */ |
| unsigned short signature_dpi; |
| unsigned char length_dpi; |
| unsigned char reserved[3]; |
| unsigned char name_of_host_bus[4]; |
| unsigned char name_of_interface_type[8]; |
| unsigned char interface_path[8]; |
| unsigned char device_path[8]; |
| unsigned char reserved2; |
| unsigned char checksum; |
| |
| /* XXX: This is necessary, because the BIOS of Thinkpad X20 |
| writes a garbage to the tail of drive parameters, |
| regardless of a size specified in a caller. */ |
| unsigned char dummy[16]; |
| } __attribute__ ((packed)) drp; |
| |
| /* It is safe to clear out DRP. */ |
| grub_memset (&drp, 0, sizeof (drp)); |
| |
| /* PhoenixBIOS 4.0 Revision 6.0 for ZF Micro might understand |
| the greater buffer size for the "get drive parameters" int |
| 0x13 call in its own way. Supposedly the BIOS assumes even |
| bigger space is available and thus corrupts the stack. |
| This is why we specify the exactly necessary size of 0x42 |
| bytes. */ |
| drp.size = sizeof (drp) - sizeof (drp.dummy); |
| |
| err = biosdisk_int13_extensions (0x4800, drive, &drp); |
| if (! err) |
| { |
| /* Set the LBA flag. */ |
| geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION; |
| |
| /* I'm not sure if GRUB should check the bit 1 of DRP.FLAGS, |
| so I omit the check for now. - okuji */ |
| /* if (drp.flags & (1 << 1)) */ |
| |
| /* FIXME: when the 2TB limit becomes critical, we must |
| change the type of TOTAL_SECTORS to unsigned long |
| long. */ |
| if (drp.total_sectors) |
| total_sectors = drp.total_sectors & ~0L; |
| else |
| /* Some buggy BIOSes doesn't return the total sectors |
| correctly but returns zero. So if it is zero, compute |
| it by C/H/S returned by the LBA BIOS call. */ |
| total_sectors = drp.cylinders * drp.heads * drp.sectors; |
| } |
| } |
| |
| /* Don't pass GEOMETRY directly, but pass each element instead, |
| so that we can change the structure easily. */ |
| err = get_diskinfo_standard (drive, |
| &geometry->cylinders, |
| &geometry->heads, |
| &geometry->sectors); |
| if (err) |
| return err; |
| |
| if (! total_sectors) |
| { |
| total_sectors = (geometry->cylinders |
| * geometry->heads |
| * geometry->sectors); |
| } |
| geometry->total_sectors = total_sectors; |
| geometry->sector_size = SECTOR_SIZE; |
| } |
| else |
| { |
| /* floppy disk */ |
| |
| /* First, try INT 13 AH=8h call. */ |
| err = get_diskinfo_standard (drive, |
| &geometry->cylinders, |
| &geometry->heads, |
| &geometry->sectors); |
| |
| #if 0 |
| /* If fails, then try floppy-specific probe routine. */ |
| if (err) |
| err = get_diskinfo_floppy (drive, |
| &geometry->cylinders, |
| &geometry->heads, |
| &geometry->sectors); |
| #endif |
| |
| if (err) |
| return err; |
| |
| geometry->total_sectors = (geometry->cylinders |
| * geometry->heads |
| * geometry->sectors); |
| geometry->sector_size = SECTOR_SIZE; |
| } |
| |
| return 0; |
| } |