~ubuntu-core-dev/grub/ubuntu

824 by Colin Watson
* Use EDD information if available to help generate a more correct
1
--- a/lib/device.c	2008-04-02 13:49:57.000000000 +0100
2
+++ b/lib/device.c	2008-04-02 13:49:29.000000000 +0100
3
@@ -35,6 +35,11 @@
4
 #include <errno.h>
5
 #include <limits.h>
6
 #include <stdarg.h>
7
+#include <dirent.h>
8
+
9
+#define EDD_MBR_SIG_OFFSET 0x1B8
10
+
11
+typedef unsigned int grub_uint32_t;
12
 
13
 #ifdef __linux__
14
 # if !defined(__GLIBC__) || \
15
@@ -400,6 +405,92 @@
16
     close (fd);
17
 }
18
 
19
+/* Read MBR signatures from EDD, if available.  */
20
+static void
21
+read_edd (grub_uint32_t *mbr_sig, char *mbr_sig_avail)
22
+{
23
+#ifdef __linux__
24
+  DIR *edd = opendir ("/sys/firmware/edd");
25
+  struct dirent *edd_entry;
26
+
27
+  if (! edd)
28
+    return;
29
+
30
+  while ((edd_entry = readdir (edd)))
31
+    {
32
+      char bios_dev_str[4];
33
+      int bios_dev;
34
+      char *signature_name;
35
+      FILE *signature_file;
36
+      char line[16];
37
+
38
+      if (strcmp (edd_entry->d_name, ".") == 0 ||
39
+	  strcmp (edd_entry->d_name, "..") == 0)
40
+	continue;
41
+
42
+      if (sscanf (edd_entry->d_name, "int13_dev%3s", bios_dev_str) != 1)
43
+	continue;
44
+      errno = 0;
45
+      bios_dev = strtol (bios_dev_str, NULL, 16);
46
+      if (errno || bios_dev < 0 || bios_dev >= NUM_DISKS)
47
+	continue;
48
+
49
+      signature_name = malloc (strlen ("/sys/firmware/edd/") +
50
+			       strlen (edd_entry->d_name) +
51
+			       strlen ("/mbr_signature") + 1);
52
+      if (! signature_name)
53
+	continue;
54
+      sprintf (signature_name, "/sys/firmware/edd/%s/mbr_signature",
55
+	       edd_entry->d_name);
56
+      signature_file = fopen (signature_name, "r");
57
+      if (! signature_file)
58
+	{
59
+	  free (signature_name);
60
+	  continue;
61
+	}
62
+
63
+      *line = 0;
64
+      fgets (line, 16, signature_file);
65
+      if (*line == '0' && *(line + 1) == 'x')
66
+	{
67
+	  mbr_sig[bios_dev] = (grub_uint32_t) strtol(line, NULL, 16);
68
+	  mbr_sig_avail[bios_dev] = 1;
69
+	}
70
+
71
+      fclose (signature_file);
72
+      free (signature_name);
73
+    }
74
+
75
+  closedir (edd);
76
+#endif /* __linux__ */
77
+}
78
+
79
+static int
80
+read_device_mbr_sig (char *device, grub_uint32_t *mbr)
81
+{
82
+#ifdef __linux__
83
+  char buf[512];
84
+  FILE *fp;
85
+
86
+  if (*device == 0)
87
+    return 0;
88
+
89
+  fp = fopen (device, "r");
90
+  if (! fp)
91
+    return 0;
92
+  if (fread (buf, 1, 512, fp) != 512)
93
+    {
94
+      fclose (fp);
95
+      return 0;
96
+    }
97
+
98
+  *mbr = *(grub_uint32_t *) &buf[EDD_MBR_SIG_OFFSET];
99
+  return 1;
100
+#else
101
+  return 0;
102
+#endif /* __linux__ */
103
+}
104
+
105
 #ifdef __linux__
106
 /* Check if we have devfs support.  */
107
 static int
108
@@ -788,6 +879,8 @@
109
   int i;
110
   int num_hd = 0;
111
   FILE *fp = 0;
112
+  grub_uint32_t *edd_mbr_sig = 0;
113
+  char *edd_mbr_sig_avail = 0;
114
 
115
   assert (map);
116
   assert (*map == 0);
117
@@ -820,6 +913,17 @@
118
 	   "Probing devices to guess BIOS drives. "
119
 	   "This may take a long time.\n");
120
   
121
+  /* Probe EDD, if available, to get more accurate device mapping. */
122
+  edd_mbr_sig = malloc (NUM_DISKS * sizeof (grub_uint32_t));
123
+  assert (edd_mbr_sig);
124
+  edd_mbr_sig_avail = malloc (NUM_DISKS * sizeof (char));
125
+  assert (edd_mbr_sig_avail);
126
+  
127
+  for (i = 0; i < NUM_DISKS; i++)
128
+    edd_mbr_sig_avail[i] = 0;
129
+  
130
+  read_edd (edd_mbr_sig, edd_mbr_sig_avail);
131
+  
132
   if (map_file)
133
     /* Try to open the device map file to write the probed data.  */
134
     fp = fopen (map_file, "w");
135
@@ -862,20 +966,12 @@
136
 	      strcat (name, "/disc");
137
 	      (*map)[num_hd + 0x80] = strdup (name);
138
 	      assert ((*map)[num_hd + 0x80]);
139
-	      
140
-	      /* If the device map file is opened, write the map.  */
141
-	      if (fp)
142
-		fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
143
 	    }
144
 	  
145
 	  num_hd++;
146
 	}
147
       
148
-      /* OK, close the device map file if opened.  */
149
-      if (fp)
150
-	fclose (fp);
151
-      
152
-      return 1;
153
+      goto edd_reorder;
154
     }
155
 #endif /* __linux__ */
156
 
157
@@ -891,10 +987,6 @@
158
 	  (*map)[num_hd + 0x80] = strdup (name);
159
 	  assert ((*map)[num_hd + 0x80]);
160
 	  
161
-	  /* If the device map file is opened, write the map.  */
162
-	  if (fp)
163
-	    fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
164
-	  
165
 	  num_hd++;
166
 	}
167
     }
168
@@ -911,10 +1003,6 @@
169
 	  (*map)[num_hd + 0x80] = strdup (name);
170
 	  assert ((*map)[num_hd + 0x80]);
171
 	  
172
-	  /* If the device map file is opened, write the map.  */
173
-	  if (fp)
174
-	    fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
175
-	  
176
 	  num_hd++;
177
 	}
178
     }
179
@@ -931,10 +1019,6 @@
180
           (*map)[num_hd + 0x80] = strdup (name);
181
           assert ((*map)[num_hd + 0x80]);
182
 
183
-          /* If the device map file is opened, write the map.  */
184
-          if (fp)
185
-            fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
186
-
187
           num_hd++;
188
         }
189
     }
190
@@ -951,10 +1035,6 @@
191
 	  (*map)[num_hd + 0x80] = strdup (name);
192
 	  assert ((*map)[num_hd + 0x80]);
193
 	  
194
-	  /* If the device map file is opened, write the map.  */
195
-	  if (fp)
196
-	    fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
197
-	  
198
 	  num_hd++;
199
 	}
200
     }
201
@@ -980,10 +1060,6 @@
202
 		(*map)[num_hd + 0x80] = strdup (name);
203
 		assert ((*map)[num_hd + 0x80]);
204
 		
205
-		/* If the device map file is opened, write the map.  */
206
-		if (fp)
207
-		  fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
208
-		
209
 		num_hd++;
210
 	      }
211
 	  }
212
@@ -1003,10 +1079,6 @@
213
           {
214
               (*map)[num_hd + 0x80] = strdup (name);
215
                   assert ((*map)[num_hd + 0x80]);
216
-                  
217
-	    /* If the device map file is opened, write the map.  */
218
-               if (fp)
219
-                     fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
220
                      
221
 	    num_hd++;
222
           }
223
@@ -1037,10 +1109,6 @@
224
 		(*map)[num_hd + 0x80] = strdup (name);
225
 		assert ((*map)[num_hd + 0x80]);
226
 		
227
-		/* If the device map file is opened, write the map.  */
228
-		if (fp)
229
-		  fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
230
-		
231
 		num_hd++;
232
 	      }
233
 	  }
234
@@ -1071,10 +1139,6 @@
235
 		(*map)[num_hd + 0x80] = strdup (name);
236
 		assert ((*map)[num_hd + 0x80]);
237
 		
238
-		/* If the device map file is opened, write the map.  */
239
-		if (fp)
240
-		  fprintf (fp, "(hd%d)\t%s\n", num_hd, name);
241
-		
242
 		num_hd++;
243
 	      }
244
 	  }
245
@@ -1082,10 +1146,184 @@
246
   }
247
 #endif /* __linux__ */
248
   
249
+edd_reorder:
250
+  /* If any devices have unique matching MBR signatures, then we can be
251
+     sure of its BIOS ID, so use that.
252
+     
253
+     If some devices match an MBR signature, but the signature is not
254
+     unique, then try to match those devices up with their signatures in
255
+     order to maximize our probability of being correct.
256
+     
257
+     Any devices that remain fill in the remaining slots in the same order
258
+     as they were detected above.
259
+     
260
+     The algorithm that follows could probably be faster, but correctness is
261
+     more important.  */
262
+  {
263
+    char *unique_edd = malloc (NUM_DISKS * sizeof (char));
264
+    char *fixed = malloc (NUM_DISKS * sizeof (char));
265
+    grub_uint32_t *device_mbr_sig = malloc (NUM_DISKS *
266
+					    sizeof (grub_uint32_t));
267
+    char *device_mbr_sig_avail = malloc (NUM_DISKS * sizeof (char));
268
+
269
+    auto void copy_device (int src, int dest);
270
+    auto void move_device (int src, int dest);
271
+
272
+    /* Copy device at position SRC to position DEST.  */
273
+    auto void copy_device (int src, int dest)
274
+      {
275
+	(*map)[dest] = (*map)[src];
276
+	device_mbr_sig[dest] = device_mbr_sig[src];
277
+	device_mbr_sig_avail[dest] = device_mbr_sig_avail[src];
278
+      }
279
+
280
+    /* Move the device at position SRC to position DEST, shifting everything
281
+       else along to make room. Relies on devices starting at 0x80 so that
282
+       we always have a temporary slot available at position 0.  */
283
+    auto void move_device (int src, int dest)
284
+      {
285
+	int cur = src;
286
+	int step, limit;
287
+
288
+	assert (! fixed[dest]);
289
+	assert (! fixed[src]);
290
+
291
+	fixed[dest] = 1;
292
+
293
+	if (dest == src)
294
+	  return;
295
+
296
+	copy_device (src, 0);
297
+
298
+	if (dest < src)
299
+	  {
300
+	    step = -1;
301
+	    limit = 0x80 - 1;
302
+	  }
303
+	else
304
+	  {
305
+	    step = 1;
306
+	    limit = NUM_DISKS;
307
+	  }
308
+	for (;;)
309
+	  {
310
+	    int walk, next = 0;
311
+	    for (walk = cur + step; walk != limit; walk += step)
312
+	      if (walk == dest || ! fixed[walk])
313
+		{
314
+		  next = walk;
315
+		  break;
316
+		}
317
+	    if (walk == limit)
318
+	      break;
319
+	    copy_device (next, cur);
320
+	    if (next == dest)
321
+	      break;
322
+	    cur = next;
323
+	  }
324
+
325
+	copy_device (0, dest);
326
+	(*map)[0] = 0;
327
+	device_mbr_sig[0] = 0;
328
+	device_mbr_sig_avail[0] = 0;
329
+      }
330
+
331
+    if (unique_edd && fixed && device_mbr_sig && device_mbr_sig_avail)
332
+      {
333
+	int other;
334
+
335
+	for (i = 0; i < NUM_DISKS; i++)
336
+	  {
337
+	    fixed[i] = 0;
338
+	    device_mbr_sig[i] = 0;
339
+	    device_mbr_sig_avail[i] = 0;
340
+	  }
341
+
342
+	/* Read all the signatures from their devices.  */
343
+	for (i = 0x80; i < NUM_DISKS; i++)
344
+	  {
345
+	    if ((*map)[i])
346
+	      device_mbr_sig_avail[i] = read_device_mbr_sig
347
+		((*map)[i], &device_mbr_sig[i]);
348
+	    else
349
+	      device_mbr_sig_avail[i] = 0;
350
+	  }
351
+
352
+	/* Check for unique EDD signatures.  */
353
+	for (i = 0x80; i < NUM_DISKS; i++)
354
+	  {
355
+	    if (! edd_mbr_sig_avail[i])
356
+	      {
357
+		unique_edd[i] = 0;
358
+		continue;
359
+	      }
360
+
361
+	    unique_edd[i] = 1;
362
+	    for (other = 0x80; other < NUM_DISKS; other++)
363
+	      {
364
+		if (i != other && edd_mbr_sig[i] == edd_mbr_sig[other])
365
+		  {
366
+		    unique_edd[i] = 0;
367
+		    break;
368
+		  }
369
+	      }
370
+	  }
371
+
372
+	/* Move the disks with unique EDD signatures into place.  */
373
+	for (i = 0x80; i < NUM_DISKS; i++)
374
+	  {
375
+	    if (! unique_edd[i])
376
+	      continue;
377
+
378
+	    for (other = 0x80; other < NUM_DISKS; other++)
379
+	      {
380
+		if (! fixed[other] && edd_mbr_sig[i] == device_mbr_sig[other])
381
+		  {
382
+		    move_device (other, i);
383
+		    break;
384
+		  }
385
+	      }
386
+	  }
387
+
388
+	/* Move the disks with non-unique EDD signatures into place.  */
389
+	for (i = 0x80; i < NUM_DISKS; i++)
390
+	  {
391
+	    if (fixed[i] || ! edd_mbr_sig_avail[i])
392
+	      continue;
393
+
394
+	    for (other = 0x80; other < NUM_DISKS; other++)
395
+	      {
396
+		if (! fixed[other] && edd_mbr_sig[i] == device_mbr_sig[other])
397
+		  {
398
+		    move_device (other, i);
399
+		    break;
400
+		  }
401
+	      }
402
+	  }
403
+      }
404
+    free (device_mbr_sig_avail);
405
+    free (device_mbr_sig);
406
+    free (fixed);
407
+    free (unique_edd);
408
+  }
409
+
410
+  /* If the device map file is opened, write the map.  */
411
+  if (fp)
412
+    {
413
+      for (i = 0x80; i < NUM_DISKS; i++)
414
+	{
415
+	  if ((*map)[i])
416
+	    fprintf (fp, "(hd%d)\t%s\n", i - 0x80, (*map)[i]);
417
+	}
418
+    }
419
+
420
   /* OK, close the device map file if opened.  */
421
   if (fp)
422
     fclose (fp);
423
 
424
+  free (edd_mbr_sig_avail);
425
+  free (edd_mbr_sig);
426
+
427
   return 1;
428
 }
429