c42cb97f08
iPXE adds Simple File System Protocol to loaded image handle, as side effect it also adds Block IO protocol (according to comments, to work around some bugs in EDK2). GRUB assumes that every device with Block IO is disk and skips network initialization entirely. But iPXE Block IO implementation is just a stub which always fails for every operation so cannot be used. Attempt to detect and skip such devices. We are using media ID which iPXE sets to "iPXE" and block IO size in hope that no real device would announce 1B block ... Closes: 50518
893 lines
22 KiB
C
893 lines
22 KiB
C
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/disk.h>
|
|
#include <grub/partition.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/types.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/err.h>
|
|
#include <grub/term.h>
|
|
#include <grub/efi/api.h>
|
|
#include <grub/efi/efi.h>
|
|
#include <grub/efi/disk.h>
|
|
|
|
struct grub_efidisk_data
|
|
{
|
|
grub_efi_handle_t handle;
|
|
grub_efi_device_path_t *device_path;
|
|
grub_efi_device_path_t *last_device_path;
|
|
grub_efi_block_io_t *block_io;
|
|
struct grub_efidisk_data *next;
|
|
};
|
|
|
|
/* GUID. */
|
|
static grub_efi_guid_t block_io_guid = GRUB_EFI_BLOCK_IO_GUID;
|
|
|
|
static struct grub_efidisk_data *fd_devices;
|
|
static struct grub_efidisk_data *hd_devices;
|
|
static struct grub_efidisk_data *cd_devices;
|
|
|
|
static struct grub_efidisk_data *
|
|
make_devices (void)
|
|
{
|
|
grub_efi_uintn_t num_handles;
|
|
grub_efi_handle_t *handles;
|
|
grub_efi_handle_t *handle;
|
|
struct grub_efidisk_data *devices = 0;
|
|
|
|
/* Find handles which support the disk io interface. */
|
|
handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &block_io_guid,
|
|
0, &num_handles);
|
|
if (! handles)
|
|
return 0;
|
|
|
|
/* Make a linked list of devices. */
|
|
for (handle = handles; num_handles--; handle++)
|
|
{
|
|
grub_efi_device_path_t *dp;
|
|
grub_efi_device_path_t *ldp;
|
|
struct grub_efidisk_data *d;
|
|
grub_efi_block_io_t *bio;
|
|
|
|
dp = grub_efi_get_device_path (*handle);
|
|
if (! dp)
|
|
continue;
|
|
|
|
ldp = grub_efi_find_last_device_path (dp);
|
|
if (! ldp)
|
|
/* This is empty. Why? */
|
|
continue;
|
|
|
|
bio = grub_efi_open_protocol (*handle, &block_io_guid,
|
|
GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
if (! bio)
|
|
/* This should not happen... Why? */
|
|
continue;
|
|
|
|
/* iPXE adds stub Block IO protocol to loaded image device handle. It is
|
|
completely non-functional and simply returns an error for every method.
|
|
So attempt to detect and skip it. Magic number is literal "iPXE" and
|
|
check block size as well */
|
|
/* FIXME: shoud we close it? We do not do it elsewhere */
|
|
if (bio->media && bio->media->media_id == 0x69505845U &&
|
|
bio->media->block_size == 1)
|
|
continue;
|
|
|
|
d = grub_malloc (sizeof (*d));
|
|
if (! d)
|
|
{
|
|
/* Uggh. */
|
|
grub_free (handles);
|
|
while (devices)
|
|
{
|
|
d = devices->next;
|
|
grub_free (devices);
|
|
devices = d;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
d->handle = *handle;
|
|
d->device_path = dp;
|
|
d->last_device_path = ldp;
|
|
d->block_io = bio;
|
|
d->next = devices;
|
|
devices = d;
|
|
}
|
|
|
|
grub_free (handles);
|
|
|
|
return devices;
|
|
}
|
|
|
|
/* Find the parent device. */
|
|
static struct grub_efidisk_data *
|
|
find_parent_device (struct grub_efidisk_data *devices,
|
|
struct grub_efidisk_data *d)
|
|
{
|
|
grub_efi_device_path_t *dp, *ldp;
|
|
struct grub_efidisk_data *parent;
|
|
|
|
dp = grub_efi_duplicate_device_path (d->device_path);
|
|
if (! dp)
|
|
return 0;
|
|
|
|
ldp = grub_efi_find_last_device_path (dp);
|
|
ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
|
|
ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
|
|
ldp->length = sizeof (*ldp);
|
|
|
|
for (parent = devices; parent; parent = parent->next)
|
|
{
|
|
/* Ignore itself. */
|
|
if (parent == d)
|
|
continue;
|
|
|
|
if (grub_efi_compare_device_paths (parent->device_path, dp) == 0)
|
|
break;
|
|
}
|
|
|
|
grub_free (dp);
|
|
return parent;
|
|
}
|
|
|
|
static int
|
|
is_child (struct grub_efidisk_data *child,
|
|
struct grub_efidisk_data *parent)
|
|
{
|
|
grub_efi_device_path_t *dp, *ldp;
|
|
int ret;
|
|
|
|
dp = grub_efi_duplicate_device_path (child->device_path);
|
|
if (! dp)
|
|
return 0;
|
|
|
|
ldp = grub_efi_find_last_device_path (dp);
|
|
ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
|
|
ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
|
|
ldp->length = sizeof (*ldp);
|
|
|
|
ret = (grub_efi_compare_device_paths (dp, parent->device_path) == 0);
|
|
grub_free (dp);
|
|
return ret;
|
|
}
|
|
|
|
#define FOR_CHILDREN(p, dev) for (p = dev; p; p = p->next) if (is_child (p, d))
|
|
|
|
/* Add a device into a list of devices in an ascending order. */
|
|
static void
|
|
add_device (struct grub_efidisk_data **devices, struct grub_efidisk_data *d)
|
|
{
|
|
struct grub_efidisk_data **p;
|
|
struct grub_efidisk_data *n;
|
|
|
|
for (p = devices; *p; p = &((*p)->next))
|
|
{
|
|
int ret;
|
|
|
|
ret = grub_efi_compare_device_paths (grub_efi_find_last_device_path ((*p)->device_path),
|
|
grub_efi_find_last_device_path (d->device_path));
|
|
if (ret == 0)
|
|
ret = grub_efi_compare_device_paths ((*p)->device_path,
|
|
d->device_path);
|
|
if (ret == 0)
|
|
return;
|
|
else if (ret > 0)
|
|
break;
|
|
}
|
|
|
|
n = grub_malloc (sizeof (*n));
|
|
if (! n)
|
|
return;
|
|
|
|
grub_memcpy (n, d, sizeof (*n));
|
|
n->next = (*p);
|
|
(*p) = n;
|
|
}
|
|
|
|
/* Name the devices. */
|
|
static void
|
|
name_devices (struct grub_efidisk_data *devices)
|
|
{
|
|
struct grub_efidisk_data *d;
|
|
|
|
/* First, identify devices by media device paths. */
|
|
for (d = devices; d; d = d->next)
|
|
{
|
|
grub_efi_device_path_t *dp;
|
|
|
|
dp = d->last_device_path;
|
|
if (! dp)
|
|
continue;
|
|
|
|
if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE)
|
|
{
|
|
int is_hard_drive = 0;
|
|
|
|
switch (GRUB_EFI_DEVICE_PATH_SUBTYPE (dp))
|
|
{
|
|
case GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE:
|
|
is_hard_drive = 1;
|
|
/* Fall through by intention. */
|
|
case GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE:
|
|
{
|
|
struct grub_efidisk_data *parent, *parent2;
|
|
|
|
parent = find_parent_device (devices, d);
|
|
if (!parent)
|
|
{
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("skipping orphaned partition: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
break;
|
|
}
|
|
parent2 = find_parent_device (devices, parent);
|
|
if (parent2)
|
|
{
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("skipping subpartition: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
/* Mark itself as used. */
|
|
d->last_device_path = 0;
|
|
break;
|
|
}
|
|
if (!parent->last_device_path)
|
|
{
|
|
d->last_device_path = 0;
|
|
break;
|
|
}
|
|
if (is_hard_drive)
|
|
{
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("adding a hard drive by a partition: ");
|
|
grub_efi_print_device_path (parent->device_path);
|
|
#endif
|
|
add_device (&hd_devices, parent);
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("adding a cdrom by a partition: ");
|
|
grub_efi_print_device_path (parent->device_path);
|
|
#endif
|
|
add_device (&cd_devices, parent);
|
|
}
|
|
|
|
/* Mark the parent as used. */
|
|
parent->last_device_path = 0;
|
|
}
|
|
/* Mark itself as used. */
|
|
d->last_device_path = 0;
|
|
break;
|
|
|
|
default:
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("skipping other type: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
/* For now, ignore the others. */
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("skipping non-media: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Let's see what can be added more. */
|
|
for (d = devices; d; d = d->next)
|
|
{
|
|
grub_efi_device_path_t *dp;
|
|
grub_efi_block_io_media_t *m;
|
|
int is_floppy = 0;
|
|
|
|
dp = d->last_device_path;
|
|
if (! dp)
|
|
continue;
|
|
|
|
/* Ghosts proudly presented by Apple. */
|
|
if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
|
|
&& GRUB_EFI_DEVICE_PATH_SUBTYPE (dp)
|
|
== GRUB_EFI_VENDOR_MEDIA_DEVICE_PATH_SUBTYPE)
|
|
{
|
|
grub_efi_vendor_device_path_t *vendor = (grub_efi_vendor_device_path_t *) dp;
|
|
const struct grub_efi_guid apple = GRUB_EFI_VENDOR_APPLE_GUID;
|
|
|
|
if (vendor->header.length == sizeof (*vendor)
|
|
&& grub_memcmp (&vendor->vendor_guid, &apple,
|
|
sizeof (vendor->vendor_guid)) == 0
|
|
&& find_parent_device (devices, d))
|
|
continue;
|
|
}
|
|
|
|
m = d->block_io->media;
|
|
if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_ACPI_DEVICE_PATH_TYPE
|
|
&& GRUB_EFI_DEVICE_PATH_SUBTYPE (dp)
|
|
== GRUB_EFI_ACPI_DEVICE_PATH_SUBTYPE)
|
|
{
|
|
grub_efi_acpi_device_path_t *acpi
|
|
= (grub_efi_acpi_device_path_t *) dp;
|
|
/* Floppy EISA ID. */
|
|
if (acpi->hid == 0x60441d0 || acpi->hid == 0x70041d0
|
|
|| acpi->hid == 0x70141d1)
|
|
is_floppy = 1;
|
|
}
|
|
if (is_floppy)
|
|
{
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("adding a floppy: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
add_device (&fd_devices, d);
|
|
}
|
|
else if (m->read_only && m->block_size > GRUB_DISK_SECTOR_SIZE)
|
|
{
|
|
/* This check is too heuristic, but assume that this is a
|
|
CDROM drive. */
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("adding a cdrom by guessing: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
add_device (&cd_devices, d);
|
|
}
|
|
else
|
|
{
|
|
/* The default is a hard drive. */
|
|
#ifdef DEBUG_NAMES
|
|
grub_printf ("adding a hard drive by guessing: ");
|
|
grub_efi_print_device_path (d->device_path);
|
|
#endif
|
|
add_device (&hd_devices, d);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_devices (struct grub_efidisk_data *devices)
|
|
{
|
|
struct grub_efidisk_data *p, *q;
|
|
|
|
for (p = devices; p; p = q)
|
|
{
|
|
q = p->next;
|
|
grub_free (p);
|
|
}
|
|
}
|
|
|
|
/* Enumerate all disks to name devices. */
|
|
static void
|
|
enumerate_disks (void)
|
|
{
|
|
struct grub_efidisk_data *devices;
|
|
|
|
devices = make_devices ();
|
|
if (! devices)
|
|
return;
|
|
|
|
name_devices (devices);
|
|
free_devices (devices);
|
|
}
|
|
|
|
static int
|
|
grub_efidisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
|
|
grub_disk_pull_t pull)
|
|
{
|
|
struct grub_efidisk_data *d;
|
|
char buf[16];
|
|
int count;
|
|
|
|
switch (pull)
|
|
{
|
|
case GRUB_DISK_PULL_NONE:
|
|
for (d = hd_devices, count = 0; d; d = d->next, count++)
|
|
{
|
|
grub_snprintf (buf, sizeof (buf), "hd%d", count);
|
|
grub_dprintf ("efidisk", "iterating %s\n", buf);
|
|
if (hook (buf, hook_data))
|
|
return 1;
|
|
}
|
|
break;
|
|
case GRUB_DISK_PULL_REMOVABLE:
|
|
for (d = fd_devices, count = 0; d; d = d->next, count++)
|
|
{
|
|
grub_snprintf (buf, sizeof (buf), "fd%d", count);
|
|
grub_dprintf ("efidisk", "iterating %s\n", buf);
|
|
if (hook (buf, hook_data))
|
|
return 1;
|
|
}
|
|
|
|
for (d = cd_devices, count = 0; d; d = d->next, count++)
|
|
{
|
|
grub_snprintf (buf, sizeof (buf), "cd%d", count);
|
|
grub_dprintf ("efidisk", "iterating %s\n", buf);
|
|
if (hook (buf, hook_data))
|
|
return 1;
|
|
}
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
get_drive_number (const char *name)
|
|
{
|
|
unsigned long drive;
|
|
|
|
if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd')
|
|
goto fail;
|
|
|
|
drive = grub_strtoul (name + 2, 0, 10);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
goto fail;
|
|
|
|
return (int) drive ;
|
|
|
|
fail:
|
|
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a efidisk");
|
|
return -1;
|
|
}
|
|
|
|
static struct grub_efidisk_data *
|
|
get_device (struct grub_efidisk_data *devices, int num)
|
|
{
|
|
struct grub_efidisk_data *d;
|
|
|
|
for (d = devices; d && num; d = d->next, num--)
|
|
;
|
|
|
|
if (num == 0)
|
|
return d;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_efidisk_open (const char *name, struct grub_disk *disk)
|
|
{
|
|
int num;
|
|
struct grub_efidisk_data *d = 0;
|
|
grub_efi_block_io_media_t *m;
|
|
|
|
grub_dprintf ("efidisk", "opening %s\n", name);
|
|
|
|
num = get_drive_number (name);
|
|
if (num < 0)
|
|
return grub_errno;
|
|
|
|
switch (name[0])
|
|
{
|
|
case 'f':
|
|
d = get_device (fd_devices, num);
|
|
break;
|
|
case 'c':
|
|
d = get_device (cd_devices, num);
|
|
break;
|
|
case 'h':
|
|
d = get_device (hd_devices, num);
|
|
break;
|
|
default:
|
|
/* Never reach here. */
|
|
break;
|
|
}
|
|
|
|
if (! d)
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device");
|
|
|
|
disk->id = ((num << GRUB_CHAR_BIT) | name[0]);
|
|
m = d->block_io->media;
|
|
/* FIXME: Probably it is better to store the block size in the disk,
|
|
and total sectors should be replaced with total blocks. */
|
|
grub_dprintf ("efidisk",
|
|
"m = %p, last block = %llx, block size = %x, io align = %x\n",
|
|
m, (unsigned long long) m->last_block, m->block_size,
|
|
m->io_align);
|
|
|
|
/* Ensure required buffer alignment is a power of two (or is zero). */
|
|
if (m->io_align & (m->io_align - 1))
|
|
return grub_error (GRUB_ERR_IO, "invalid buffer alignment %d", m->io_align);
|
|
|
|
disk->total_sectors = m->last_block + 1;
|
|
/* Don't increase this value due to bug in some EFI. */
|
|
disk->max_agglomerate = 0xa0000 >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS);
|
|
if (m->block_size & (m->block_size - 1) || !m->block_size)
|
|
return grub_error (GRUB_ERR_IO, "invalid sector size %d",
|
|
m->block_size);
|
|
for (disk->log_sector_size = 0;
|
|
(1U << disk->log_sector_size) < m->block_size;
|
|
disk->log_sector_size++);
|
|
disk->data = d;
|
|
|
|
grub_dprintf ("efidisk", "opening %s succeeded\n", name);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static void
|
|
grub_efidisk_close (struct grub_disk *disk __attribute__ ((unused)))
|
|
{
|
|
/* EFI disks do not allocate extra memory, so nothing to do here. */
|
|
grub_dprintf ("efidisk", "closing %s\n", disk->name);
|
|
}
|
|
|
|
static grub_efi_status_t
|
|
grub_efidisk_readwrite (struct grub_disk *disk, grub_disk_addr_t sector,
|
|
grub_size_t size, char *buf, int wr)
|
|
{
|
|
struct grub_efidisk_data *d;
|
|
grub_efi_block_io_t *bio;
|
|
grub_efi_status_t status;
|
|
grub_size_t io_align, num_bytes;
|
|
char *aligned_buf;
|
|
|
|
d = disk->data;
|
|
bio = d->block_io;
|
|
|
|
/* Set alignment to 1 if 0 specified */
|
|
io_align = bio->media->io_align ? bio->media->io_align : 1;
|
|
num_bytes = size << disk->log_sector_size;
|
|
|
|
if ((grub_addr_t) buf & (io_align - 1))
|
|
{
|
|
aligned_buf = grub_memalign (io_align, num_bytes);
|
|
if (! aligned_buf)
|
|
return GRUB_EFI_OUT_OF_RESOURCES;
|
|
if (wr)
|
|
grub_memcpy (aligned_buf, buf, num_bytes);
|
|
}
|
|
else
|
|
{
|
|
aligned_buf = buf;
|
|
}
|
|
|
|
status = efi_call_5 ((wr ? bio->write_blocks : bio->read_blocks), bio,
|
|
bio->media->media_id, (grub_efi_uint64_t) sector,
|
|
(grub_efi_uintn_t) num_bytes, aligned_buf);
|
|
|
|
if ((grub_addr_t) buf & (io_align - 1))
|
|
{
|
|
if (!wr)
|
|
grub_memcpy (buf, aligned_buf, num_bytes);
|
|
grub_free (aligned_buf);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_efidisk_read (struct grub_disk *disk, grub_disk_addr_t sector,
|
|
grub_size_t size, char *buf)
|
|
{
|
|
grub_efi_status_t status;
|
|
|
|
grub_dprintf ("efidisk",
|
|
"reading 0x%lx sectors at the sector 0x%llx from %s\n",
|
|
(unsigned long) size, (unsigned long long) sector, disk->name);
|
|
|
|
status = grub_efidisk_readwrite (disk, sector, size, buf, 0);
|
|
|
|
if (status == GRUB_EFI_NO_MEDIA)
|
|
return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("no media in `%s'"), disk->name);
|
|
else if (status != GRUB_EFI_SUCCESS)
|
|
return grub_error (GRUB_ERR_READ_ERROR,
|
|
N_("failure reading sector 0x%llx from `%s'"),
|
|
(unsigned long long) sector,
|
|
disk->name);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_efidisk_write (struct grub_disk *disk, grub_disk_addr_t sector,
|
|
grub_size_t size, const char *buf)
|
|
{
|
|
grub_efi_status_t status;
|
|
|
|
grub_dprintf ("efidisk",
|
|
"writing 0x%lx sectors at the sector 0x%llx to %s\n",
|
|
(unsigned long) size, (unsigned long long) sector, disk->name);
|
|
|
|
status = grub_efidisk_readwrite (disk, sector, size, (char *) buf, 1);
|
|
|
|
if (status == GRUB_EFI_NO_MEDIA)
|
|
return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("no media in `%s'"), disk->name);
|
|
else if (status != GRUB_EFI_SUCCESS)
|
|
return grub_error (GRUB_ERR_WRITE_ERROR,
|
|
N_("failure writing sector 0x%llx to `%s'"),
|
|
(unsigned long long) sector, disk->name);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static struct grub_disk_dev grub_efidisk_dev =
|
|
{
|
|
.name = "efidisk",
|
|
.id = GRUB_DISK_DEVICE_EFIDISK_ID,
|
|
.iterate = grub_efidisk_iterate,
|
|
.open = grub_efidisk_open,
|
|
.close = grub_efidisk_close,
|
|
.read = grub_efidisk_read,
|
|
.write = grub_efidisk_write,
|
|
.next = 0
|
|
};
|
|
|
|
void
|
|
grub_efidisk_fini (void)
|
|
{
|
|
free_devices (fd_devices);
|
|
free_devices (hd_devices);
|
|
free_devices (cd_devices);
|
|
fd_devices = 0;
|
|
hd_devices = 0;
|
|
cd_devices = 0;
|
|
grub_disk_dev_unregister (&grub_efidisk_dev);
|
|
}
|
|
|
|
void
|
|
grub_efidisk_init (void)
|
|
{
|
|
grub_disk_firmware_fini = grub_efidisk_fini;
|
|
|
|
enumerate_disks ();
|
|
grub_disk_dev_register (&grub_efidisk_dev);
|
|
}
|
|
|
|
/* Some utility functions to map GRUB devices with EFI devices. */
|
|
grub_efi_handle_t
|
|
grub_efidisk_get_device_handle (grub_disk_t disk)
|
|
{
|
|
struct grub_efidisk_data *d;
|
|
char type;
|
|
|
|
if (disk->dev->id != GRUB_DISK_DEVICE_EFIDISK_ID)
|
|
return 0;
|
|
|
|
d = disk->data;
|
|
type = disk->name[0];
|
|
|
|
switch (type)
|
|
{
|
|
case 'f':
|
|
/* This is the simplest case. */
|
|
return d->handle;
|
|
|
|
case 'c':
|
|
/* FIXME: probably this is not correct. */
|
|
return d->handle;
|
|
|
|
case 'h':
|
|
/* If this is the whole disk, just return its own data. */
|
|
if (! disk->partition)
|
|
return d->handle;
|
|
|
|
/* Otherwise, we must query the corresponding device to the firmware. */
|
|
{
|
|
struct grub_efidisk_data *devices;
|
|
grub_efi_handle_t handle = 0;
|
|
struct grub_efidisk_data *c;
|
|
|
|
devices = make_devices ();
|
|
FOR_CHILDREN (c, devices)
|
|
{
|
|
grub_efi_hard_drive_device_path_t *hd;
|
|
|
|
hd = (grub_efi_hard_drive_device_path_t *) c->last_device_path;
|
|
|
|
if ((GRUB_EFI_DEVICE_PATH_TYPE (c->last_device_path)
|
|
== GRUB_EFI_MEDIA_DEVICE_PATH_TYPE)
|
|
&& (GRUB_EFI_DEVICE_PATH_SUBTYPE (c->last_device_path)
|
|
== GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE)
|
|
&& (grub_partition_get_start (disk->partition)
|
|
== (hd->partition_start << (disk->log_sector_size
|
|
- GRUB_DISK_SECTOR_BITS)))
|
|
&& (grub_partition_get_len (disk->partition)
|
|
== (hd->partition_size << (disk->log_sector_size
|
|
- GRUB_DISK_SECTOR_BITS))))
|
|
{
|
|
handle = c->handle;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free_devices (devices);
|
|
|
|
if (handle != 0)
|
|
return handle;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define NEEDED_BUFLEN sizeof ("XdXXXXXXXXXX")
|
|
static inline int
|
|
get_diskname_from_path_real (const grub_efi_device_path_t *path,
|
|
struct grub_efidisk_data *head,
|
|
char *buf)
|
|
{
|
|
int count = 0;
|
|
struct grub_efidisk_data *d;
|
|
for (d = head, count = 0; d; d = d->next, count++)
|
|
if (grub_efi_compare_device_paths (d->device_path, path) == 0)
|
|
{
|
|
grub_snprintf (buf, NEEDED_BUFLEN - 1, "d%d", count);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int
|
|
get_diskname_from_path (const grub_efi_device_path_t *path,
|
|
char *buf)
|
|
{
|
|
if (get_diskname_from_path_real (path, hd_devices, buf + 1))
|
|
{
|
|
buf[0] = 'h';
|
|
return 1;
|
|
}
|
|
|
|
if (get_diskname_from_path_real (path, fd_devices, buf + 1))
|
|
{
|
|
buf[0] = 'f';
|
|
return 1;
|
|
}
|
|
|
|
if (get_diskname_from_path_real (path, cd_devices, buf + 1))
|
|
{
|
|
buf[0] = 'c';
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Context for grub_efidisk_get_device_name. */
|
|
struct grub_efidisk_get_device_name_ctx
|
|
{
|
|
char *partition_name;
|
|
grub_efi_hard_drive_device_path_t *hd;
|
|
};
|
|
|
|
/* Helper for grub_efidisk_get_device_name.
|
|
Find the identical partition. */
|
|
static int
|
|
grub_efidisk_get_device_name_iter (grub_disk_t disk,
|
|
const grub_partition_t part, void *data)
|
|
{
|
|
struct grub_efidisk_get_device_name_ctx *ctx = data;
|
|
|
|
if (grub_partition_get_start (part)
|
|
== (ctx->hd->partition_start << (disk->log_sector_size
|
|
- GRUB_DISK_SECTOR_BITS))
|
|
&& grub_partition_get_len (part)
|
|
== (ctx->hd->partition_size << (disk->log_sector_size
|
|
- GRUB_DISK_SECTOR_BITS)))
|
|
{
|
|
ctx->partition_name = grub_partition_get_name (part);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *
|
|
grub_efidisk_get_device_name (grub_efi_handle_t *handle)
|
|
{
|
|
grub_efi_device_path_t *dp, *ldp;
|
|
char device_name[NEEDED_BUFLEN];
|
|
|
|
dp = grub_efi_get_device_path (handle);
|
|
if (! dp)
|
|
return 0;
|
|
|
|
ldp = grub_efi_find_last_device_path (dp);
|
|
if (! ldp)
|
|
return 0;
|
|
|
|
if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
|
|
&& (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) == GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE
|
|
|| GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE))
|
|
{
|
|
struct grub_efidisk_get_device_name_ctx ctx;
|
|
char *dev_name;
|
|
grub_efi_device_path_t *dup_dp;
|
|
grub_disk_t parent = 0;
|
|
|
|
/* It is necessary to duplicate the device path so that GRUB
|
|
can overwrite it. */
|
|
dup_dp = grub_efi_duplicate_device_path (dp);
|
|
if (! dup_dp)
|
|
return 0;
|
|
|
|
while (1)
|
|
{
|
|
grub_efi_device_path_t *dup_ldp;
|
|
dup_ldp = grub_efi_find_last_device_path (dup_dp);
|
|
if (!(GRUB_EFI_DEVICE_PATH_TYPE (dup_ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
|
|
&& (GRUB_EFI_DEVICE_PATH_SUBTYPE (dup_ldp) == GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE
|
|
|| GRUB_EFI_DEVICE_PATH_SUBTYPE (dup_ldp) == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE)))
|
|
break;
|
|
|
|
dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
|
|
dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
|
|
dup_ldp->length = sizeof (*dup_ldp);
|
|
}
|
|
|
|
if (!get_diskname_from_path (dup_dp, device_name))
|
|
{
|
|
grub_free (dup_dp);
|
|
return 0;
|
|
}
|
|
|
|
parent = grub_disk_open (device_name);
|
|
grub_free (dup_dp);
|
|
|
|
if (! parent)
|
|
return 0;
|
|
|
|
/* Find a partition which matches the hard drive device path. */
|
|
ctx.partition_name = NULL;
|
|
ctx.hd = (grub_efi_hard_drive_device_path_t *) ldp;
|
|
if (ctx.hd->partition_start == 0
|
|
&& (ctx.hd->partition_size << (parent->log_sector_size
|
|
- GRUB_DISK_SECTOR_BITS))
|
|
== grub_disk_get_size (parent))
|
|
{
|
|
dev_name = grub_strdup (parent->name);
|
|
}
|
|
else
|
|
{
|
|
grub_partition_iterate (parent, grub_efidisk_get_device_name_iter,
|
|
&ctx);
|
|
|
|
if (! ctx.partition_name)
|
|
{
|
|
/* No partition found. In most cases partition is embed in
|
|
the root path anyway, so this is not critical.
|
|
This happens only if partition is on partmap that GRUB
|
|
doesn't need to access root.
|
|
*/
|
|
grub_disk_close (parent);
|
|
return grub_strdup (device_name);
|
|
}
|
|
|
|
dev_name = grub_xasprintf ("%s,%s", parent->name,
|
|
ctx.partition_name);
|
|
grub_free (ctx.partition_name);
|
|
}
|
|
grub_disk_close (parent);
|
|
|
|
return dev_name;
|
|
}
|
|
/* This may be guessed device - floppy, cdrom or entire disk. */
|
|
if (!get_diskname_from_path (dp, device_name))
|
|
return 0;
|
|
return grub_strdup (device_name);
|
|
}
|