]>
Commit | Line | Data |
---|---|---|
9cacaa17 | 1 | /* |
2 | * GRUB -- GRand Unified Bootloader | |
5a79f472 | 3 | * Copyright (C) 2006,2007 Free Software Foundation, Inc. |
9cacaa17 | 4 | * |
5a79f472 | 5 | * GRUB is free software: you can redistribute it and/or modify |
9cacaa17 | 6 | * it under the terms of the GNU General Public License as published by |
5a79f472 | 7 | * the Free Software Foundation, either version 3 of the License, or |
9cacaa17 | 8 | * (at your option) any later version. |
9 | * | |
5a79f472 | 10 | * GRUB is distributed in the hope that it will be useful, |
9cacaa17 | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
5a79f472 | 16 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
9cacaa17 | 17 | */ |
18 | ||
19 | #include <grub/disk.h> | |
2965c7cc | 20 | #include <grub/partition.h> |
9cacaa17 | 21 | #include <grub/mm.h> |
22 | #include <grub/types.h> | |
23 | #include <grub/misc.h> | |
24 | #include <grub/err.h> | |
25 | #include <grub/term.h> | |
26 | #include <grub/efi/api.h> | |
27 | #include <grub/efi/efi.h> | |
28 | #include <grub/efi/disk.h> | |
29 | ||
30 | struct grub_efidisk_data | |
31 | { | |
32 | grub_efi_handle_t handle; | |
33 | grub_efi_device_path_t *device_path; | |
34 | grub_efi_device_path_t *last_device_path; | |
35 | grub_efi_block_io_t *block_io; | |
36 | grub_efi_disk_io_t *disk_io; | |
37 | struct grub_efidisk_data *next; | |
38 | }; | |
39 | ||
40 | /* GUIDs. */ | |
41 | static grub_efi_guid_t disk_io_guid = GRUB_EFI_DISK_IO_GUID; | |
42 | static grub_efi_guid_t block_io_guid = GRUB_EFI_BLOCK_IO_GUID; | |
9cacaa17 | 43 | |
44 | static struct grub_efidisk_data *fd_devices; | |
45 | static struct grub_efidisk_data *hd_devices; | |
46 | static struct grub_efidisk_data *cd_devices; | |
47 | ||
48 | /* Duplicate a device path. */ | |
49 | static grub_efi_device_path_t * | |
50 | duplicate_device_path (const grub_efi_device_path_t *dp) | |
51 | { | |
52 | grub_efi_device_path_t *p; | |
53 | grub_size_t total_size = 0; | |
54 | ||
55 | for (p = (grub_efi_device_path_t *) dp; | |
56 | ; | |
57 | p = GRUB_EFI_NEXT_DEVICE_PATH (p)) | |
58 | { | |
59 | total_size += GRUB_EFI_DEVICE_PATH_LENGTH (p); | |
60 | if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (p)) | |
61 | break; | |
62 | } | |
63 | ||
64 | p = grub_malloc (total_size); | |
65 | if (! p) | |
66 | return 0; | |
67 | ||
68 | grub_memcpy (p, dp, total_size); | |
69 | return p; | |
70 | } | |
71 | ||
72 | /* Return the device path node right before the end node. */ | |
73 | static grub_efi_device_path_t * | |
74 | find_last_device_path (const grub_efi_device_path_t *dp) | |
75 | { | |
76 | grub_efi_device_path_t *next, *p; | |
77 | ||
78 | if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp)) | |
79 | return 0; | |
80 | ||
81 | for (p = (grub_efi_device_path_t *) dp, next = GRUB_EFI_NEXT_DEVICE_PATH (p); | |
82 | ! GRUB_EFI_END_ENTIRE_DEVICE_PATH (next); | |
83 | p = next, next = GRUB_EFI_NEXT_DEVICE_PATH (next)) | |
84 | ; | |
85 | ||
86 | return p; | |
87 | } | |
88 | ||
89 | /* Compare device paths. */ | |
90 | static int | |
91 | compare_device_paths (const grub_efi_device_path_t *dp1, | |
92 | const grub_efi_device_path_t *dp2) | |
93 | { | |
94 | if (! dp1 || ! dp2) | |
95 | /* Return non-zero. */ | |
96 | return 1; | |
97 | ||
98 | while (1) | |
99 | { | |
100 | grub_efi_uint8_t type1, type2; | |
101 | grub_efi_uint8_t subtype1, subtype2; | |
102 | grub_efi_uint16_t len1, len2; | |
103 | int ret; | |
104 | ||
105 | type1 = GRUB_EFI_DEVICE_PATH_TYPE (dp1); | |
106 | type2 = GRUB_EFI_DEVICE_PATH_TYPE (dp2); | |
107 | ||
108 | if (type1 != type2) | |
109 | return (int) type2 - (int) type1; | |
110 | ||
111 | subtype1 = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp1); | |
112 | subtype2 = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp2); | |
113 | ||
114 | if (subtype1 != subtype2) | |
115 | return (int) subtype1 - (int) subtype2; | |
116 | ||
117 | len1 = GRUB_EFI_DEVICE_PATH_LENGTH (dp1); | |
118 | len2 = GRUB_EFI_DEVICE_PATH_LENGTH (dp2); | |
119 | ||
120 | if (len1 != len2) | |
121 | return (int) len1 - (int) len2; | |
122 | ||
123 | ret = grub_memcmp (dp1, dp2, len1); | |
124 | if (ret != 0) | |
125 | return ret; | |
126 | ||
127 | if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp1)) | |
128 | break; | |
129 | ||
130 | dp1 = (grub_efi_device_path_t *) ((char *) dp1 + len1); | |
131 | dp2 = (grub_efi_device_path_t *) ((char *) dp2 + len2); | |
132 | } | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static struct grub_efidisk_data * | |
138 | make_devices (void) | |
139 | { | |
140 | grub_efi_uintn_t num_handles; | |
141 | grub_efi_handle_t *handles; | |
142 | grub_efi_handle_t *handle; | |
143 | struct grub_efidisk_data *devices = 0; | |
144 | ||
145 | /* Find handles which support the disk io interface. */ | |
146 | handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &disk_io_guid, | |
147 | 0, &num_handles); | |
148 | if (! handles) | |
149 | return 0; | |
150 | ||
151 | /* Make a linked list of devices. */ | |
152 | for (handle = handles; num_handles--; handle++) | |
153 | { | |
154 | grub_efi_device_path_t *dp; | |
155 | grub_efi_device_path_t *ldp; | |
156 | struct grub_efidisk_data *d; | |
157 | grub_efi_block_io_t *bio; | |
158 | grub_efi_disk_io_t *dio; | |
159 | ||
7f362539 | 160 | dp = grub_efi_get_device_path (*handle); |
9cacaa17 | 161 | if (! dp) |
162 | continue; | |
163 | ||
164 | ldp = find_last_device_path (dp); | |
165 | if (! ldp) | |
166 | /* This is empty. Why? */ | |
167 | continue; | |
168 | ||
169 | bio = grub_efi_open_protocol (*handle, &block_io_guid, | |
170 | GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); | |
171 | dio = grub_efi_open_protocol (*handle, &disk_io_guid, | |
172 | GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); | |
173 | if (! bio || ! dio) | |
174 | /* This should not happen... Why? */ | |
175 | continue; | |
176 | ||
177 | d = grub_malloc (sizeof (*d)); | |
178 | if (! d) | |
179 | { | |
180 | /* Uggh. */ | |
181 | grub_free (handles); | |
182 | return 0; | |
183 | } | |
184 | ||
185 | d->handle = *handle; | |
186 | d->device_path = dp; | |
187 | d->last_device_path = ldp; | |
188 | d->block_io = bio; | |
189 | d->disk_io = dio; | |
190 | d->next = devices; | |
191 | devices = d; | |
192 | } | |
193 | ||
194 | grub_free (handles); | |
195 | ||
196 | return devices; | |
197 | } | |
198 | ||
199 | /* Find the parent device. */ | |
200 | static struct grub_efidisk_data * | |
201 | find_parent_device (struct grub_efidisk_data *devices, | |
202 | struct grub_efidisk_data *d) | |
203 | { | |
204 | grub_efi_device_path_t *dp, *ldp; | |
205 | struct grub_efidisk_data *parent; | |
206 | ||
207 | dp = duplicate_device_path (d->device_path); | |
208 | if (! dp) | |
209 | return 0; | |
210 | ||
211 | ldp = find_last_device_path (dp); | |
212 | ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE; | |
213 | ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; | |
214 | ldp->length[0] = sizeof (*ldp); | |
215 | ldp->length[1] = 0; | |
216 | ||
217 | for (parent = devices; parent; parent = parent->next) | |
218 | { | |
219 | /* Ignore itself. */ | |
220 | if (parent == d) | |
221 | continue; | |
222 | ||
223 | if (compare_device_paths (parent->device_path, dp) == 0) | |
224 | { | |
225 | /* Found. */ | |
226 | if (! parent->last_device_path) | |
227 | parent = 0; | |
228 | ||
229 | break; | |
230 | } | |
231 | } | |
232 | ||
233 | grub_free (dp); | |
234 | return parent; | |
235 | } | |
236 | ||
2965c7cc | 237 | static int |
238 | iterate_child_devices (struct grub_efidisk_data *devices, | |
239 | struct grub_efidisk_data *d, | |
240 | int (*hook) (struct grub_efidisk_data *child)) | |
241 | { | |
242 | struct grub_efidisk_data *p; | |
243 | ||
244 | for (p = devices; p; p = p->next) | |
245 | { | |
246 | grub_efi_device_path_t *dp, *ldp; | |
247 | ||
248 | dp = duplicate_device_path (p->device_path); | |
249 | if (! dp) | |
250 | return 0; | |
251 | ||
252 | ldp = find_last_device_path (dp); | |
253 | ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE; | |
254 | ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; | |
255 | ldp->length[0] = sizeof (*ldp); | |
256 | ldp->length[1] = 0; | |
257 | ||
258 | if (compare_device_paths (dp, d->device_path) == 0) | |
259 | if (hook (p)) | |
260 | { | |
261 | grub_free (dp); | |
262 | return 1; | |
263 | } | |
264 | ||
265 | grub_free (dp); | |
266 | } | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
9cacaa17 | 271 | /* Add a device into a list of devices in an ascending order. */ |
272 | static void | |
273 | add_device (struct grub_efidisk_data **devices, struct grub_efidisk_data *d) | |
274 | { | |
275 | struct grub_efidisk_data **p; | |
276 | struct grub_efidisk_data *n; | |
277 | ||
278 | for (p = devices; *p; p = &((*p)->next)) | |
279 | { | |
280 | int ret; | |
281 | ||
2965c7cc | 282 | ret = compare_device_paths (find_last_device_path ((*p)->device_path), |
283 | find_last_device_path (d->device_path)); | |
284 | if (ret == 0) | |
285 | ret = compare_device_paths ((*p)->device_path, | |
286 | d->device_path); | |
9cacaa17 | 287 | if (ret == 0) |
288 | return; | |
289 | else if (ret > 0) | |
290 | break; | |
291 | } | |
292 | ||
293 | n = grub_malloc (sizeof (*n)); | |
294 | if (! n) | |
295 | return; | |
296 | ||
297 | grub_memcpy (n, d, sizeof (*n)); | |
298 | n->next = (*p); | |
299 | (*p) = n; | |
300 | } | |
301 | ||
302 | /* Name the devices. */ | |
303 | static void | |
304 | name_devices (struct grub_efidisk_data *devices) | |
305 | { | |
306 | struct grub_efidisk_data *d; | |
307 | ||
308 | /* First, identify devices by media device paths. */ | |
309 | for (d = devices; d; d = d->next) | |
310 | { | |
311 | grub_efi_device_path_t *dp; | |
312 | ||
313 | dp = d->last_device_path; | |
314 | if (! dp) | |
315 | continue; | |
316 | ||
317 | if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE) | |
318 | { | |
319 | int is_hard_drive = 0; | |
320 | ||
321 | switch (GRUB_EFI_DEVICE_PATH_SUBTYPE (dp)) | |
322 | { | |
323 | case GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE: | |
324 | is_hard_drive = 1; | |
325 | /* Fall through by intention. */ | |
326 | case GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE: | |
327 | { | |
328 | struct grub_efidisk_data *parent; | |
329 | ||
330 | parent = find_parent_device (devices, d); | |
331 | if (parent) | |
332 | { | |
333 | if (is_hard_drive) | |
334 | { | |
335 | #if 0 | |
336 | grub_printf ("adding a hard drive by a partition: "); | |
337 | grub_print_device_path (parent->device_path); | |
338 | #endif | |
339 | add_device (&hd_devices, parent); | |
340 | } | |
341 | else | |
342 | { | |
343 | #if 0 | |
344 | grub_printf ("adding a cdrom by a partition: "); | |
345 | grub_print_device_path (parent->device_path); | |
346 | #endif | |
347 | add_device (&cd_devices, parent); | |
348 | } | |
349 | ||
350 | /* Mark the parent as used. */ | |
351 | parent->last_device_path = 0; | |
352 | } | |
353 | } | |
354 | /* Mark itself as used. */ | |
355 | d->last_device_path = 0; | |
356 | break; | |
357 | ||
358 | default: | |
359 | /* For now, ignore the others. */ | |
360 | break; | |
361 | } | |
362 | } | |
363 | } | |
364 | ||
365 | /* Let's see what can be added more. */ | |
366 | for (d = devices; d; d = d->next) | |
367 | { | |
368 | grub_efi_device_path_t *dp; | |
369 | grub_efi_block_io_media_t *m; | |
370 | ||
371 | dp = d->last_device_path; | |
372 | if (! dp) | |
373 | continue; | |
374 | ||
375 | m = d->block_io->media; | |
376 | if (m->logical_partition) | |
377 | { | |
378 | /* Only one partition in a non-media device. Assume that this | |
379 | is a floppy drive. */ | |
380 | #if 0 | |
381 | grub_printf ("adding a floppy by guessing: "); | |
382 | grub_print_device_path (d->device_path); | |
383 | #endif | |
384 | add_device (&fd_devices, d); | |
385 | } | |
386 | else if (m->read_only && m->block_size > GRUB_DISK_SECTOR_SIZE) | |
387 | { | |
388 | /* This check is too heuristic, but assume that this is a | |
389 | CDROM drive. */ | |
390 | #if 0 | |
391 | grub_printf ("adding a cdrom by guessing: "); | |
392 | grub_print_device_path (d->device_path); | |
393 | #endif | |
394 | add_device (&cd_devices, d); | |
395 | } | |
396 | else | |
397 | { | |
398 | /* The default is a hard drive. */ | |
399 | #if 0 | |
400 | grub_printf ("adding a hard drive by guessing: "); | |
401 | grub_print_device_path (d->device_path); | |
402 | #endif | |
403 | add_device (&hd_devices, d); | |
404 | } | |
405 | } | |
406 | } | |
407 | ||
408 | static void | |
409 | free_devices (struct grub_efidisk_data *devices) | |
410 | { | |
411 | struct grub_efidisk_data *p, *q; | |
412 | ||
413 | for (p = devices; p; p = q) | |
414 | { | |
415 | q = p->next; | |
416 | grub_free (p); | |
417 | } | |
418 | } | |
419 | ||
420 | /* Enumerate all disks to name devices. */ | |
421 | static void | |
422 | enumerate_disks (void) | |
423 | { | |
424 | struct grub_efidisk_data *devices; | |
425 | ||
426 | devices = make_devices (); | |
427 | if (! devices) | |
428 | return; | |
429 | ||
430 | name_devices (devices); | |
431 | free_devices (devices); | |
432 | } | |
433 | ||
434 | static int | |
435 | grub_efidisk_iterate (int (*hook) (const char *name)) | |
436 | { | |
437 | struct grub_efidisk_data *d; | |
438 | char buf[16]; | |
439 | int count; | |
440 | ||
441 | for (d = fd_devices, count = 0; d; d = d->next, count++) | |
442 | { | |
443 | grub_sprintf (buf, "fd%d", count); | |
444 | grub_dprintf ("efidisk", "iterating %s\n", buf); | |
445 | if (hook (buf)) | |
446 | return 1; | |
447 | } | |
448 | ||
449 | for (d = hd_devices, count = 0; d; d = d->next, count++) | |
450 | { | |
451 | grub_sprintf (buf, "hd%d", count); | |
452 | grub_dprintf ("efidisk", "iterating %s\n", buf); | |
453 | if (hook (buf)) | |
454 | return 1; | |
455 | } | |
456 | ||
457 | for (d = cd_devices, count = 0; d; d = d->next, count++) | |
458 | { | |
459 | grub_sprintf (buf, "cd%d", count); | |
460 | grub_dprintf ("efidisk", "iterating %s\n", buf); | |
461 | if (hook (buf)) | |
462 | return 1; | |
463 | } | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
468 | static int | |
469 | get_drive_number (const char *name) | |
470 | { | |
471 | unsigned long drive; | |
472 | ||
473 | if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd') | |
474 | goto fail; | |
475 | ||
476 | drive = grub_strtoul (name + 2, 0, 10); | |
477 | if (grub_errno != GRUB_ERR_NONE) | |
478 | goto fail; | |
479 | ||
480 | return (int) drive ; | |
481 | ||
482 | fail: | |
483 | grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a efidisk"); | |
484 | return -1; | |
485 | } | |
486 | ||
487 | static struct grub_efidisk_data * | |
488 | get_device (struct grub_efidisk_data *devices, int num) | |
489 | { | |
490 | struct grub_efidisk_data *d; | |
491 | ||
492 | for (d = devices; d && num; d = d->next, num--) | |
493 | ; | |
494 | ||
495 | if (num == 0) | |
496 | return d; | |
497 | ||
498 | return 0; | |
499 | } | |
500 | ||
501 | static grub_err_t | |
502 | grub_efidisk_open (const char *name, struct grub_disk *disk) | |
503 | { | |
504 | int num; | |
505 | struct grub_efidisk_data *d = 0; | |
506 | grub_efi_block_io_media_t *m; | |
507 | ||
508 | grub_dprintf ("efidisk", "opening %s\n", name); | |
509 | ||
510 | num = get_drive_number (name); | |
511 | if (num < 0) | |
512 | return grub_errno; | |
513 | ||
514 | switch (name[0]) | |
515 | { | |
516 | case 'f': | |
517 | disk->has_partitions = 0; | |
518 | d = get_device (fd_devices, num); | |
519 | break; | |
520 | case 'c': | |
521 | /* FIXME: a CDROM should have partitions, but not implemented yet. */ | |
522 | disk->has_partitions = 0; | |
523 | d = get_device (cd_devices, num); | |
524 | break; | |
525 | case 'h': | |
526 | disk->has_partitions = 1; | |
527 | d = get_device (hd_devices, num); | |
528 | break; | |
529 | default: | |
530 | /* Never reach here. */ | |
531 | break; | |
532 | } | |
533 | ||
534 | if (! d) | |
535 | return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device"); | |
536 | ||
537 | disk->id = ((num << 8) | name[0]); | |
538 | m = d->block_io->media; | |
539 | /* FIXME: Probably it is better to store the block size in the disk, | |
540 | and total sectors should be replaced with total blocks. */ | |
541 | grub_dprintf ("efidisk", "m = %p, last block = %llx, block size = %x\n", | |
4ad2d049 | 542 | m, (unsigned long long) m->last_block, m->block_size); |
9cacaa17 | 543 | disk->total_sectors = (m->last_block |
544 | * (m->block_size >> GRUB_DISK_SECTOR_BITS)); | |
545 | disk->data = d; | |
546 | ||
547 | grub_dprintf ("efidisk", "opening %s succeeded\n", name); | |
548 | ||
549 | return GRUB_ERR_NONE; | |
550 | } | |
551 | ||
552 | static void | |
553 | grub_efidisk_close (struct grub_disk *disk __attribute__ ((unused))) | |
554 | { | |
555 | /* EFI disks do not allocate extra memory, so nothing to do here. */ | |
7f362539 | 556 | grub_dprintf ("efidisk", "closing %s\n", disk->name); |
9cacaa17 | 557 | } |
558 | ||
559 | static grub_err_t | |
524a1e6a | 560 | grub_efidisk_read (struct grub_disk *disk, grub_disk_addr_t sector, |
561 | grub_size_t size, char *buf) | |
9cacaa17 | 562 | { |
563 | /* For now, use the disk io interface rather than the block io's. */ | |
564 | struct grub_efidisk_data *d; | |
565 | grub_efi_disk_io_t *dio; | |
566 | grub_efi_block_io_t *bio; | |
567 | grub_efi_status_t status; | |
568 | ||
569 | d = disk->data; | |
570 | dio = d->disk_io; | |
571 | bio = d->block_io; | |
572 | ||
524a1e6a | 573 | grub_dprintf ("efidisk", |
4ad2d049 | 574 | "reading 0x%lx sectors at the sector 0x%llx from %s\n", |
575 | (unsigned long) size, (unsigned long long) sector, disk->name); | |
9cacaa17 | 576 | |
20011694 | 577 | status = efi_call_5 (dio->read, dio, bio->media->media_id, |
9cacaa17 | 578 | (grub_efi_uint64_t) sector << GRUB_DISK_SECTOR_BITS, |
579 | (grub_efi_uintn_t) size << GRUB_DISK_SECTOR_BITS, | |
580 | buf); | |
581 | if (status != GRUB_EFI_SUCCESS) | |
582 | return grub_error (GRUB_ERR_READ_ERROR, "efidisk read error"); | |
583 | ||
584 | return GRUB_ERR_NONE; | |
585 | } | |
586 | ||
587 | static grub_err_t | |
524a1e6a | 588 | grub_efidisk_write (struct grub_disk *disk, grub_disk_addr_t sector, |
589 | grub_size_t size, const char *buf) | |
9cacaa17 | 590 | { |
591 | /* For now, use the disk io interface rather than the block io's. */ | |
592 | struct grub_efidisk_data *d; | |
593 | grub_efi_disk_io_t *dio; | |
594 | grub_efi_block_io_t *bio; | |
595 | grub_efi_status_t status; | |
596 | ||
597 | d = disk->data; | |
598 | dio = d->disk_io; | |
599 | bio = d->block_io; | |
600 | ||
524a1e6a | 601 | grub_dprintf ("efidisk", |
4ad2d049 | 602 | "writing 0x%lx sectors at the sector 0x%llx to %s\n", |
603 | (unsigned long) size, (unsigned long long) sector, disk->name); | |
9cacaa17 | 604 | |
20011694 | 605 | status = efi_call_5 (dio->write, dio, bio->media->media_id, |
9cacaa17 | 606 | (grub_efi_uint64_t) sector << GRUB_DISK_SECTOR_BITS, |
607 | (grub_efi_uintn_t) size << GRUB_DISK_SECTOR_BITS, | |
608 | (void *) buf); | |
609 | if (status != GRUB_EFI_SUCCESS) | |
610 | return grub_error (GRUB_ERR_WRITE_ERROR, "efidisk write error"); | |
611 | ||
612 | return GRUB_ERR_NONE; | |
613 | } | |
614 | ||
615 | static struct grub_disk_dev grub_efidisk_dev = | |
616 | { | |
617 | .name = "efidisk", | |
618 | .id = GRUB_DISK_DEVICE_EFIDISK_ID, | |
619 | .iterate = grub_efidisk_iterate, | |
620 | .open = grub_efidisk_open, | |
621 | .close = grub_efidisk_close, | |
622 | .read = grub_efidisk_read, | |
623 | .write = grub_efidisk_write, | |
624 | .next = 0 | |
625 | }; | |
626 | ||
627 | void | |
628 | grub_efidisk_init (void) | |
629 | { | |
630 | enumerate_disks (); | |
631 | grub_disk_dev_register (&grub_efidisk_dev); | |
632 | } | |
633 | ||
634 | void | |
635 | grub_efidisk_fini (void) | |
636 | { | |
637 | free_devices (fd_devices); | |
638 | free_devices (hd_devices); | |
639 | free_devices (cd_devices); | |
640 | grub_disk_dev_unregister (&grub_efidisk_dev); | |
641 | } | |
2965c7cc | 642 | |
643 | /* Some utility functions to map GRUB devices with EFI devices. */ | |
644 | grub_efi_handle_t | |
645 | grub_efidisk_get_device_handle (grub_disk_t disk) | |
646 | { | |
647 | struct grub_efidisk_data *d; | |
648 | char type; | |
649 | ||
650 | if (disk->dev->id != GRUB_DISK_DEVICE_EFIDISK_ID) | |
651 | return 0; | |
652 | ||
653 | d = disk->data; | |
654 | type = disk->name[0]; | |
655 | ||
656 | switch (type) | |
657 | { | |
658 | case 'f': | |
659 | /* This is the simplest case. */ | |
660 | return d->handle; | |
661 | ||
662 | case 'c': | |
663 | /* FIXME: probably this is not correct. */ | |
664 | return d->handle; | |
665 | ||
666 | case 'h': | |
667 | /* If this is the whole disk, just return its own data. */ | |
668 | if (! disk->partition) | |
669 | return d->handle; | |
670 | ||
671 | /* Otherwise, we must query the corresponding device to the firmware. */ | |
672 | { | |
673 | struct grub_efidisk_data *devices; | |
674 | grub_efi_handle_t handle = 0; | |
675 | auto int find_partition (struct grub_efidisk_data *c); | |
676 | ||
677 | int find_partition (struct grub_efidisk_data *c) | |
678 | { | |
679 | grub_efi_hard_drive_device_path_t hd; | |
680 | ||
681 | grub_memcpy (&hd, c->last_device_path, sizeof (hd)); | |
682 | ||
683 | if ((GRUB_EFI_DEVICE_PATH_TYPE (c->last_device_path) | |
684 | == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE) | |
7f362539 | 685 | && (GRUB_EFI_DEVICE_PATH_SUBTYPE (c->last_device_path) |
2965c7cc | 686 | == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE) |
687 | && (grub_partition_get_start (disk->partition) | |
688 | == hd.partition_start) | |
689 | && (grub_partition_get_len (disk->partition) | |
690 | == hd.partition_size)) | |
691 | { | |
692 | handle = c->handle; | |
693 | return 1; | |
694 | } | |
695 | ||
696 | return 0; | |
697 | } | |
698 | ||
699 | devices = make_devices (); | |
700 | iterate_child_devices (devices, d, find_partition); | |
701 | free_devices (devices); | |
702 | ||
703 | if (handle != 0) | |
704 | return handle; | |
705 | } | |
706 | break; | |
707 | ||
708 | default: | |
709 | break; | |
710 | } | |
711 | ||
712 | return 0; | |
713 | } | |
714 | ||
715 | char * | |
716 | grub_efidisk_get_device_name (grub_efi_handle_t *handle) | |
717 | { | |
718 | grub_efi_device_path_t *dp, *ldp; | |
719 | ||
7f362539 | 720 | dp = grub_efi_get_device_path (handle); |
2965c7cc | 721 | if (! dp) |
722 | return 0; | |
723 | ||
724 | ldp = find_last_device_path (dp); | |
725 | if (! ldp) | |
726 | return 0; | |
727 | ||
728 | if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE | |
729 | && (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) | |
730 | == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE)) | |
731 | { | |
732 | /* This is a hard disk partition. */ | |
733 | grub_disk_t parent = 0; | |
734 | char *partition_name = 0; | |
735 | char *device_name; | |
736 | grub_efi_device_path_t *dup_dp, *dup_ldp; | |
737 | grub_efi_hard_drive_device_path_t hd; | |
738 | auto int find_parent_disk (const char *name); | |
739 | auto int find_partition (grub_disk_t disk, const grub_partition_t part); | |
740 | ||
741 | /* Find the disk which is the parent of a given hard disk partition. */ | |
742 | int find_parent_disk (const char *name) | |
743 | { | |
744 | grub_disk_t disk; | |
745 | ||
746 | disk = grub_disk_open (name); | |
747 | if (! disk) | |
748 | return 1; | |
749 | ||
750 | if (disk->dev->id == GRUB_DISK_DEVICE_EFIDISK_ID) | |
751 | { | |
752 | struct grub_efidisk_data *d; | |
753 | ||
754 | d = disk->data; | |
755 | if (compare_device_paths (d->device_path, dup_dp) == 0) | |
756 | { | |
757 | parent = disk; | |
758 | return 1; | |
759 | } | |
760 | } | |
761 | ||
762 | grub_disk_close (disk); | |
763 | return 0; | |
764 | } | |
765 | ||
766 | /* Find the identical partition. */ | |
767 | int find_partition (grub_disk_t disk __attribute__ ((unused)), | |
768 | const grub_partition_t part) | |
769 | { | |
770 | if (grub_partition_get_start (part) == hd.partition_start | |
771 | && grub_partition_get_len (part) == hd.partition_size) | |
772 | { | |
773 | partition_name = grub_partition_get_name (part); | |
774 | return 1; | |
775 | } | |
776 | ||
777 | return 0; | |
778 | } | |
779 | ||
780 | /* It is necessary to duplicate the device path so that GRUB | |
781 | can overwrite it. */ | |
782 | dup_dp = duplicate_device_path (dp); | |
783 | if (! dup_dp) | |
784 | return 0; | |
785 | ||
786 | dup_ldp = find_last_device_path (dup_dp); | |
787 | dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE; | |
788 | dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE; | |
789 | dup_ldp->length[0] = sizeof (*dup_ldp); | |
790 | dup_ldp->length[1] = 0; | |
791 | ||
792 | grub_efidisk_iterate (find_parent_disk); | |
793 | grub_free (dup_dp); | |
794 | ||
795 | if (! parent) | |
796 | return 0; | |
797 | ||
798 | /* Find a partition which matches the hard drive device path. */ | |
799 | grub_memcpy (&hd, ldp, sizeof (hd)); | |
800 | grub_partition_iterate (parent, find_partition); | |
801 | ||
802 | if (! partition_name) | |
803 | { | |
804 | grub_disk_close (parent); | |
805 | return 0; | |
806 | } | |
807 | ||
808 | device_name = grub_malloc (grub_strlen (parent->name) + 1 | |
809 | + grub_strlen (partition_name) + 1); | |
810 | if (! device_name) | |
811 | { | |
812 | grub_free (partition_name); | |
813 | grub_disk_close (parent); | |
814 | return 0; | |
815 | } | |
816 | ||
817 | grub_sprintf (device_name, "%s,%s", parent->name, partition_name); | |
818 | grub_free (partition_name); | |
819 | grub_disk_close (parent); | |
820 | return device_name; | |
821 | } | |
822 | else | |
823 | { | |
824 | /* This should be an entire disk. */ | |
825 | auto int find_disk (const char *name); | |
826 | char *device_name = 0; | |
827 | ||
828 | int find_disk (const char *name) | |
829 | { | |
830 | grub_disk_t disk; | |
831 | ||
832 | disk = grub_disk_open (name); | |
833 | if (! disk) | |
834 | return 1; | |
835 | ||
836 | if (disk->id == GRUB_DISK_DEVICE_EFIDISK_ID) | |
837 | { | |
838 | struct grub_efidisk_data *d; | |
839 | ||
840 | d = disk->data; | |
841 | if (compare_device_paths (d->device_path, dp) == 0) | |
842 | { | |
843 | device_name = grub_strdup (disk->name); | |
844 | grub_disk_close (disk); | |
845 | return 1; | |
846 | } | |
847 | } | |
848 | ||
849 | grub_disk_close (disk); | |
850 | return 0; | |
851 | ||
852 | } | |
853 | ||
854 | grub_efidisk_iterate (find_disk); | |
855 | return device_name; | |
856 | } | |
857 | ||
858 | return 0; | |
859 | } |