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