xref: /illumos-gate/usr/src/grub/grub-0.97/stage2/bios.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 = ((unsigned long long)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 = ((unsigned long long)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 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 	      if (drp.total_sectors)
282 		total_sectors = drp.total_sectors;
283 	      else
284 		/* Some buggy BIOSes doesn't return the total sectors
285 		   correctly but returns zero. So if it is zero, compute
286 		   it by C/H/S returned by the LBA BIOS call.  */
287 		total_sectors = (unsigned long long)drp.cylinders *
288 		    drp.heads * drp.sectors;
289 	    }
290 	}
291 
292       /* Don't pass GEOMETRY directly, but pass each element instead,
293 	 so that we can change the structure easily.  */
294       err = get_diskinfo_standard (drive,
295 				   &geometry->cylinders,
296 				   &geometry->heads,
297 				   &geometry->sectors);
298       if (err)
299 	return err;
300 
301       if (! total_sectors)
302 	{
303 	  total_sectors = ((unsigned long long)geometry->cylinders
304 			   * geometry->heads
305 			   * geometry->sectors);
306 	}
307       geometry->total_sectors = total_sectors;
308       geometry->sector_size = SECTOR_SIZE;
309     }
310   else
311     {
312       /* floppy disk */
313 
314       /* First, try INT 13 AH=8h call.  */
315       err = get_diskinfo_standard (drive,
316 				   &geometry->cylinders,
317 				   &geometry->heads,
318 				   &geometry->sectors);
319 
320 #if 0
321       /* If fails, then try floppy-specific probe routine.  */
322       if (err)
323 	err = get_diskinfo_floppy (drive,
324 				   &geometry->cylinders,
325 				   &geometry->heads,
326 				   &geometry->sectors);
327 #endif
328 
329       if (err)
330 	return err;
331 
332       geometry->total_sectors = ((unsigned long long)geometry->cylinders
333 				 * geometry->heads
334 				 * geometry->sectors);
335       geometry->sector_size = SECTOR_SIZE;
336     }
337 
338   return 0;
339 }
340