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#include "shared.h"
22
23
24/* These are defined in asm.S, and never be used elsewhere, so declare the
25   prototypes here.  */
26extern int biosdisk_int13_extensions (int ax, int drive, void *dap);
27extern int biosdisk_standard (int ah, int drive,
28			      int coff, int hoff, int soff,
29			      int nsec, int segment);
30extern int check_int13_extensions (int drive);
31extern int get_diskinfo_standard (int drive,
32				  unsigned long *cylinders,
33				  unsigned long *heads,
34				  unsigned long *sectors);
35#if 0
36extern int get_diskinfo_floppy (int drive,
37				unsigned long *cylinders,
38				unsigned long *heads,
39				unsigned long *sectors);
40#endif
41
42
43/* Read/write NSEC sectors starting from SECTOR in DRIVE disk with GEOMETRY
44   from/into SEGMENT segment. If READ is BIOSDISK_READ, then read it,
45   else if READ is BIOSDISK_WRITE, then write it. If an geometry error
46   occurs, return BIOSDISK_ERROR_GEOMETRY, and if other error occurs, then
47   return the error number. Otherwise, return 0.  */
48int
49biosdisk (int read, int drive, struct geometry *geometry,
50	  int sector, int nsec, int segment)
51{
52  int err;
53
54  if (geometry->flags & BIOSDISK_FLAG_LBA_EXTENSION)
55    {
56      struct disk_address_packet
57      {
58	unsigned char length;
59	unsigned char reserved;
60	unsigned short blocks;
61	unsigned long buffer;
62	unsigned long long block;
63      } __attribute__ ((packed)) dap;
64
65      /* XXX: Don't check the geometry by default, because some buggy
66	 BIOSes don't return the number of total sectors correctly,
67	 even if they have working LBA support. Hell.  */
68#ifdef NO_BUGGY_BIOS_IN_THE_WORLD
69      if (sector >= geometry->total_sectors)
70	return BIOSDISK_ERROR_GEOMETRY;
71#endif /* NO_BUGGY_BIOS_IN_THE_WORLD */
72
73      /* FIXME: sizeof (DAP) must be 0x10. Should assert that the compiler
74	 can't add any padding.  */
75      dap.length = sizeof (dap);
76      dap.block = sector;
77      dap.blocks = nsec;
78      dap.reserved = 0;
79      /* This is undocumented part. The address is formated in
80	 SEGMENT:ADDRESS.  */
81      dap.buffer = segment << 16;
82
83      err = biosdisk_int13_extensions ((read + 0x42) << 8, drive, &dap);
84
85/* #undef NO_INT13_FALLBACK */
86#ifndef NO_INT13_FALLBACK
87      if (err)
88	{
89	  if (geometry->flags & BIOSDISK_FLAG_CDROM)
90	    return err;
91
92	  geometry->flags &= ~BIOSDISK_FLAG_LBA_EXTENSION;
93	  geometry->total_sectors = (geometry->cylinders
94				     * geometry->heads
95				     * geometry->sectors);
96	  return biosdisk (read, drive, geometry, sector, nsec, segment);
97	}
98#endif /* ! NO_INT13_FALLBACK */
99
100    }
101  else
102    {
103      int cylinder_offset, head_offset, sector_offset;
104      int head;
105
106      /* SECTOR_OFFSET is counted from one, while HEAD_OFFSET and
107	 CYLINDER_OFFSET are counted from zero.  */
108      sector_offset = sector % geometry->sectors + 1;
109      head = sector / geometry->sectors;
110      head_offset = head % geometry->heads;
111      cylinder_offset = head / geometry->heads;
112
113      if (cylinder_offset >= geometry->cylinders)
114	return BIOSDISK_ERROR_GEOMETRY;
115
116      err = biosdisk_standard (read + 0x02, drive,
117			       cylinder_offset, head_offset, sector_offset,
118			       nsec, segment);
119    }
120
121  return err;
122}
123
124/* Check bootable CD-ROM emulation status.  */
125static int
126get_cdinfo (int drive, struct geometry *geometry)
127{
128  int err;
129  struct iso_spec_packet
130  {
131    unsigned char size;
132    unsigned char media_type;
133    unsigned char drive_no;
134    unsigned char controller_no;
135    unsigned long image_lba;
136    unsigned short device_spec;
137    unsigned short cache_seg;
138    unsigned short load_seg;
139    unsigned short length_sec512;
140    unsigned char cylinders;
141    unsigned char sectors;
142    unsigned char heads;
143
144    unsigned char dummy[16];
145  } __attribute__ ((packed)) cdrp;
146
147  grub_memset (&cdrp, 0, sizeof (cdrp));
148  cdrp.size = sizeof (cdrp) - sizeof (cdrp.dummy);
149  err = biosdisk_int13_extensions (0x4B01, drive, &cdrp);
150  if (! err && cdrp.drive_no == drive)
151    {
152      if ((cdrp.media_type & 0x0F) == 0)
153        {
154          /* No emulation bootable CD-ROM */
155          geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION | BIOSDISK_FLAG_CDROM;
156          geometry->cylinders = 0;
157          geometry->heads = 1;
158          geometry->sectors = 15;
159          geometry->sector_size = 2048;
160          geometry->total_sectors = MAXINT;
161          return 1;
162        }
163      else
164        {
165	  /* Floppy or hard-disk emulation */
166          geometry->cylinders
167	    = ((unsigned int) cdrp.cylinders
168	       + (((unsigned int) (cdrp.sectors & 0xC0)) << 2));
169          geometry->heads = cdrp.heads;
170          geometry->sectors = cdrp.sectors & 0x3F;
171          geometry->sector_size = SECTOR_SIZE;
172          geometry->total_sectors = (geometry->cylinders
173				     * geometry->heads
174				     * geometry->sectors);
175          return -1;
176        }
177    }
178  return 0;
179}
180
181/* Return the geometry of DRIVE in GEOMETRY. If an error occurs, return
182   non-zero, otherwise zero.  */
183int
184get_diskinfo (int drive, struct geometry *geometry)
185{
186  int err;
187
188  /* Clear the flags.  */
189  geometry->flags = 0;
190
191  if (drive & 0x80)
192    {
193      /* hard disk or CD-ROM */
194      int version;
195      unsigned long total_sectors = 0;
196
197      version = check_int13_extensions (drive);
198
199      if (drive >= 0x88 || version)
200	{
201	  /* Possible CD-ROM - check the status.  */
202	  if (get_cdinfo (drive, geometry))
203	    return 0;
204	}
205
206      if (version)
207	{
208	  struct drive_parameters
209	  {
210	    unsigned short size;
211	    unsigned short flags;
212	    unsigned long cylinders;
213	    unsigned long heads;
214	    unsigned long sectors;
215	    unsigned long long total_sectors;
216	    unsigned short bytes_per_sector;
217	    /* ver 2.0 or higher */
218	    unsigned long EDD_configuration_parameters;
219	    /* ver 3.0 or higher */
220	    unsigned short signature_dpi;
221	    unsigned char length_dpi;
222	    unsigned char reserved[3];
223	    unsigned char name_of_host_bus[4];
224	    unsigned char name_of_interface_type[8];
225	    unsigned char interface_path[8];
226	    unsigned char device_path[8];
227	    unsigned char reserved2;
228	    unsigned char checksum;
229
230	    /* XXX: This is necessary, because the BIOS of Thinkpad X20
231	       writes a garbage to the tail of drive parameters,
232	       regardless of a size specified in a caller.  */
233	    unsigned char dummy[16];
234	  } __attribute__ ((packed)) drp;
235
236	  /* It is safe to clear out DRP.  */
237	  grub_memset (&drp, 0, sizeof (drp));
238
239	  /* PhoenixBIOS 4.0 Revision 6.0 for ZF Micro might understand
240	     the greater buffer size for the "get drive parameters" int
241	     0x13 call in its own way.  Supposedly the BIOS assumes even
242	     bigger space is available and thus corrupts the stack.
243	     This is why we specify the exactly necessary size of 0x42
244	     bytes. */
245	  drp.size = sizeof (drp) - sizeof (drp.dummy);
246
247	  err = biosdisk_int13_extensions (0x4800, drive, &drp);
248	  if (! err)
249	    {
250	      /* Set the LBA flag.  */
251	      geometry->flags = BIOSDISK_FLAG_LBA_EXTENSION;
252
253	      /* I'm not sure if GRUB should check the bit 1 of DRP.FLAGS,
254		 so I omit the check for now. - okuji  */
255	      /* if (drp.flags & (1 << 1)) */
256
257	      /* FIXME: when the 2TB limit becomes critical, we must
258		 change the type of TOTAL_SECTORS to unsigned long
259		 long.  */
260	      if (drp.total_sectors)
261		total_sectors = drp.total_sectors & ~0L;
262	      else
263		/* Some buggy BIOSes doesn't return the total sectors
264		   correctly but returns zero. So if it is zero, compute
265		   it by C/H/S returned by the LBA BIOS call.  */
266		total_sectors = drp.cylinders * drp.heads * drp.sectors;
267	    }
268	}
269
270      /* Don't pass GEOMETRY directly, but pass each element instead,
271	 so that we can change the structure easily.  */
272      err = get_diskinfo_standard (drive,
273				   &geometry->cylinders,
274				   &geometry->heads,
275				   &geometry->sectors);
276      if (err)
277	return err;
278
279      if (! total_sectors)
280	{
281	  total_sectors = (geometry->cylinders
282			   * geometry->heads
283			   * geometry->sectors);
284	}
285      geometry->total_sectors = total_sectors;
286      geometry->sector_size = SECTOR_SIZE;
287    }
288  else
289    {
290      /* floppy disk */
291
292      /* First, try INT 13 AH=8h call.  */
293      err = get_diskinfo_standard (drive,
294				   &geometry->cylinders,
295				   &geometry->heads,
296				   &geometry->sectors);
297
298#if 0
299      /* If fails, then try floppy-specific probe routine.  */
300      if (err)
301	err = get_diskinfo_floppy (drive,
302				   &geometry->cylinders,
303				   &geometry->heads,
304				   &geometry->sectors);
305#endif
306
307      if (err)
308	return err;
309
310      geometry->total_sectors = (geometry->cylinders
311				 * geometry->heads
312				 * geometry->sectors);
313      geometry->sector_size = SECTOR_SIZE;
314    }
315
316  return 0;
317}
318