summaryrefslogtreecommitdiffstats
path: root/grub-core/disk/ieee1275
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/disk/ieee1275')
-rw-r--r--grub-core/disk/ieee1275/nand.c242
-rw-r--r--grub-core/disk/ieee1275/obdisk.c1076
-rw-r--r--grub-core/disk/ieee1275/ofdisk.c741
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;
+}