1/* bios.c - implement C part of low-level BIOS disk input and output */
2/*
3 *  GRUB  --  GRand Unified Bootloader
4 *  Copyright (C) 1999,2000,2003,2004  Free Software Foundation, Inc.
5 *
6 *  This program is free software; you can redistribute it and/or modify
7 *  it under the terms of the GNU General Public License as published by
8 *  the Free Software Foundation; either version 2 of the License, or
9 *  (at your option) any later version.
10 *
11 *  This program is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU General Public License for more details.
15 *
16 *  You should have received a copy of the GNU General Public License
17 *  along with this program; if not, write to the Free Software
18 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20/*
21 * Copyright 2016 Nexenta Systems, Inc.
22 */
23
24#include "shared.h"
25
26
27/* These are defined in asm.S, and never be used elsewhere, so declare the
28   prototypes here.  */
29extern int biosdisk_int13_extensions (int ax, int drive, void *dap);
30extern int biosdisk_standard (int ah, int drive,
31			      int coff, int hoff, int soff,
32			      int nsec, int segment);
33extern int check_int13_extensions (int drive);
34extern int get_diskinfo_standard (int drive,
35				  unsigned long *cylinders,
36				  unsigned long *heads,
37				  unsigned long *sectors);
38#if 0
39extern int get_diskinfo_floppy (int drive,
40				unsigned long *cylinders,
41				unsigned long *heads,
42				unsigned long *sectors);
43#endif
44
45
46/* Read/write NSEC sectors starting from SECTOR in DRIVE disk with GEOMETRY
47   from/into SEGMENT segment. If READ is BIOSDISK_READ, then read it,
48   else if READ is BIOSDISK_WRITE, then write it. If an geometry error
49   occurs, return BIOSDISK_ERROR_GEOMETRY, and if other error occurs, then
50   return the error number. Otherwise, return 0.  */
51int
52biosdisk (int read, int drive, struct geometry *geometry,
53	  unsigned long long sector, int nsec, int segment)
54{
55
56  int err;
57
58  if (geometry->flags & BIOSDISK_FLAG_LBA_EXTENSION)
59    {
60      struct disk_address_packet
61      {
62	unsigned char length;
63	unsigned char reserved;
64	unsigned short blocks;
65	unsigned long buffer;
66	unsigned long long block;
67      } __attribute__ ((packed)) dap;
68
69      /* XXX: Don't check the geometry by default, because some buggy
70	 BIOSes don't return the number of total sectors correctly,
71	 even if they have working LBA support. Hell.  */
72#ifdef NO_BUGGY_BIOS_IN_THE_WORLD
73      if (sector >= geometry->total_sectors)
74	return BIOSDISK_ERROR_GEOMETRY;
75#endif /* NO_BUGGY_BIOS_IN_THE_WORLD */
76
77      /* FIXME: sizeof (DAP) must be 0x10. Should assert that the compiler
78	 can't add any padding.  */
79      dap.length = sizeof (dap);
80      dap.block = sector;
81      dap.blocks = nsec;
82      dap.reserved = 0;
83      /* This is undocumented part. The address is formated in
84	 SEGMENT:ADDRESS.  */
85      dap.buffer = segment << 16;
86      err = biosdisk_int13_extensions ((read + 0x42) << 8, drive, &dap);
87      /*
88       * Try to report errors upwards when the bios has read only part of
89       * the requested buffer, but didn't return an error code.
90       */
91      if (err == 0 && dap.blocks != nsec)
92	err = BIOSDISK_ERROR_SHORT_IO;
93
94/* #undef NO_INT13_FALLBACK */
95#ifndef NO_INT13_FALLBACK
96      if (err)
97	{
98	  if (geometry->flags & BIOSDISK_FLAG_CDROM)
99	    return err;
100
101	  geometry->flags &= ~BIOSDISK_FLAG_LBA_EXTENSION;
102	  geometry->total_sectors = ((unsigned long long)geometry->cylinders
103				     * geometry->heads
104				     * geometry->sectors);
105	  return biosdisk (read, drive, geometry, sector, nsec, segment);
106	}
107#endif /* ! NO_INT13_FALLBACK */
108
109    }
110  else
111    {
112      int cylinder_offset, head_offset, sector_offset;
113      int head;
114      /* SECTOR_OFFSET is counted from one, while HEAD_OFFSET and
115	 CYLINDER_OFFSET are counted from zero.  */
116      sector_offset = sector % geometry->sectors + 1;
117      head = sector / geometry->sectors;
118      head_offset = head % geometry->heads;
119      cylinder_offset = head / geometry->heads;
120
121      if (cylinder_offset >= geometry->cylinders)
122	return BIOSDISK_ERROR_GEOMETRY;
123
124      err = biosdisk_standard (read + 0x02, drive,
125			       cylinder_offset, head_offset, sector_offset,
126			       nsec, segment);
127    }
128
129  return err;
130}
131
132/* Check bootable CD-ROM emulation status.  */
133static int
134get_cdinfo (int drive, struct geometry *geometry)
135{
136  int err;
137  struct iso_spec_packet
138  {
139    unsigned char size;
140    unsigned char media_type;
141    unsigned char drive_no;
142    unsigned char controller_no;
143    unsigned long image_lba;
144    unsigned short device_spec;
145    unsigned short cache_seg;
146    unsigned short load_seg;
147    unsigned short length_sec512;
148    unsigned char cylinders;
149    unsigned char sectors;
150    unsigned char heads;
151
152    unsigned char dummy[16];
153  } __attribute__ ((packed)) cdrp;
154
155  grub_memset (&cdrp, 0, sizeof (cdrp));
156  cdrp.size = sizeof (cdrp) - sizeof (cdrp.dummy);
157  err = biosdisk_int13_extensions (0x4B01, drive, &cdrp);
158  if (! err && cdrp.drive_no == drive)
159    {
160      if ((cdrp.media_type & 0x0F) == 0)
161        {
162          /* No emulation bootable CD-ROM */
163          geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION | BIOSDISK_FLAG_CDROM;
164          geometry->cylinders = 0;
165          geometry->heads = 1;
166          geometry->sectors = 15;
167          geometry->sector_size = 2048;
168          geometry->total_sectors = MAXUINT;
169          return 1;
170        }
171      else
172        {
173	  /* Floppy or hard-disk emulation */
174          geometry->cylinders
175	    = ((unsigned int) cdrp.cylinders
176	       + (((unsigned int) (cdrp.sectors & 0xC0)) << 2));
177          geometry->heads = cdrp.heads;
178          geometry->sectors = cdrp.sectors & 0x3F;
179          geometry->sector_size = SECTOR_SIZE;
180          geometry->total_sectors = ((unsigned long long)geometry->cylinders
181				     * geometry->heads
182				     * geometry->sectors);
183          return -1;
184        }
185    }
186
187  /*
188   * If this is the boot_drive, default to non-emulation bootable CD-ROM.
189   *
190   * Some BIOS (Tecra S1) fails the int13 call above. If we return
191   * failure here, GRUB will run, but cannot see the boot drive,
192   * not a very good situation. Defaulting to non-emulation mode
193   * is a last-ditch effort.
194   */
195  if (drive >= 0x88 && drive == boot_drive)
196    {
197      geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION | BIOSDISK_FLAG_CDROM;
198      geometry->cylinders = 0;
199      geometry->heads = 1;
200      geometry->sectors = 15;
201      geometry->sector_size = 2048;
202      geometry->total_sectors = MAXUINT;
203      return 1;
204    }
205  return 0;
206}
207
208/* Return the geometry of DRIVE in GEOMETRY. If an error occurs, return
209   non-zero, otherwise zero.  */
210int
211get_diskinfo (int drive, struct geometry *geometry)
212{
213  int err;
214  int gotchs = 0;
215
216  /* Clear the flags.  */
217  geometry->flags = 0;
218
219  if (drive & 0x80)
220    {
221      /* hard disk or CD-ROM */
222      int version;
223      unsigned long long total_sectors = 0;
224
225      version = check_int13_extensions (drive);
226
227      if (drive >= 0x88 || version)
228	{
229	  /* Possible CD-ROM - check the status.  */
230	  if (get_cdinfo (drive, geometry))
231	    return 0;
232	}
233
234      /* Don't pass GEOMETRY directly, but pass each element instead,
235	 so that we can change the structure easily.  */
236      err = get_diskinfo_standard (drive,
237				   &geometry->cylinders,
238				   &geometry->heads,
239				   &geometry->sectors);
240      if (err == 0)
241	gotchs = 1;
242      /* get_diskinfo_standard returns 0x60 if the BIOS call actually
243	 succeeded but returned 0 sectors -- in this case don't
244	 return yet but continue to check the LBA geom */
245      else if (err != 0x60)
246	return err;
247
248      if (version)
249	{
250	  struct drive_parameters
251	  {
252	    unsigned short size;
253	    unsigned short flags;
254	    unsigned long cylinders;
255	    unsigned long heads;
256	    unsigned long sectors;
257	    unsigned long long total_sectors;
258	    unsigned short bytes_per_sector;
259	    /* ver 2.0 or higher */
260	    unsigned long EDD_configuration_parameters;
261	    /* ver 3.0 or higher */
262	    unsigned short signature_dpi;
263	    unsigned char length_dpi;
264	    unsigned char reserved[3];
265	    unsigned char name_of_host_bus[4];
266	    unsigned char name_of_interface_type[8];
267	    unsigned char interface_path[8];
268	    unsigned char device_path[8];
269	    unsigned char reserved2;
270	    unsigned char checksum;
271
272	    /* XXX: This is necessary, because the BIOS of Thinkpad X20
273	       writes a garbage to the tail of drive parameters,
274	       regardless of a size specified in a caller.  */
275	    unsigned char dummy[16];
276	  } __attribute__ ((packed)) drp;
277
278	  /* It is safe to clear out DRP.  */
279	  grub_memset (&drp, 0, sizeof (drp));
280
281	  /* PhoenixBIOS 4.0 Revision 6.0 for ZF Micro might understand
282	     the greater buffer size for the "get drive parameters" int
283	     0x13 call in its own way.  Supposedly the BIOS assumes even
284	     bigger space is available and thus corrupts the stack.
285	     This is why we specify the exactly necessary size of 0x42
286	     bytes. */
287	  drp.size = sizeof (drp) - sizeof (drp.dummy);
288
289	  err = biosdisk_int13_extensions (0x4800, drive, &drp);
290	  if (! err)
291	    {
292	      /* Set the LBA flag.  */
293	      geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION;
294
295	      /* I'm not sure if GRUB should check the bit 1 of DRP.FLAGS,
296		 so I omit the check for now. - okuji  */
297	      /* if (drp.flags & (1 << 1)) */
298
299	      /* If we didn't get valid CHS info from the standard call,
300		 then we should fill it out here */
301	      if (! gotchs)
302		{
303		  geometry->cylinders = drp.cylinders;
304
305		  if (drp.sectors > 0 && drp.heads > 0)
306		    {
307		      geometry->heads = drp.heads;
308		      geometry->sectors = drp.sectors;
309		    }
310		  else
311		    {
312		      /* Return fake geometry. This disk reports that it
313			 supports LBA, so all the other routines will use LBA
314			 to talk to it and not look at this geometry. However,
315			 some of the partition-finding routines still need
316			 non-zero values in these fields. */
317		      geometry->heads = 16;
318		      geometry->sectors = 63;
319		    }
320		  gotchs = 1;
321		}
322
323	      if (drp.total_sectors)
324		total_sectors = drp.total_sectors;
325	      else
326		/* Some buggy BIOSes doesn't return the total sectors
327		   correctly but returns zero. So if it is zero, compute
328		   it by C/H/S returned by the LBA BIOS call.  */
329		total_sectors = (unsigned long long)drp.cylinders *
330		    drp.heads * drp.sectors;
331	    }
332	}
333
334      /* In case we got the 0x60 return code from _standard on a disk that
335	 didn't support LBA (or was somehow invalid), return that error now */
336      if (! gotchs)
337	return 0x60;
338
339      if (! total_sectors)
340	{
341	  total_sectors = ((unsigned long long)geometry->cylinders
342			   * geometry->heads
343			   * geometry->sectors);
344	}
345      geometry->total_sectors = total_sectors;
346      geometry->sector_size = SECTOR_SIZE;
347    }
348  else
349    {
350      /* floppy disk */
351
352      /* First, try INT 13 AH=8h call.  */
353      err = get_diskinfo_standard (drive,
354				   &geometry->cylinders,
355				   &geometry->heads,
356				   &geometry->sectors);
357
358#if 0
359      /* If fails, then try floppy-specific probe routine.  */
360      if (err)
361	err = get_diskinfo_floppy (drive,
362				   &geometry->cylinders,
363				   &geometry->heads,
364				   &geometry->sectors);
365#endif
366
367      if (err)
368	return err;
369
370      geometry->total_sectors = ((unsigned long long)geometry->cylinders
371				 * geometry->heads
372				 * geometry->sectors);
373      geometry->sector_size = SECTOR_SIZE;
374    }
375
376  return 0;
377}
378