17c478bdstevel@tonic-gate/* bios.c - implement C part of low-level BIOS disk input and output */
37c478bdstevel@tonic-gate *  GRUB  --  GRand Unified Bootloader
47c478bdstevel@tonic-gate *  Copyright (C) 1999,2000,2003,2004  Free Software Foundation, Inc.
57c478bdstevel@tonic-gate *
67c478bdstevel@tonic-gate *  This program is free software; you can redistribute it and/or modify
77c478bdstevel@tonic-gate *  it under the terms of the GNU General Public License as published by
87c478bdstevel@tonic-gate *  the Free Software Foundation; either version 2 of the License, or
97c478bdstevel@tonic-gate *  (at your option) any later version.
107c478bdstevel@tonic-gate *
117c478bdstevel@tonic-gate *  This program is distributed in the hope that it will be useful,
127c478bdstevel@tonic-gate *  but WITHOUT ANY WARRANTY; without even the implied warranty of
137c478bdstevel@tonic-gate *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
147c478bdstevel@tonic-gate *  GNU General Public License for more details.
157c478bdstevel@tonic-gate *
167c478bdstevel@tonic-gate *  You should have received a copy of the GNU General Public License
177c478bdstevel@tonic-gate *  along with this program; if not, write to the Free Software
187c478bdstevel@tonic-gate *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
197c478bdstevel@tonic-gate */
209890706Hans Rosenfeld/*
219890706Hans Rosenfeld * Copyright 2016 Nexenta Systems, Inc.
229890706Hans Rosenfeld */
247c478bdstevel@tonic-gate#include "shared.h"
277c478bdstevel@tonic-gate/* These are defined in asm.S, and never be used elsewhere, so declare the
287c478bdstevel@tonic-gate   prototypes here.  */
297c478bdstevel@tonic-gateextern int biosdisk_int13_extensions (int ax, int drive, void *dap);
307c478bdstevel@tonic-gateextern int biosdisk_standard (int ah, int drive,
317c478bdstevel@tonic-gate			      int coff, int hoff, int soff,
327c478bdstevel@tonic-gate			      int nsec, int segment);
337c478bdstevel@tonic-gateextern int check_int13_extensions (int drive);
347c478bdstevel@tonic-gateextern int get_diskinfo_standard (int drive,
357c478bdstevel@tonic-gate				  unsigned long *cylinders,
367c478bdstevel@tonic-gate				  unsigned long *heads,
377c478bdstevel@tonic-gate				  unsigned long *sectors);
387c478bdstevel@tonic-gate#if 0
397c478bdstevel@tonic-gateextern int get_diskinfo_floppy (int drive,
407c478bdstevel@tonic-gate				unsigned long *cylinders,
417c478bdstevel@tonic-gate				unsigned long *heads,
427c478bdstevel@tonic-gate				unsigned long *sectors);
467c478bdstevel@tonic-gate/* Read/write NSEC sectors starting from SECTOR in DRIVE disk with GEOMETRY
477c478bdstevel@tonic-gate   from/into SEGMENT segment. If READ is BIOSDISK_READ, then read it,
487c478bdstevel@tonic-gate   else if READ is BIOSDISK_WRITE, then write it. If an geometry error
497c478bdstevel@tonic-gate   occurs, return BIOSDISK_ERROR_GEOMETRY, and if other error occurs, then
507c478bdstevel@tonic-gate   return the error number. Otherwise, return 0.  */
527c478bdstevel@tonic-gatebiosdisk (int read, int drive, struct geometry *geometry,
539890706Hans Rosenfeld	  unsigned long long sector, int nsec, int segment)
55342440ePrasad Singamsetty
567c478bdstevel@tonic-gate  int err;
587c478bdstevel@tonic-gate  if (geometry->flags & BIOSDISK_FLAG_LBA_EXTENSION)
597c478bdstevel@tonic-gate    {
607c478bdstevel@tonic-gate      struct disk_address_packet
617c478bdstevel@tonic-gate      {
627c478bdstevel@tonic-gate	unsigned char length;
637c478bdstevel@tonic-gate	unsigned char reserved;
647c478bdstevel@tonic-gate	unsigned short blocks;
657c478bdstevel@tonic-gate	unsigned long buffer;
667c478bdstevel@tonic-gate	unsigned long long block;
677c478bdstevel@tonic-gate      } __attribute__ ((packed)) dap;
697c478bdstevel@tonic-gate      /* XXX: Don't check the geometry by default, because some buggy
707c478bdstevel@tonic-gate	 BIOSes don't return the number of total sectors correctly,
717c478bdstevel@tonic-gate	 even if they have working LBA support. Hell.  */
727c478bdstevel@tonic-gate#ifdef NO_BUGGY_BIOS_IN_THE_WORLD
737c478bdstevel@tonic-gate      if (sector >= geometry->total_sectors)
747c478bdstevel@tonic-gate	return BIOSDISK_ERROR_GEOMETRY;
757c478bdstevel@tonic-gate#endif /* NO_BUGGY_BIOS_IN_THE_WORLD */
777c478bdstevel@tonic-gate      /* FIXME: sizeof (DAP) must be 0x10. Should assert that the compiler
787c478bdstevel@tonic-gate	 can't add any padding.  */
797c478bdstevel@tonic-gate      dap.length = sizeof (dap);
807c478bdstevel@tonic-gate      dap.block = sector;
817c478bdstevel@tonic-gate      dap.blocks = nsec;
827c478bdstevel@tonic-gate      dap.reserved = 0;
837c478bdstevel@tonic-gate      /* This is undocumented part. The address is formated in
847c478bdstevel@tonic-gate	 SEGMENT:ADDRESS.  */
857c478bdstevel@tonic-gate      dap.buffer = segment << 16;
867c478bdstevel@tonic-gate      err = biosdisk_int13_extensions ((read + 0x42) << 8, drive, &dap);
879caa148szhou      /*
889caa148szhou       * Try to report errors upwards when the bios has read only part of
899caa148szhou       * the requested buffer, but didn't return an error code.
909caa148szhou       */
919caa148szhou      if (err == 0 && dap.blocks != nsec)
929caa148szhou	err = BIOSDISK_ERROR_SHORT_IO;
947c478bdstevel@tonic-gate/* #undef NO_INT13_FALLBACK */
957c478bdstevel@tonic-gate#ifndef NO_INT13_FALLBACK
967c478bdstevel@tonic-gate      if (err)
977c478bdstevel@tonic-gate	{
987c478bdstevel@tonic-gate	  if (geometry->flags & BIOSDISK_FLAG_CDROM)
997c478bdstevel@tonic-gate	    return err;
1017c478bdstevel@tonic-gate	  geometry->flags &= ~BIOSDISK_FLAG_LBA_EXTENSION;
102828d47cShidokht Yadegari	  geometry->total_sectors = ((unsigned long long)geometry->cylinders
1037c478bdstevel@tonic-gate				     * geometry->heads
1047c478bdstevel@tonic-gate				     * geometry->sectors);
1057c478bdstevel@tonic-gate	  return biosdisk (read, drive, geometry, sector, nsec, segment);
1067c478bdstevel@tonic-gate	}
1077c478bdstevel@tonic-gate#endif /* ! NO_INT13_FALLBACK */
1097c478bdstevel@tonic-gate    }
1107c478bdstevel@tonic-gate  else
1117c478bdstevel@tonic-gate    {
1127c478bdstevel@tonic-gate      int cylinder_offset, head_offset, sector_offset;
1137c478bdstevel@tonic-gate      int head;
1147c478bdstevel@tonic-gate      /* SECTOR_OFFSET is counted from one, while HEAD_OFFSET and
1157c478bdstevel@tonic-gate	 CYLINDER_OFFSET are counted from zero.  */
1167c478bdstevel@tonic-gate      sector_offset = sector % geometry->sectors + 1;
1177c478bdstevel@tonic-gate      head = sector / geometry->sectors;
1187c478bdstevel@tonic-gate      head_offset = head % geometry->heads;
1197c478bdstevel@tonic-gate      cylinder_offset = head / geometry->heads;
1217c478bdstevel@tonic-gate      if (cylinder_offset >= geometry->cylinders)
1227c478bdstevel@tonic-gate	return BIOSDISK_ERROR_GEOMETRY;
1247c478bdstevel@tonic-gate      err = biosdisk_standard (read + 0x02, drive,
1257c478bdstevel@tonic-gate			       cylinder_offset, head_offset, sector_offset,
1267c478bdstevel@tonic-gate			       nsec, segment);
1277c478bdstevel@tonic-gate    }
1297c478bdstevel@tonic-gate  return err;
1327c478bdstevel@tonic-gate/* Check bootable CD-ROM emulation status.  */
1337c478bdstevel@tonic-gatestatic int
1347c478bdstevel@tonic-gateget_cdinfo (int drive, struct geometry *geometry)
1367c478bdstevel@tonic-gate  int err;
1377c478bdstevel@tonic-gate  struct iso_spec_packet
1387c478bdstevel@tonic-gate  {
1397c478bdstevel@tonic-gate    unsigned char size;
1407c478bdstevel@tonic-gate    unsigned char media_type;
1417c478bdstevel@tonic-gate    unsigned char drive_no;
1427c478bdstevel@tonic-gate    unsigned char controller_no;
1437c478bdstevel@tonic-gate    unsigned long image_lba;
1447c478bdstevel@tonic-gate    unsigned short device_spec;
1457c478bdstevel@tonic-gate    unsigned short cache_seg;
1467c478bdstevel@tonic-gate    unsigned short load_seg;
1477c478bdstevel@tonic-gate    unsigned short length_sec512;
1487c478bdstevel@tonic-gate    unsigned char cylinders;
1497c478bdstevel@tonic-gate    unsigned char sectors;
1507c478bdstevel@tonic-gate    unsigned char heads;
1527c478bdstevel@tonic-gate    unsigned char dummy[16];
1537c478bdstevel@tonic-gate  } __attribute__ ((packed)) cdrp;
1557c478bdstevel@tonic-gate  grub_memset (&cdrp, 0, sizeof (cdrp));
1567c478bdstevel@tonic-gate  cdrp.size = sizeof (cdrp) - sizeof (cdrp.dummy);
1577c478bdstevel@tonic-gate  err = biosdisk_int13_extensions (0x4B01, drive, &cdrp);
1587c478bdstevel@tonic-gate  if (! err && cdrp.drive_no == drive)
1597c478bdstevel@tonic-gate    {
1607c478bdstevel@tonic-gate      if ((cdrp.media_type & 0x0F) == 0)
1617c478bdstevel@tonic-gate        {
1627c478bdstevel@tonic-gate          /* No emulation bootable CD-ROM */
1637c478bdstevel@tonic-gate          geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION | BIOSDISK_FLAG_CDROM;
1647c478bdstevel@tonic-gate          geometry->cylinders = 0;
1657c478bdstevel@tonic-gate          geometry->heads = 1;
1667c478bdstevel@tonic-gate          geometry->sectors = 15;
1677c478bdstevel@tonic-gate          geometry->sector_size = 2048;
16898c507cJan Setje-Eilers          geometry->total_sectors = MAXUINT;
1697c478bdstevel@tonic-gate          return 1;
1707c478bdstevel@tonic-gate        }
1717c478bdstevel@tonic-gate      else
1727c478bdstevel@tonic-gate        {
1737c478bdstevel@tonic-gate	  /* Floppy or hard-disk emulation */
1747c478bdstevel@tonic-gate          geometry->cylinders
1757c478bdstevel@tonic-gate	    = ((unsigned int) cdrp.cylinders
1767c478bdstevel@tonic-gate	       + (((unsigned int) (cdrp.sectors & 0xC0)) << 2));
1777c478bdstevel@tonic-gate          geometry->heads = cdrp.heads;
1787c478bdstevel@tonic-gate          geometry->sectors = cdrp.sectors & 0x3F;
1797c478bdstevel@tonic-gate          geometry->sector_size = SECTOR_SIZE;
180828d47cShidokht Yadegari          geometry->total_sectors = ((unsigned long long)geometry->cylinders
1817c478bdstevel@tonic-gate				     * geometry->heads
1827c478bdstevel@tonic-gate				     * geometry->sectors);
1837c478bdstevel@tonic-gate          return -1;
1847c478bdstevel@tonic-gate        }
1857c478bdstevel@tonic-gate    }
1877c478bdstevel@tonic-gate  /*
1887c478bdstevel@tonic-gate   * If this is the boot_drive, default to non-emulation bootable CD-ROM.
1897c478bdstevel@tonic-gate   *
1907c478bdstevel@tonic-gate   * Some BIOS (Tecra S1) fails the int13 call above. If we return
1917c478bdstevel@tonic-gate   * failure here, GRUB will run, but cannot see the boot drive,
1927c478bdstevel@tonic-gate   * not a very good situation. Defaulting to non-emulation mode
1937c478bdstevel@tonic-gate   * is a last-ditch effort.
1947c478bdstevel@tonic-gate   */
1957c478bdstevel@tonic-gate  if (drive >= 0x88 && drive == boot_drive)
1967c478bdstevel@tonic-gate    {
1977c478bdstevel@tonic-gate      geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION | BIOSDISK_FLAG_CDROM;
1987c478bdstevel@tonic-gate      geometry->cylinders = 0;
1997c478bdstevel@tonic-gate      geometry->heads = 1;
2007c478bdstevel@tonic-gate      geometry->sectors = 15;
2017c478bdstevel@tonic-gate      geometry->sector_size = 2048;
20298c507cJan Setje-Eilers      geometry->total_sectors = MAXUINT;
2037c478bdstevel@tonic-gate      return 1;
2047c478bdstevel@tonic-gate    }
2057c478bdstevel@tonic-gate  return 0;
2087c478bdstevel@tonic-gate/* Return the geometry of DRIVE in GEOMETRY. If an error occurs, return
2097c478bdstevel@tonic-gate   non-zero, otherwise zero.  */
2117c478bdstevel@tonic-gateget_diskinfo (int drive, struct geometry *geometry)
2137c478bdstevel@tonic-gate  int err;
2142f7f7a6Alex Wilson  int gotchs = 0;
2167c478bdstevel@tonic-gate  /* Clear the flags.  */
2177c478bdstevel@tonic-gate  geometry->flags = 0;
2197c478bdstevel@tonic-gate  if (drive & 0x80)
2207c478bdstevel@tonic-gate    {
2217c478bdstevel@tonic-gate      /* hard disk or CD-ROM */
2227c478bdstevel@tonic-gate      int version;
223828d47cShidokht Yadegari      unsigned long long total_sectors = 0;
2257c478bdstevel@tonic-gate      version = check_int13_extensions (drive);
2277c478bdstevel@tonic-gate      if (drive >= 0x88 || version)
2287c478bdstevel@tonic-gate	{
2297c478bdstevel@tonic-gate	  /* Possible CD-ROM - check the status.  */
2307c478bdstevel@tonic-gate	  if (get_cdinfo (drive, geometry))
2317c478bdstevel@tonic-gate	    return 0;
2327c478bdstevel@tonic-gate	}
2332f7f7a6Alex Wilson
2342f7f7a6Alex Wilson      /* Don't pass GEOMETRY directly, but pass each element instead,
2352f7f7a6Alex Wilson	 so that we can change the structure easily.  */
2362f7f7a6Alex Wilson      err = get_diskinfo_standard (drive,
2372f7f7a6Alex Wilson				   &geometry->cylinders,
2382f7f7a6Alex Wilson				   &geometry->heads,
2392f7f7a6Alex Wilson				   &geometry->sectors);
2402f7f7a6Alex Wilson      if (err == 0)
2412f7f7a6Alex Wilson	gotchs = 1;
2422f7f7a6Alex Wilson      /* get_diskinfo_standard returns 0x60 if the BIOS call actually
2432f7f7a6Alex Wilson	 succeeded but returned 0 sectors -- in this case don't
2442f7f7a6Alex Wilson	 return yet but continue to check the LBA geom */
2452f7f7a6Alex Wilson      else if (err != 0x60)
2462f7f7a6Alex Wilson	return err;
2487c478bdstevel@tonic-gate      if (version)
2497c478bdstevel@tonic-gate	{
2507c478bdstevel@tonic-gate	  struct drive_parameters
2517c478bdstevel@tonic-gate	  {
2527c478bdstevel@tonic-gate	    unsigned short size;
2537c478bdstevel@tonic-gate	    unsigned short flags;
2547c478bdstevel@tonic-gate	    unsigned long cylinders;
2557c478bdstevel@tonic-gate	    unsigned long heads;
2567c478bdstevel@tonic-gate	    unsigned long sectors;
2577c478bdstevel@tonic-gate	    unsigned long long total_sectors;
2587c478bdstevel@tonic-gate	    unsigned short bytes_per_sector;
2597c478bdstevel@tonic-gate	    /* ver 2.0 or higher */
2607c478bdstevel@tonic-gate	    unsigned long EDD_configuration_parameters;
2617c478bdstevel@tonic-gate	    /* ver 3.0 or higher */
2627c478bdstevel@tonic-gate	    unsigned short signature_dpi;
2637c478bdstevel@tonic-gate	    unsigned char length_dpi;
2647c478bdstevel@tonic-gate	    unsigned char reserved[3];
2657c478bdstevel@tonic-gate	    unsigned char name_of_host_bus[4];
2667c478bdstevel@tonic-gate	    unsigned char name_of_interface_type[8];
2677c478bdstevel@tonic-gate	    unsigned char interface_path[8];
2687c478bdstevel@tonic-gate	    unsigned char device_path[8];
2697c478bdstevel@tonic-gate	    unsigned char reserved2;
2707c478bdstevel@tonic-gate	    unsigned char checksum;
2727c478bdstevel@tonic-gate	    /* XXX: This is necessary, because the BIOS of Thinkpad X20
2737c478bdstevel@tonic-gate	       writes a garbage to the tail of drive parameters,
2747c478bdstevel@tonic-gate	       regardless of a size specified in a caller.  */
2757c478bdstevel@tonic-gate	    unsigned char dummy[16];
2767c478bdstevel@tonic-gate	  } __attribute__ ((packed)) drp;
2787c478bdstevel@tonic-gate	  /* It is safe to clear out DRP.  */
2797c478bdstevel@tonic-gate	  grub_memset (&drp, 0, sizeof (drp));
2817c478bdstevel@tonic-gate	  /* PhoenixBIOS 4.0 Revision 6.0 for ZF Micro might understand
2827c478bdstevel@tonic-gate	     the greater buffer size for the "get drive parameters" int
2837c478bdstevel@tonic-gate	     0x13 call in its own way.  Supposedly the BIOS assumes even
2847c478bdstevel@tonic-gate	     bigger space is available and thus corrupts the stack.
2857c478bdstevel@tonic-gate	     This is why we specify the exactly necessary size of 0x42
2867c478bdstevel@tonic-gate	     bytes. */
2877c478bdstevel@tonic-gate	  drp.size = sizeof (drp) - sizeof (drp.dummy);
2897c478bdstevel@tonic-gate	  err = biosdisk_int13_extensions (0x4800, drive, &drp);
2907c478bdstevel@tonic-gate	  if (! err)
2917c478bdstevel@tonic-gate	    {
2927c478bdstevel@tonic-gate	      /* Set the LBA flag.  */
2937c478bdstevel@tonic-gate	      geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION;
2957c478bdstevel@tonic-gate	      /* I'm not sure if GRUB should check the bit 1 of DRP.FLAGS,
2967c478bdstevel@tonic-gate		 so I omit the check for now. - okuji  */
2977c478bdstevel@tonic-gate	      /* if (drp.flags & (1 << 1)) */
2982f7f7a6Alex Wilson
2992f7f7a6Alex Wilson	      /* If we didn't get valid CHS info from the standard call,
3002f7f7a6Alex Wilson		 then we should fill it out here */
3012f7f7a6Alex Wilson	      if (! gotchs)
3022f7f7a6Alex Wilson		{
3032f7f7a6Alex Wilson		  geometry->cylinders = drp.cylinders;
3042f7f7a6Alex Wilson
3052f7f7a6Alex Wilson		  if (drp.sectors > 0 && drp.heads > 0)
3062f7f7a6Alex Wilson		    {
3072f7f7a6Alex Wilson		      geometry->heads = drp.heads;
3082f7f7a6Alex Wilson		      geometry->sectors = drp.sectors;
3092f7f7a6Alex Wilson		    }
3102f7f7a6Alex Wilson		  else
3112f7f7a6Alex Wilson		    {
3122f7f7a6Alex Wilson		      /* Return fake geometry. This disk reports that it
3132f7f7a6Alex Wilson			 supports LBA, so all the other routines will use LBA
3142f7f7a6Alex Wilson			 to talk to it and not look at this geometry. However,
3152f7f7a6Alex Wilson			 some of the partition-finding routines still need
3162f7f7a6Alex Wilson			 non-zero values in these fields. */
3172f7f7a6Alex Wilson		      geometry->heads = 16;
3182f7f7a6Alex Wilson		      geometry->sectors = 63;
3192f7f7a6Alex Wilson		    }
3202f7f7a6Alex Wilson		  gotchs = 1;
3212f7f7a6Alex Wilson		}
3237c478bdstevel@tonic-gate	      if (drp.total_sectors)
324828d47cShidokht Yadegari		total_sectors = drp.total_sectors;
3257c478bdstevel@tonic-gate	      else
3267c478bdstevel@tonic-gate		/* Some buggy BIOSes doesn't return the total sectors
3277c478bdstevel@tonic-gate		   correctly but returns zero. So if it is zero, compute
3287c478bdstevel@tonic-gate		   it by C/H/S returned by the LBA BIOS call.  */
329828d47cShidokht Yadegari		total_sectors = (unsigned long long)drp.cylinders *
330828d47cShidokht Yadegari		    drp.heads * drp.sectors;
3317c478bdstevel@tonic-gate	    }
3327c478bdstevel@tonic-gate	}
3342f7f7a6Alex Wilson      /* In case we got the 0x60 return code from _standard on a disk that
3352f7f7a6Alex Wilson	 didn't support LBA (or was somehow invalid), return that error now */
3362f7f7a6Alex Wilson      if (! gotchs)
3372f7f7a6Alex Wilson	return 0x60;
3397c478bdstevel@tonic-gate      if (! total_sectors)
3407c478bdstevel@tonic-gate	{
341828d47cShidokht Yadegari	  total_sectors = ((unsigned long long)geometry->cylinders
3427c478bdstevel@tonic-gate			   * geometry->heads
3437c478bdstevel@tonic-gate			   * geometry->sectors);
3447c478bdstevel@tonic-gate	}
3457c478bdstevel@tonic-gate      geometry->total_sectors = total_sectors;
3467c478bdstevel@tonic-gate      geometry->sector_size = SECTOR_SIZE;
3477c478bdstevel@tonic-gate    }
3487c478bdstevel@tonic-gate  else
3497c478bdstevel@tonic-gate    {
3507c478bdstevel@tonic-gate      /* floppy disk */
3527c478bdstevel@tonic-gate      /* First, try INT 13 AH=8h call.  */
3537c478bdstevel@tonic-gate      err = get_diskinfo_standard (drive,
3547c478bdstevel@tonic-gate				   &geometry->cylinders,
3557c478bdstevel@tonic-gate				   &geometry->heads,
3567c478bdstevel@tonic-gate				   &geometry->sectors);
3587c478bdstevel@tonic-gate#if 0
3597c478bdstevel@tonic-gate      /* If fails, then try floppy-specific probe routine.  */
3607c478bdstevel@tonic-gate      if (err)
3617c478bdstevel@tonic-gate	err = get_diskinfo_floppy (drive,
3627c478bdstevel@tonic-gate				   &geometry->cylinders,
3637c478bdstevel@tonic-gate				   &geometry->heads,
3647c478bdstevel@tonic-gate				   &geometry->sectors);
3677c478bdstevel@tonic-gate      if (err)
3687c478bdstevel@tonic-gate	return err;
370828d47cShidokht Yadegari      geometry->total_sectors = ((unsigned long long)geometry->cylinders
3717c478bdstevel@tonic-gate				 * geometry->heads
3727c478bdstevel@tonic-gate				 * geometry->sectors);
3737c478bdstevel@tonic-gate      geometry->sector_size = SECTOR_SIZE;
3747c478bdstevel@tonic-gate    }
3767c478bdstevel@tonic-gate  return 0;