diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:29:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:29:51 +0000 |
commit | 6e7a315eb67cb6c113cf37e1d66c4f11a51a2b3e (patch) | |
tree | 32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/disk/ieee1275 | |
parent | Initial commit. (diff) | |
download | grub2-upstream/2.06.tar.xz grub2-upstream/2.06.zip |
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | grub-core/disk/ieee1275/nand.c | 242 | ||||
-rw-r--r-- | grub-core/disk/ieee1275/obdisk.c | 1076 | ||||
-rw-r--r-- | grub-core/disk/ieee1275/ofdisk.c | 741 |
3 files changed, 2059 insertions, 0 deletions
diff --git a/grub-core/disk/ieee1275/nand.c b/grub-core/disk/ieee1275/nand.c new file mode 100644 index 0000000..bcf3a06 --- /dev/null +++ b/grub-core/disk/ieee1275/nand.c @@ -0,0 +1,242 @@ +/* nand.c - NAND flash disk access. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008,2009 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/misc.h> +#include <grub/disk.h> +#include <grub/mm.h> +#include <grub/dl.h> +#include <grub/ieee1275/ieee1275.h> +#include <grub/i18n.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +struct grub_nand_data +{ + grub_ieee1275_ihandle_t handle; + grub_uint32_t block_size; +}; + +static int +grub_nand_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, + grub_disk_pull_t pull) +{ + static int have_nand = -1; + + if (pull != GRUB_DISK_PULL_NONE) + return 0; + + if (have_nand == -1) + { + struct grub_ieee1275_devalias alias; + + have_nand = 0; + FOR_IEEE1275_DEVALIASES(alias) + if (grub_strcmp (alias.name, "nand") == 0) + { + have_nand = 1; + break; + } + grub_ieee1275_devalias_free (&alias); + } + + if (have_nand) + return hook ("nand", hook_data); + + return 0; +} + +static grub_err_t +grub_nand_read (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, char *buf); + +static grub_err_t +grub_nand_open (const char *name, grub_disk_t disk) +{ + grub_ieee1275_ihandle_t dev_ihandle = 0; + struct grub_nand_data *data = 0; + const char *devname; + struct size_args + { + struct grub_ieee1275_common_hdr common; + grub_ieee1275_cell_t method; + grub_ieee1275_cell_t ihandle; + grub_ieee1275_cell_t result; + grub_ieee1275_cell_t size1; + grub_ieee1275_cell_t size2; + } args; + + if (grub_memcmp (name, "nand/", sizeof ("nand/") - 1) == 0) + devname = name + sizeof ("nand/") - 1; + else if (grub_strcmp (name, "nand") == 0) + devname = name; + else + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a NAND device"); + + data = grub_malloc (sizeof (*data)); + if (! data) + goto fail; + + grub_ieee1275_open (devname, &dev_ihandle); + if (! dev_ihandle) + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device"); + goto fail; + } + + data->handle = dev_ihandle; + + INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 2); + args.method = (grub_ieee1275_cell_t) "block-size"; + args.ihandle = dev_ihandle; + args.result = 1; + + if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result)) + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't get block size"); + goto fail; + } + + data->block_size = (args.size1 >> GRUB_DISK_SECTOR_BITS); + if (!data->block_size) + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "invalid block size"); + goto fail; + } + + INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 3); + args.method = (grub_ieee1275_cell_t) "size"; + args.ihandle = dev_ihandle; + args.result = 1; + + if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result)) + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't get disk size"); + goto fail; + } + + disk->total_sectors = args.size1; + disk->total_sectors <<= 32; + disk->total_sectors += args.size2; + disk->total_sectors >>= GRUB_DISK_SECTOR_BITS; + + disk->id = dev_ihandle; + + disk->data = data; + + return 0; + +fail: + if (dev_ihandle) + grub_ieee1275_close (dev_ihandle); + grub_free (data); + return grub_errno; +} + +static void +grub_nand_close (grub_disk_t disk) +{ + grub_ieee1275_close (((struct grub_nand_data *) disk->data)->handle); + grub_free (disk->data); +} + +static grub_err_t +grub_nand_read (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, char *buf) +{ + struct grub_nand_data *data = disk->data; + grub_size_t bsize, ofs; + + struct read_args + { + struct grub_ieee1275_common_hdr common; + grub_ieee1275_cell_t method; + grub_ieee1275_cell_t ihandle; + grub_ieee1275_cell_t ofs; + grub_ieee1275_cell_t page; + grub_ieee1275_cell_t len; + grub_ieee1275_cell_t buf; + grub_ieee1275_cell_t result; + } args; + + INIT_IEEE1275_COMMON (&args.common, "call-method", 6, 1); + args.method = (grub_ieee1275_cell_t) "pio-read"; + args.ihandle = data->handle; + args.buf = (grub_ieee1275_cell_t) buf; + args.page = (grub_ieee1275_cell_t) ((grub_size_t) sector / data->block_size); + + ofs = ((grub_size_t) sector % data->block_size) << GRUB_DISK_SECTOR_BITS; + size <<= GRUB_DISK_SECTOR_BITS; + bsize = (data->block_size << GRUB_DISK_SECTOR_BITS); + + do + { + grub_size_t len; + + len = (ofs + size > bsize) ? (bsize - ofs) : size; + + args.len = (grub_ieee1275_cell_t) len; + args.ofs = (grub_ieee1275_cell_t) ofs; + args.result = 1; + + if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result)) + return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx " + "from `%s'"), + (unsigned long long) sector, + disk->name); + + ofs = 0; + size -= len; + args.buf += len; + args.page++; + } while (size); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_nand_write (grub_disk_t disk __attribute ((unused)), + grub_disk_addr_t sector __attribute ((unused)), + grub_size_t size __attribute ((unused)), + const char *buf __attribute ((unused))) +{ + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "nand write is not supported"); +} + +static struct grub_disk_dev grub_nand_dev = + { + .name = "nand", + .id = GRUB_DISK_DEVICE_NAND_ID, + .disk_iterate = grub_nand_iterate, + .disk_open = grub_nand_open, + .disk_close = grub_nand_close, + .disk_read = grub_nand_read, + .disk_write = grub_nand_write, + .next = 0 + }; + +GRUB_MOD_INIT(nand) +{ + grub_disk_dev_register (&grub_nand_dev); +} + +GRUB_MOD_FINI(nand) +{ + grub_disk_dev_unregister (&grub_nand_dev); +} diff --git a/grub-core/disk/ieee1275/obdisk.c b/grub-core/disk/ieee1275/obdisk.c new file mode 100644 index 0000000..ec413c3 --- /dev/null +++ b/grub-core/disk/ieee1275/obdisk.c @@ -0,0 +1,1076 @@ +/* obdisk.c - Open Boot disk access. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2019 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/env.h> +#include <grub/i18n.h> +#include <grub/kernel.h> +#include <grub/list.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/scsicmd.h> +#include <grub/time.h> +#include <grub/ieee1275/ieee1275.h> +#include <grub/ieee1275/obdisk.h> + +#define IEEE1275_DEV "ieee1275/" +#define IEEE1275_DISK_ALIAS "/disk@" + +struct disk_dev +{ + struct disk_dev *next; + struct disk_dev **prev; + char *name; + char *raw_name; + char *grub_devpath; + char *grub_alias_devpath; + grub_ieee1275_ihandle_t ihandle; + grub_uint32_t block_size; + grub_uint64_t num_blocks; + unsigned int log_sector_size; + grub_uint32_t opened; + grub_uint32_t valid; + grub_uint32_t boot_dev; +}; + +struct parent_dev +{ + struct parent_dev *next; + struct parent_dev **prev; + char *name; + char *type; + grub_ieee1275_ihandle_t ihandle; + grub_uint32_t address_cells; +}; + +static struct grub_scsi_test_unit_ready tur = +{ + .opcode = grub_scsi_cmd_test_unit_ready, + .lun = 0, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + .control = 0, +}; + +static int disks_enumerated; +static struct disk_dev *disk_devs; +static struct parent_dev *parent_devs; + +static const char *block_blacklist[] = { + /* Requires additional work in grub before being able to be used. */ + "/iscsi-hba", + /* This block device should never be used by grub. */ + "/reboot-memory@0", + 0 +}; + +#define STRCMP(a, b) ((a) && (b) && (grub_strcmp (a, b) == 0)) + +static char * +strip_ob_partition (char *path) +{ + char *sptr; + + sptr = grub_strstr (path, ":"); + + if (sptr != NULL) + *sptr = '\0'; + + return path; +} + +static void +escape_commas (const char *src, char *dest) +{ + const char *iptr; + + for (iptr = src; *iptr; ) + { + if (*iptr == ',') + *dest++ ='\\'; + + *dest++ = *iptr++; + } + + *dest = '\0'; +} + +static int +count_commas (const char *src) +{ + int count = 0; + + for ( ; *src; src++) + if (*src == ',') + count++; + + return count; +} + + +static char * +decode_grub_devname (const char *name) +{ + char *devpath = grub_malloc (grub_strlen (name) + 1); + char *p, c; + + if (devpath == NULL) + return NULL; + + /* Un-escape commas. */ + p = devpath; + while ((c = *name++) != '\0') + { + if (c == '\\' && *name == ',') + { + *p++ = ','; + name++; + } + else + *p++ = c; + } + + *p++ = '\0'; + + return devpath; +} + +static char * +encode_grub_devname (const char *path) +{ + char *encoding, *optr; + + if (path == NULL) + return NULL; + + encoding = grub_malloc (sizeof (IEEE1275_DEV) + count_commas (path) + + grub_strlen (path) + 1); + + if (encoding == NULL) + { + grub_print_error (); + return NULL; + } + + optr = grub_stpcpy (encoding, IEEE1275_DEV); + escape_commas (path, optr); + return encoding; +} + +static char * +get_parent_devname (const char *devname) +{ + char *parent, *pptr; + + parent = grub_strdup (devname); + + if (parent == NULL) + { + grub_print_error (); + return NULL; + } + + pptr = grub_strstr (parent, IEEE1275_DISK_ALIAS); + + if (pptr != NULL) + *pptr = '\0'; + + return parent; +} + +static void +free_parent_dev (struct parent_dev *parent) +{ + if (parent != NULL) + { + grub_free (parent->name); + grub_free (parent->type); + grub_free (parent); + } +} + +static struct parent_dev * +init_parent (const char *parent) +{ + struct parent_dev *op; + + op = grub_zalloc (sizeof (struct parent_dev)); + + if (op == NULL) + { + grub_print_error (); + return NULL; + } + + op->name = grub_strdup (parent); + op->type = grub_malloc (IEEE1275_MAX_PROP_LEN); + + if ((op->name == NULL) || (op->type == NULL)) + { + grub_print_error (); + free_parent_dev (op); + return NULL; + } + + return op; +} + +static struct parent_dev * +open_new_parent (const char *parent) +{ + struct parent_dev *op = init_parent(parent); + grub_ieee1275_ihandle_t ihandle; + grub_ieee1275_phandle_t phandle; + grub_uint32_t address_cells = 2; + + if (op == NULL) + return NULL; + + grub_ieee1275_open (parent, &ihandle); + + if (ihandle == 0) + { + grub_error (GRUB_ERR_BAD_DEVICE, "unable to open %s", parent); + grub_print_error (); + free_parent_dev (op); + return NULL; + } + + if (grub_ieee1275_instance_to_package (ihandle, &phandle)) + { + grub_error (GRUB_ERR_BAD_DEVICE, "unable to get parent %s", parent); + grub_print_error (); + free_parent_dev (op); + return NULL; + } + + /* + * IEEE Std 1275-1994 page 110: A missing "address-cells" property + * signifies that the number of address cells is two. So ignore on error. + */ + if (grub_ieee1275_get_integer_property (phandle, "#address-cells", + &address_cells, + sizeof (address_cells), 0) != 0) + address_cells = 2; + + grub_ieee1275_get_property (phandle, "device_type", op->type, + IEEE1275_MAX_PROP_LEN, NULL); + + op->ihandle = ihandle; + op->address_cells = address_cells; + return op; +} + +static struct parent_dev * +open_parent (const char *parent) +{ + struct parent_dev *op; + + op = grub_named_list_find (GRUB_AS_NAMED_LIST (parent_devs), parent); + + if (op == NULL) + { + op = open_new_parent (parent); + + if (op != NULL) + grub_list_push (GRUB_AS_LIST_P (&parent_devs), GRUB_AS_LIST (op)); + } + + return op; +} + +static void +display_parents (void) +{ + struct parent_dev *parent; + + grub_printf ("-------------------- PARENTS --------------------\n"); + + FOR_LIST_ELEMENTS (parent, parent_devs) + { + grub_printf ("name: %s\n", parent->name); + grub_printf ("type: %s\n", parent->type); + grub_printf ("address_cells %x\n", parent->address_cells); + } + + grub_printf ("-------------------------------------------------\n"); +} + +static char * +canonicalise_4cell_ua (grub_ieee1275_ihandle_t ihandle, char *unit_address) +{ + grub_uint32_t phy_lo, phy_hi, lun_lo, lun_hi; + int valid_phy = 0; + grub_size_t size; + char *canon = NULL; + + valid_phy = grub_ieee1275_decode_unit4 (ihandle, unit_address, + grub_strlen (unit_address), &phy_lo, + &phy_hi, &lun_lo, &lun_hi); + + if ((valid_phy == 0) && (phy_hi != 0xffffffff)) + canon = grub_ieee1275_encode_uint4 (ihandle, phy_lo, phy_hi, + lun_lo, lun_hi, &size); + + return canon; +} + +static char * +canonicalise_disk (const char *devname) +{ + char *canon, *parent; + struct parent_dev *op; + + canon = grub_ieee1275_canonicalise_devname (devname); + + if (canon == NULL) + { + /* This should not happen. */ + grub_error (GRUB_ERR_BAD_DEVICE, "canonicalise devname failed"); + grub_print_error (); + return NULL; + } + + /* Don't try to open the parent of a virtual device. */ + if (grub_strstr (canon, "virtual-devices")) + return canon; + + parent = get_parent_devname (canon); + + if (parent == NULL) + return NULL; + + op = open_parent (parent); + + /* + * Devices with 4 address cells can have many different types of addressing + * (phy, wwn, and target lun). Use the parents encode-unit / decode-unit + * to find the true canonical name. + */ + if ((op) && (op->address_cells == 4)) + { + char *unit_address, *real_unit_address, *real_canon; + grub_size_t real_unit_str_len; + + unit_address = grub_strstr (canon, IEEE1275_DISK_ALIAS); + unit_address += grub_strlen (IEEE1275_DISK_ALIAS); + + if (unit_address == NULL) + { + /* + * This should not be possible, but return the canonical name for + * the non-disk block device. + */ + grub_free (parent); + return (canon); + } + + real_unit_address = canonicalise_4cell_ua (op->ihandle, unit_address); + + if (real_unit_address == NULL) + { + /* + * This is not an error, since this function could be called with a devalias + * containing a drive that isn't installed in the system. + */ + grub_free (parent); + return NULL; + } + + real_unit_str_len = grub_strlen (op->name) + sizeof (IEEE1275_DISK_ALIAS) + + grub_strlen (real_unit_address); + + real_canon = grub_malloc (real_unit_str_len); + + grub_snprintf (real_canon, real_unit_str_len, "%s/disk@%s", + op->name, real_unit_address); + + grub_free (canon); + canon = real_canon; + } + + grub_free (parent); + return (canon); +} + +static struct disk_dev * +add_canon_disk (const char *cname) +{ + struct disk_dev *dev; + + dev = grub_zalloc (sizeof (struct disk_dev)); + + if (dev == NULL) + goto failed; + + if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_RAW_DEVNAMES)) + { + /* + * Append :nolabel to the end of all SPARC disks. + * nolabel is mutually exclusive with all other + * arguments and allows a client program to open + * the entire (raw) disk. Any disk label is ignored. + */ + dev->raw_name = grub_malloc (grub_strlen (cname) + sizeof (":nolabel")); + + if (dev->raw_name == NULL) + goto failed; + + grub_snprintf (dev->raw_name, grub_strlen (cname) + sizeof (":nolabel"), + "%s:nolabel", cname); + } + + /* + * Don't use grub_ieee1275_encode_devname here, the devpath in grub.cfg doesn't + * understand device aliases, which the layer above sometimes sends us. + */ + dev->grub_devpath = encode_grub_devname(cname); + + if (dev->grub_devpath == NULL) + goto failed; + + dev->name = grub_strdup (cname); + + if (dev->name == NULL) + goto failed; + + dev->valid = 1; + grub_list_push (GRUB_AS_LIST_P (&disk_devs), GRUB_AS_LIST (dev)); + return dev; + + failed: + grub_print_error (); + + if (dev != NULL) + { + grub_free (dev->name); + grub_free (dev->grub_devpath); + grub_free (dev->raw_name); + } + + grub_free (dev); + return NULL; +} + +static grub_err_t +add_disk (const char *path) +{ + grub_err_t ret = GRUB_ERR_NONE; + struct disk_dev *dev; + char *canon; + + canon = canonicalise_disk (path); + dev = grub_named_list_find (GRUB_AS_NAMED_LIST (disk_devs), canon); + + if ((canon != NULL) && (dev == NULL)) + { + struct disk_dev *ob_device; + + ob_device = add_canon_disk (canon); + + if (ob_device == NULL) + ret = grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure to add disk"); + } + else if (dev != NULL) + dev->valid = 1; + + grub_free (canon); + return (ret); +} + +static grub_err_t +grub_obdisk_read (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, char *dest) +{ + grub_err_t ret = GRUB_ERR_NONE; + struct disk_dev *dev; + unsigned long long pos; + grub_ssize_t result = 0; + + if (disk->data == NULL) + return grub_error (GRUB_ERR_BAD_DEVICE, "invalid disk data"); + + dev = (struct disk_dev *)disk->data; + pos = sector << disk->log_sector_size; + grub_ieee1275_seek (dev->ihandle, pos, &result); + + if (result < 0) + { + dev->opened = 0; + return grub_error (GRUB_ERR_READ_ERROR, "seek error, can't seek block %llu", + (long long) sector); + } + + grub_ieee1275_read (dev->ihandle, dest, size << disk->log_sector_size, + &result); + + if (result != (grub_ssize_t) (size << disk->log_sector_size)) + { + dev->opened = 0; + return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx " + "from `%s'"), + (unsigned long long) sector, + disk->name); + } + return ret; +} + +static void +grub_obdisk_close (grub_disk_t disk) +{ + grub_memset (disk, 0, sizeof (*disk)); +} + +static void +scan_usb_disk (const char *parent) +{ + struct parent_dev *op; + grub_ssize_t result; + + op = open_parent (parent); + + if (op == NULL) + { + grub_error (GRUB_ERR_BAD_DEVICE, "unable to open %s", parent); + grub_print_error (); + return; + } + + if ((grub_ieee1275_set_address (op->ihandle, 0, 0) == 0) && + (grub_ieee1275_no_data_command (op->ihandle, &tur, &result) == 0) && + (result == 0)) + { + char *buf; + + buf = grub_malloc (IEEE1275_MAX_PATH_LEN); + + if (buf == NULL) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure"); + grub_print_error (); + return; + } + + grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@0", parent); + add_disk (buf); + grub_free (buf); + } +} + +static void +scan_nvme_disk (const char *path) +{ + char *buf; + + buf = grub_malloc (IEEE1275_MAX_PATH_LEN); + + if (buf == NULL) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure"); + grub_print_error (); + return; + } + + grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@1", path); + add_disk (buf); + grub_free (buf); +} + +static void +scan_sparc_sas_2cell (struct parent_dev *op) +{ + grub_ssize_t result; + grub_uint8_t tgt; + char *buf; + + buf = grub_malloc (IEEE1275_MAX_PATH_LEN); + + if (buf == NULL) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure"); + grub_print_error (); + return; + } + + for (tgt = 0; tgt < 0xf; tgt++) + { + + if ((grub_ieee1275_set_address(op->ihandle, tgt, 0) == 0) && + (grub_ieee1275_no_data_command (op->ihandle, &tur, &result) == 0) && + (result == 0)) + { + + grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@%" + PRIxGRUB_UINT32_T, op->name, tgt); + + add_disk (buf); + } + } +} + +static void +scan_sparc_sas_4cell (struct parent_dev *op) +{ + grub_uint16_t exp; + grub_uint8_t phy; + char *buf; + + buf = grub_malloc (IEEE1275_MAX_PATH_LEN); + + if (buf == NULL) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure"); + grub_print_error (); + return; + } + + /* + * Cycle thru the potential for dual ported SAS disks + * behind a SAS expander. + */ + for (exp = 0; exp <= 0x100; exp+=0x100) + + /* The current limit is 32 disks on a phy. */ + for (phy = 0; phy < 0x20; phy++) + { + char *canon = NULL; + + grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "p%" PRIxGRUB_UINT32_T ",0", + exp | phy); + + canon = canonicalise_4cell_ua (op->ihandle, buf); + + if (canon != NULL) + { + grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@%s", + op->name, canon); + + add_disk (buf); + grub_free (canon); + } + } + + grub_free (buf); +} + +static void +scan_sparc_sas_disk (const char *parent) +{ + struct parent_dev *op; + + op = open_parent (parent); + + if ((op != NULL) && (op->address_cells == 4)) + scan_sparc_sas_4cell (op); + else if ((op != NULL) && (op->address_cells == 2)) + scan_sparc_sas_2cell (op); +} + +static void +iterate_devtree (const struct grub_ieee1275_devalias *alias) +{ + struct grub_ieee1275_devalias child; + + if ((grub_strcmp (alias->type, "scsi-2") == 0) || + (grub_strcmp (alias->type, "scsi-sas") == 0)) + return scan_sparc_sas_disk (alias->path); + + else if (grub_strcmp (alias->type, "nvme") == 0) + return scan_nvme_disk (alias->path); + + else if (grub_strcmp (alias->type, "scsi-usb") == 0) + return scan_usb_disk (alias->path); + + else if (grub_strcmp (alias->type, "block") == 0) + { + const char **bl = block_blacklist; + + while (*bl != NULL) + { + if (grub_strstr (alias->path, *bl)) + return; + bl++; + } + + add_disk (alias->path); + return; + } + + FOR_IEEE1275_DEVCHILDREN (alias->path, child) + iterate_devtree (&child); +} + +static void +enumerate_disks (void) +{ + struct grub_ieee1275_devalias alias; + + FOR_IEEE1275_DEVCHILDREN("/", alias) + iterate_devtree (&alias); +} + +static grub_err_t +add_bootpath (void) +{ + struct disk_dev *ob_device; + grub_err_t ret = GRUB_ERR_NONE; + char *dev, *alias; + char *type; + + dev = grub_ieee1275_get_boot_dev (); + + if (dev == NULL) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure adding boot device"); + + type = grub_ieee1275_get_device_type (dev); + + if (type == NULL) + { + grub_free (dev); + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure adding boot device"); + } + + alias = NULL; + + if (grub_strcmp (type, "network") != 0) + { + dev = strip_ob_partition (dev); + ob_device = add_canon_disk (dev); + + if (ob_device == NULL) + ret = grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure adding boot device"); + + ob_device->valid = 1; + + alias = grub_ieee1275_get_devname (dev); + + if (alias && grub_strcmp (alias, dev) != 0) + ob_device->grub_alias_devpath = grub_ieee1275_encode_devname (dev); + + ob_device->boot_dev = 1; + } + + grub_free (type); + grub_free (dev); + grub_free (alias); + return ret; +} + +static void +enumerate_aliases (void) +{ + struct grub_ieee1275_devalias alias; + + /* + * Some block device aliases are not in canonical form + * + * For example: + * + * disk3 /pci@301/pci@1/scsi@0/disk@p3 + * disk2 /pci@301/pci@1/scsi@0/disk@p2 + * disk1 /pci@301/pci@1/scsi@0/disk@p1 + * disk /pci@301/pci@1/scsi@0/disk@p0 + * disk0 /pci@301/pci@1/scsi@0/disk@p0 + * + * None of these devices are in canonical form. + * + * Also, just because there is a devalias, doesn't mean there is a disk + * at that location. And a valid boot block device doesn't have to have + * a devalias at all. + * + * At this point, all valid disks have been found in the system + * and devaliases that point to canonical names are stored in the + * disk_devs list already. + */ + FOR_IEEE1275_DEVALIASES (alias) + { + struct disk_dev *dev; + char *canon; + + if (grub_strcmp (alias.type, "block") != 0) + continue; + + canon = canonicalise_disk (alias.name); + + if (canon == NULL) + /* This is not an error, a devalias could point to a nonexistent disk. */ + continue; + + dev = grub_named_list_find (GRUB_AS_NAMED_LIST (disk_devs), canon); + + if (dev != NULL) + { + /* + * If more than one alias points to the same device, + * remove the previous one unless it is the boot dev, + * since the upper level will use the first one. The reason + * all the others are redone is in the case of hot-plugging + * a disk. If the boot disk gets hot-plugged, it will come + * thru here with a different name without the boot_dev flag + * set. + */ + if ((dev->boot_dev) && (dev->grub_alias_devpath)) + continue; + + grub_free (dev->grub_alias_devpath); + dev->grub_alias_devpath = grub_ieee1275_encode_devname (alias.path); + } + grub_free (canon); + } +} + +static void +display_disks (void) +{ + struct disk_dev *dev; + + grub_printf ("--------------------- DISKS ---------------------\n"); + + FOR_LIST_ELEMENTS (dev, disk_devs) + { + grub_printf ("name: %s\n", dev->name); + grub_printf ("grub_devpath: %s\n", dev->grub_devpath); + grub_printf ("grub_alias_devpath: %s\n", dev->grub_alias_devpath); + grub_printf ("valid: %s\n", (dev->valid) ? "yes" : "no"); + grub_printf ("boot_dev: %s\n", (dev->boot_dev) ? "yes" : "no"); + grub_printf ("opened: %s\n", (dev->ihandle) ? "yes" : "no"); + grub_printf ("block size: %" PRIuGRUB_UINT32_T "\n", + dev->block_size); + grub_printf ("num blocks: %" PRIuGRUB_UINT64_T "\n", + dev->num_blocks); + grub_printf ("log sector size: %" PRIuGRUB_UINT32_T "\n", + dev->log_sector_size); + grub_printf ("\n"); + } + + grub_printf ("-------------------------------------------------\n"); +} + +static void +display_stats (void) +{ + const char *debug = grub_env_get ("debug"); + + if (debug == NULL) + return; + + if (grub_strword (debug, "all") || grub_strword (debug, "obdisk")) + { + display_parents (); + display_disks (); + } +} + +static void +invalidate_all_disks (void) +{ + struct disk_dev *dev = NULL; + + if (disks_enumerated != 0) + FOR_LIST_ELEMENTS (dev, disk_devs) + dev->valid = 0; +} + +static struct disk_dev * +find_legacy_grub_devpath (const char *name) +{ + struct disk_dev *dev = NULL; + char *canon, *devpath = NULL; + + devpath = decode_grub_devname (name + sizeof ("ieee1275")); + canon = canonicalise_disk (devpath); + + if (canon != NULL) + dev = grub_named_list_find (GRUB_AS_NAMED_LIST (disk_devs), canon); + + grub_free (devpath); + grub_free (canon); + return dev; +} + +static void +enumerate_devices (void) +{ + invalidate_all_disks (); + enumerate_disks (); + enumerate_aliases (); + disks_enumerated = 1; + display_stats (); +} + +static struct disk_dev * +find_grub_devpath_real (const char *name) +{ + struct disk_dev *dev = NULL; + + FOR_LIST_ELEMENTS (dev, disk_devs) + { + if ((STRCMP (dev->grub_devpath, name)) + || (STRCMP (dev->grub_alias_devpath, name))) + break; + } + + return dev; +} + +static struct disk_dev * +find_grub_devpath (const char *name) +{ + struct disk_dev *dev = NULL; + int enumerated; + + do { + enumerated = disks_enumerated; + dev = find_grub_devpath_real (name); + + if (dev != NULL) + break; + + dev = find_legacy_grub_devpath (name); + + if (dev != NULL) + break; + + enumerate_devices (); + } while (enumerated == 0); + + return dev; +} + +static int +grub_obdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, + grub_disk_pull_t pull) +{ + struct disk_dev *dev; + const char *name; + + if (pull != GRUB_DISK_PULL_NONE) + return 0; + + enumerate_devices (); + + FOR_LIST_ELEMENTS (dev, disk_devs) + { + if (dev->valid == 1) + { + if (dev->grub_alias_devpath) + name = dev->grub_alias_devpath; + else + name = dev->grub_devpath; + + if (hook (name, hook_data)) + return 1; + } + } + + return 0; +} + +static grub_err_t +grub_obdisk_open (const char *name, grub_disk_t disk) +{ + grub_ieee1275_ihandle_t ihandle = 0; + struct disk_dev *dev = NULL; + + if (grub_strncmp (name, IEEE1275_DEV, sizeof (IEEE1275_DEV) - 1) != 0) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not IEEE1275 device"); + + dev = find_grub_devpath (name); + + if (dev == NULL) + { + grub_printf ("UNKNOWN DEVICE: %s\n", name); + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "%s", name); + } + + if (dev->opened == 0) + { + if (dev->raw_name != NULL) + grub_ieee1275_open (dev->raw_name, &ihandle); + else + grub_ieee1275_open (dev->name, &ihandle); + + if (ihandle == 0) + { + grub_printf ("Can't open device %s\n", name); + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device %s", name); + } + + dev->block_size = grub_ieee1275_get_block_size (ihandle); + dev->num_blocks = grub_ieee1275_num_blocks (ihandle); + + if (dev->num_blocks == 0) + dev->num_blocks = grub_ieee1275_num_blocks64 (ihandle); + + if (dev->num_blocks == 0) + dev->num_blocks = GRUB_DISK_SIZE_UNKNOWN; + + if (dev->block_size != 0) + { + for (dev->log_sector_size = 0; + (1U << dev->log_sector_size) < dev->block_size; + dev->log_sector_size++); + } + else + dev->log_sector_size = 9; + + dev->ihandle = ihandle; + dev->opened = 1; + } + + disk->total_sectors = dev->num_blocks; + disk->id = dev->ihandle; + disk->data = dev; + disk->log_sector_size = dev->log_sector_size; + return GRUB_ERR_NONE; +} + + +static struct grub_disk_dev grub_obdisk_dev = + { + .name = "obdisk", + .id = GRUB_DISK_DEVICE_OBDISK_ID, + .disk_iterate = grub_obdisk_iterate, + .disk_open = grub_obdisk_open, + .disk_close = grub_obdisk_close, + .disk_read = grub_obdisk_read, + }; + +void +grub_obdisk_init (void) +{ + grub_disk_firmware_fini = grub_obdisk_fini; + add_bootpath (); + grub_disk_dev_register (&grub_obdisk_dev); +} + +void +grub_obdisk_fini (void) +{ + struct disk_dev *dev; + + FOR_LIST_ELEMENTS (dev, disk_devs) + { + if (dev->opened != 0) + grub_ieee1275_close (dev->ihandle); + } + + grub_disk_dev_unregister (&grub_obdisk_dev); +} diff --git a/grub-core/disk/ieee1275/ofdisk.c b/grub-core/disk/ieee1275/ofdisk.c new file mode 100644 index 0000000..03674cb --- /dev/null +++ b/grub-core/disk/ieee1275/ofdisk.c @@ -0,0 +1,741 @@ +/* ofdisk.c - Open Firmware disk access. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2004,2006,2007,2008,2009 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/misc.h> +#include <grub/disk.h> +#include <grub/mm.h> +#include <grub/ieee1275/ieee1275.h> +#include <grub/ieee1275/ofdisk.h> +#include <grub/i18n.h> +#include <grub/time.h> + +static char *last_devpath; +static grub_ieee1275_ihandle_t last_ihandle; + +struct ofdisk_hash_ent +{ + char *devpath; + char *open_path; + char *grub_devpath; + int is_boot; + int is_removable; + int block_size_fails; + /* Pointer to shortest available name on nodes representing canonical names, + otherwise NULL. */ + const char *shortest; + const char *grub_shortest; + struct ofdisk_hash_ent *next; +}; + +static grub_err_t +grub_ofdisk_get_block_size (const char *device, grub_uint32_t *block_size, + struct ofdisk_hash_ent *op); + +#define OFDISK_HASH_SZ 8 +static struct ofdisk_hash_ent *ofdisk_hash[OFDISK_HASH_SZ]; + +static int +ofdisk_hash_fn (const char *devpath) +{ + int hash = 0; + while (*devpath) + hash ^= *devpath++; + return (hash & (OFDISK_HASH_SZ - 1)); +} + +static struct ofdisk_hash_ent * +ofdisk_hash_find (const char *devpath) +{ + struct ofdisk_hash_ent *p = ofdisk_hash[ofdisk_hash_fn(devpath)]; + + while (p) + { + if (!grub_strcmp (p->devpath, devpath)) + break; + p = p->next; + } + return p; +} + +static struct ofdisk_hash_ent * +ofdisk_hash_add_real (char *devpath) +{ + struct ofdisk_hash_ent *p; + struct ofdisk_hash_ent **head = &ofdisk_hash[ofdisk_hash_fn(devpath)]; + const char *iptr; + char *optr; + + p = grub_zalloc (sizeof (*p)); + if (!p) + return NULL; + + p->devpath = devpath; + + p->grub_devpath = grub_malloc (sizeof ("ieee1275/") + + 2 * grub_strlen (p->devpath)); + + if (!p->grub_devpath) + { + grub_free (p); + return NULL; + } + + if (! grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_PARTITION_0)) + { + p->open_path = grub_malloc (grub_strlen (p->devpath) + 3); + if (!p->open_path) + { + grub_free (p->grub_devpath); + grub_free (p); + return NULL; + } + optr = grub_stpcpy (p->open_path, p->devpath); + *optr++ = ':'; + *optr++ = '0'; + *optr = '\0'; + } + else + p->open_path = p->devpath; + + optr = grub_stpcpy (p->grub_devpath, "ieee1275/"); + for (iptr = p->devpath; *iptr; ) + { + if (*iptr == ',') + *optr++ = '\\'; + *optr++ = *iptr++; + } + *optr = 0; + + p->next = *head; + *head = p; + return p; +} + +static int +check_string_removable (const char *str) +{ + const char *ptr = grub_strrchr (str, '/'); + + if (ptr) + ptr++; + else + ptr = str; + return (grub_strncmp (ptr, "cdrom", 5) == 0 || grub_strncmp (ptr, "fd", 2) == 0); +} + +static struct ofdisk_hash_ent * +ofdisk_hash_add (char *devpath, char *curcan) +{ + struct ofdisk_hash_ent *p, *pcan; + + p = ofdisk_hash_add_real (devpath); + + grub_dprintf ("disk", "devpath = %s, canonical = %s\n", devpath, curcan); + + if (!curcan) + { + p->shortest = p->devpath; + p->grub_shortest = p->grub_devpath; + if (check_string_removable (devpath)) + p->is_removable = 1; + return p; + } + + pcan = ofdisk_hash_find (curcan); + if (!pcan) + pcan = ofdisk_hash_add_real (curcan); + else + grub_free (curcan); + + if (check_string_removable (devpath) || check_string_removable (curcan)) + pcan->is_removable = 1; + + if (!pcan) + grub_errno = GRUB_ERR_NONE; + else + { + if (!pcan->shortest + || grub_strlen (pcan->shortest) > grub_strlen (devpath)) + { + pcan->shortest = p->devpath; + pcan->grub_shortest = p->grub_devpath; + } + } + + return p; +} + +static void +dev_iterate_real (const char *name, const char *path) +{ + struct ofdisk_hash_ent *op; + + grub_dprintf ("disk", "disk name = %s, path = %s\n", name, + path); + + op = ofdisk_hash_find (path); + if (!op) + { + char *name_dup = grub_strdup (name); + char *can = grub_strdup (path); + if (!name_dup || !can) + { + grub_errno = GRUB_ERR_NONE; + grub_free (name_dup); + grub_free (can); + return; + } + op = ofdisk_hash_add (name_dup, can); + } + return; +} + +static void +dev_iterate (const struct grub_ieee1275_devalias *alias) +{ + if (grub_strcmp (alias->type, "vscsi") == 0) + { + static grub_ieee1275_ihandle_t ihandle; + struct set_color_args + { + struct grub_ieee1275_common_hdr common; + grub_ieee1275_cell_t method; + grub_ieee1275_cell_t ihandle; + grub_ieee1275_cell_t catch_result; + grub_ieee1275_cell_t nentries; + grub_ieee1275_cell_t table; + } + args; + char *buf, *bufptr; + unsigned i; + + if (grub_ieee1275_open (alias->path, &ihandle)) + return; + + /* This method doesn't need memory allocation for the table. Open + firmware takes care of all memory management and the result table + stays in memory and is never freed. */ + INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 3); + args.method = (grub_ieee1275_cell_t) "vscsi-report-luns"; + args.ihandle = ihandle; + args.table = 0; + args.nentries = 0; + + if (IEEE1275_CALL_ENTRY_FN (&args) == -1 || args.catch_result) + { + grub_ieee1275_close (ihandle); + return; + } + + buf = grub_malloc (grub_strlen (alias->path) + 32); + if (!buf) + return; + bufptr = grub_stpcpy (buf, alias->path); + + for (i = 0; i < args.nentries; i++) + { + grub_uint64_t *ptr; + + ptr = *(grub_uint64_t **) (args.table + 4 + 8 * i); + while (*ptr) + { + grub_snprintf (bufptr, 32, "/disk@%" PRIxGRUB_UINT64_T, *ptr++); + dev_iterate_real (buf, buf); + } + } + grub_ieee1275_close (ihandle); + grub_free (buf); + return; + } + else if (grub_strcmp (alias->type, "sas_ioa") == 0) + { + /* The method returns the number of disks and a table where + * each ID is 64-bit long. Example of sas paths: + * /pci@80000002000001f/pci1014,034A@0/sas/disk@c05db70800 + * /pci@80000002000001f/pci1014,034A@0/sas/disk@a05db70800 + * /pci@80000002000001f/pci1014,034A@0/sas/disk@805db70800 */ + + struct sas_children + { + struct grub_ieee1275_common_hdr common; + grub_ieee1275_cell_t method; + grub_ieee1275_cell_t ihandle; + grub_ieee1275_cell_t max; + grub_ieee1275_cell_t table; + grub_ieee1275_cell_t catch_result; + grub_ieee1275_cell_t nentries; + } + args; + char *buf, *bufptr; + unsigned i; + grub_uint64_t *table; + grub_uint16_t table_size; + grub_ieee1275_ihandle_t ihandle; + + buf = grub_malloc (grub_strlen (alias->path) + + sizeof ("/disk@7766554433221100")); + if (!buf) + return; + bufptr = grub_stpcpy (buf, alias->path); + + /* Power machines documentation specify 672 as maximum SAS disks in + one system. Using a slightly larger value to be safe. */ + table_size = 768; + table = grub_calloc (table_size, sizeof (grub_uint64_t)); + + if (!table) + { + grub_free (buf); + return; + } + + if (grub_ieee1275_open (alias->path, &ihandle)) + { + grub_free (buf); + grub_free (table); + return; + } + + INIT_IEEE1275_COMMON (&args.common, "call-method", 4, 2); + args.method = (grub_ieee1275_cell_t) "get-sas-children"; + args.ihandle = ihandle; + args.max = table_size; + args.table = (grub_ieee1275_cell_t) table; + args.catch_result = 0; + args.nentries = 0; + + if (IEEE1275_CALL_ENTRY_FN (&args) == -1) + { + grub_ieee1275_close (ihandle); + grub_free (table); + grub_free (buf); + return; + } + + for (i = 0; i < args.nentries; i++) + { + grub_snprintf (bufptr, sizeof ("/disk@7766554433221100"), + "/disk@%" PRIxGRUB_UINT64_T, table[i]); + dev_iterate_real (buf, buf); + } + + grub_ieee1275_close (ihandle); + grub_free (table); + grub_free (buf); + } + + if (!grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_TREE_SCANNING_FOR_DISKS) + && grub_strcmp (alias->type, "block") == 0) + { + dev_iterate_real (alias->path, alias->path); + return; + } + + { + struct grub_ieee1275_devalias child; + + FOR_IEEE1275_DEVCHILDREN(alias->path, child) + dev_iterate (&child); + } +} + +static void +scan (void) +{ + struct grub_ieee1275_devalias alias; + FOR_IEEE1275_DEVALIASES(alias) + { + if (grub_strcmp (alias.type, "block") != 0) + continue; + dev_iterate_real (alias.name, alias.path); + } + + FOR_IEEE1275_DEVCHILDREN("/", alias) + dev_iterate (&alias); +} + +static int +grub_ofdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, + grub_disk_pull_t pull) +{ + unsigned i; + + if (pull != GRUB_DISK_PULL_NONE) + return 0; + + scan (); + + for (i = 0; i < ARRAY_SIZE (ofdisk_hash); i++) + { + static struct ofdisk_hash_ent *ent; + for (ent = ofdisk_hash[i]; ent; ent = ent->next) + { + if (!ent->shortest) + continue; + if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_OFDISK_SDCARD_ONLY)) + { + grub_ieee1275_phandle_t dev; + char tmp[8]; + + if (grub_ieee1275_finddevice (ent->devpath, &dev)) + { + grub_dprintf ("disk", "finddevice (%s) failed\n", + ent->devpath); + continue; + } + + if (grub_ieee1275_get_property (dev, "iconname", tmp, + sizeof tmp, 0)) + { + grub_dprintf ("disk", "get iconname failed\n"); + continue; + } + + if (grub_strcmp (tmp, "sdmmc") != 0) + { + grub_dprintf ("disk", "device is not an SD card\n"); + continue; + } + } + + if (!ent->is_boot && ent->is_removable) + continue; + + if (hook (ent->grub_shortest, hook_data)) + return 1; + } + } + return 0; +} + +static char * +compute_dev_path (const char *name) +{ + char *devpath = grub_malloc (grub_strlen (name) + 3); + char *p, c; + + if (!devpath) + return NULL; + + /* Un-escape commas. */ + p = devpath; + while ((c = *name++) != '\0') + { + if (c == '\\' && *name == ',') + { + *p++ = ','; + name++; + } + else + *p++ = c; + } + + *p++ = '\0'; + + return devpath; +} + +static grub_err_t +grub_ofdisk_open (const char *name, grub_disk_t disk) +{ + grub_ieee1275_phandle_t dev; + char *devpath; + /* XXX: This should be large enough for any possible case. */ + char prop[64]; + grub_ssize_t actual; + grub_uint32_t block_size = 0; + grub_err_t err; + + if (grub_strncmp (name, "ieee1275/", sizeof ("ieee1275/") - 1) != 0) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, + "not IEEE1275 device"); + devpath = compute_dev_path (name + sizeof ("ieee1275/") - 1); + if (! devpath) + return grub_errno; + + grub_dprintf ("disk", "Opening `%s'.\n", devpath); + + if (grub_ieee1275_finddevice (devpath, &dev)) + { + grub_free (devpath); + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, + "can't read device properties"); + } + + if (grub_ieee1275_get_property (dev, "device_type", prop, sizeof (prop), + &actual)) + { + grub_free (devpath); + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't read the device type"); + } + + if (grub_strcmp (prop, "block")) + { + grub_free (devpath); + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a block device"); + } + + /* XXX: There is no property to read the number of blocks. There + should be a property `#blocks', but it is not there. Perhaps it + is possible to use seek for this. */ + disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN; + + { + struct ofdisk_hash_ent *op; + op = ofdisk_hash_find (devpath); + if (!op) + op = ofdisk_hash_add (devpath, NULL); + if (!op) + { + grub_free (devpath); + return grub_errno; + } + disk->id = (unsigned long) op; + disk->data = op->open_path; + + err = grub_ofdisk_get_block_size (devpath, &block_size, op); + if (err) + { + grub_free (devpath); + return err; + } + if (block_size != 0) + { + for (disk->log_sector_size = 0; + (1U << disk->log_sector_size) < block_size; + disk->log_sector_size++); + } + else + disk->log_sector_size = 9; + } + + grub_free (devpath); + return 0; +} + +static void +grub_ofdisk_close (grub_disk_t disk) +{ + if (disk->data == last_devpath) + { + if (last_ihandle) + grub_ieee1275_close (last_ihandle); + last_ihandle = 0; + last_devpath = NULL; + } + disk->data = 0; +} + +static grub_err_t +grub_ofdisk_prepare (grub_disk_t disk, grub_disk_addr_t sector) +{ + grub_ssize_t status; + unsigned long long pos; + + if (disk->data != last_devpath) + { + if (last_ihandle) + grub_ieee1275_close (last_ihandle); + last_ihandle = 0; + last_devpath = NULL; + + grub_ieee1275_open (disk->data, &last_ihandle); + if (! last_ihandle) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device"); + last_devpath = disk->data; + } + + pos = sector << disk->log_sector_size; + + grub_ieee1275_seek (last_ihandle, pos, &status); + if (status < 0) + return grub_error (GRUB_ERR_READ_ERROR, + "seek error, can't seek block %llu", + (long long) sector); + return 0; +} + +static grub_err_t +grub_ofdisk_read (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, char *buf) +{ + grub_err_t err; + grub_ssize_t actual; + err = grub_ofdisk_prepare (disk, sector); + if (err) + return err; + grub_ieee1275_read (last_ihandle, buf, size << disk->log_sector_size, + &actual); + if (actual != (grub_ssize_t) (size << disk->log_sector_size)) + return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx " + "from `%s'"), + (unsigned long long) sector, + disk->name); + + return 0; +} + +static grub_err_t +grub_ofdisk_write (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, const char *buf) +{ + grub_err_t err; + grub_ssize_t actual; + err = grub_ofdisk_prepare (disk, sector); + if (err) + return err; + grub_ieee1275_write (last_ihandle, buf, size << disk->log_sector_size, + &actual); + if (actual != (grub_ssize_t) (size << disk->log_sector_size)) + return grub_error (GRUB_ERR_WRITE_ERROR, N_("failure writing sector 0x%llx " + "to `%s'"), + (unsigned long long) sector, + disk->name); + + return 0; +} + +static struct grub_disk_dev grub_ofdisk_dev = + { + .name = "ofdisk", + .id = GRUB_DISK_DEVICE_OFDISK_ID, + .disk_iterate = grub_ofdisk_iterate, + .disk_open = grub_ofdisk_open, + .disk_close = grub_ofdisk_close, + .disk_read = grub_ofdisk_read, + .disk_write = grub_ofdisk_write, + .next = 0 + }; + +static void +insert_bootpath (void) +{ + char *bootpath; + grub_ssize_t bootpath_size; + char *type; + + if (grub_ieee1275_get_property_length (grub_ieee1275_chosen, "bootpath", + &bootpath_size) + || bootpath_size <= 0) + { + /* Should never happen. */ + grub_printf ("/chosen/bootpath property missing!\n"); + return; + } + + bootpath = (char *) grub_malloc ((grub_size_t) bootpath_size + 64); + if (! bootpath) + { + grub_print_error (); + return; + } + grub_ieee1275_get_property (grub_ieee1275_chosen, "bootpath", bootpath, + (grub_size_t) bootpath_size + 1, 0); + bootpath[bootpath_size] = '\0'; + + /* Transform an OF device path to a GRUB path. */ + + type = grub_ieee1275_get_device_type (bootpath); + if (!(type && grub_strcmp (type, "network") == 0)) + { + struct ofdisk_hash_ent *op; + char *device = grub_ieee1275_get_devname (bootpath); + op = ofdisk_hash_add (device, NULL); + op->is_boot = 1; + } + grub_free (type); + grub_free (bootpath); +} + +void +grub_ofdisk_fini (void) +{ + if (last_ihandle) + grub_ieee1275_close (last_ihandle); + last_ihandle = 0; + last_devpath = NULL; + + grub_disk_dev_unregister (&grub_ofdisk_dev); +} + +void +grub_ofdisk_init (void) +{ + grub_disk_firmware_fini = grub_ofdisk_fini; + + insert_bootpath (); + + grub_disk_dev_register (&grub_ofdisk_dev); +} + +static grub_err_t +grub_ofdisk_get_block_size (const char *device, grub_uint32_t *block_size, + struct ofdisk_hash_ent *op) +{ + struct size_args_ieee1275 + { + struct grub_ieee1275_common_hdr common; + grub_ieee1275_cell_t method; + grub_ieee1275_cell_t ihandle; + grub_ieee1275_cell_t result; + grub_ieee1275_cell_t size1; + grub_ieee1275_cell_t size2; + } args_ieee1275; + + if (last_ihandle) + grub_ieee1275_close (last_ihandle); + + last_ihandle = 0; + last_devpath = NULL; + + grub_ieee1275_open (device, &last_ihandle); + if (! last_ihandle) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device"); + + *block_size = 0; + + if (op->block_size_fails >= 2) + return GRUB_ERR_NONE; + + INIT_IEEE1275_COMMON (&args_ieee1275.common, "call-method", 2, 2); + args_ieee1275.method = (grub_ieee1275_cell_t) "block-size"; + args_ieee1275.ihandle = last_ihandle; + args_ieee1275.result = 1; + + if (IEEE1275_CALL_ENTRY_FN (&args_ieee1275) == -1) + { + grub_dprintf ("disk", "can't get block size: failed call-method\n"); + op->block_size_fails++; + } + else if (args_ieee1275.result) + { + grub_dprintf ("disk", "can't get block size: %lld\n", + (long long) args_ieee1275.result); + op->block_size_fails++; + } + else if (args_ieee1275.size1 + && !(args_ieee1275.size1 & (args_ieee1275.size1 - 1)) + && args_ieee1275.size1 >= 512 && args_ieee1275.size1 <= 16384) + { + op->block_size_fails = 0; + *block_size = args_ieee1275.size1; + } + + return 0; +} |