|  | /* bios.c - implement C part of low-level BIOS disk input and output */ | 
|  | /* | 
|  | *  GRUB  --  GRand Unified Bootloader | 
|  | *  Copyright (C) 1999,2000  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 ah, 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_int13_extensions (int drive, void *drp); | 
|  | extern int get_diskinfo_standard (int drive, | 
|  | unsigned long *cylinders, | 
|  | unsigned long *heads, | 
|  | unsigned long *sectors); | 
|  | extern int get_diskinfo_floppy (int drive, | 
|  | unsigned long *cylinders, | 
|  | unsigned long *heads, | 
|  | unsigned long *sectors); | 
|  |  | 
|  |  | 
|  | /* 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; | 
|  | } 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, drive, &dap); | 
|  |  | 
|  | /* #undef NO_INT13_FALLBACK */ | 
|  | #ifndef NO_INT13_FALLBACK | 
|  | if (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; | 
|  | } | 
|  |  | 
|  | /* 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 */ | 
|  | int version; | 
|  | unsigned long total_sectors = 0; | 
|  |  | 
|  | version = check_int13_extensions (drive); | 
|  | 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; | 
|  |  | 
|  | drp.size = sizeof (drp); | 
|  | err = get_diskinfo_int13_extensions (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; | 
|  | } | 
|  | else | 
|  | { | 
|  | /* floppy disk */ | 
|  |  | 
|  | /* First, try INT 13 AH=8h call.  */ | 
|  | err = get_diskinfo_standard (drive, | 
|  | &geometry->cylinders, | 
|  | &geometry->heads, | 
|  | &geometry->sectors); | 
|  |  | 
|  | /* If fails, then try floppy-specific probe routine.  */ | 
|  | if (err) | 
|  | err = get_diskinfo_floppy (drive, | 
|  | &geometry->cylinders, | 
|  | &geometry->heads, | 
|  | &geometry->sectors); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | geometry->total_sectors = (geometry->cylinders | 
|  | * geometry->heads | 
|  | * geometry->sectors); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } |