vaseboot/VasEBoot-core/disk/ieee1275/ofdisk.c

787 lines
20 KiB
C

/* ofdisk.c - Open Firmware disk access. */
/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2004,2006,2007,2008,2009 Free Software Foundation, Inc.
*
* VAS_EBOOT 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.
*
* VAS_EBOOT 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 VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/misc.h>
#include <VasEBoot/disk.h>
#include <VasEBoot/mm.h>
#include <VasEBoot/ieee1275/ieee1275.h>
#include <VasEBoot/ieee1275/ofdisk.h>
#include <VasEBoot/i18n.h>
#include <VasEBoot/time.h>
#include <VasEBoot/safemath.h>
static char *last_devpath;
static VasEBoot_ieee1275_ihandle_t last_ihandle;
struct ofdisk_hash_ent
{
char *devpath;
char *open_path;
char *VasEBoot_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 *VasEBoot_shortest;
struct ofdisk_hash_ent *next;
};
static VasEBoot_err_t
VasEBoot_ofdisk_get_block_size (const char *device, VasEBoot_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 (!VasEBoot_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;
VasEBoot_size_t sz;
p = VasEBoot_zalloc (sizeof (*p));
if (!p)
return NULL;
p->devpath = devpath;
if (VasEBoot_mul (VasEBoot_strlen (p->devpath), 2, &sz) ||
VasEBoot_add (sz, sizeof ("ieee1275/"), &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while obtaining size of device path"));
VasEBoot_free (p);
return NULL;
}
p->VasEBoot_devpath = VasEBoot_malloc (sz);
if (!p->VasEBoot_devpath)
{
VasEBoot_free (p);
return NULL;
}
if (! VasEBoot_ieee1275_test_flag (VAS_EBOOT_IEEE1275_FLAG_NO_PARTITION_0))
{
if (VasEBoot_add (VasEBoot_strlen (p->devpath), 3, &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while obtaining size of an open path"));
VasEBoot_free (p->VasEBoot_devpath);
VasEBoot_free (p);
return NULL;
}
p->open_path = VasEBoot_malloc (sz);
if (!p->open_path)
{
VasEBoot_free (p->VasEBoot_devpath);
VasEBoot_free (p);
return NULL;
}
optr = VasEBoot_stpcpy (p->open_path, p->devpath);
*optr++ = ':';
*optr++ = '0';
*optr = '\0';
}
else
p->open_path = p->devpath;
optr = VasEBoot_stpcpy (p->VasEBoot_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 = VasEBoot_strrchr (str, '/');
if (ptr)
ptr++;
else
ptr = str;
return (VasEBoot_strncmp (ptr, "cdrom", 5) == 0 || VasEBoot_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);
VasEBoot_dprintf ("disk", "devpath = %s, canonical = %s\n", devpath, curcan);
if (!curcan)
{
p->shortest = p->devpath;
p->VasEBoot_shortest = p->VasEBoot_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
VasEBoot_free (curcan);
if (check_string_removable (devpath) || check_string_removable (curcan))
pcan->is_removable = 1;
if (!pcan)
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
else
{
if (!pcan->shortest
|| VasEBoot_strlen (pcan->shortest) > VasEBoot_strlen (devpath))
{
pcan->shortest = p->devpath;
pcan->VasEBoot_shortest = p->VasEBoot_devpath;
}
}
return p;
}
static void
dev_iterate_real (const char *name, const char *path)
{
struct ofdisk_hash_ent *op;
VasEBoot_dprintf ("disk", "disk name = %s, path = %s\n", name,
path);
op = ofdisk_hash_find (path);
if (!op)
{
char *name_dup = VasEBoot_strdup (name);
char *can = VasEBoot_strdup (path);
if (!name_dup || !can)
{
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
VasEBoot_free (name_dup);
VasEBoot_free (can);
return;
}
op = ofdisk_hash_add (name_dup, can);
}
return;
}
static void
dev_iterate (const struct VasEBoot_ieee1275_devalias *alias)
{
if (VasEBoot_strcmp (alias->type, "vscsi") == 0)
{
static VasEBoot_ieee1275_ihandle_t ihandle;
struct set_color_args
{
struct VasEBoot_ieee1275_common_hdr common;
VasEBoot_ieee1275_cell_t method;
VasEBoot_ieee1275_cell_t ihandle;
VasEBoot_ieee1275_cell_t catch_result;
VasEBoot_ieee1275_cell_t nentries;
VasEBoot_ieee1275_cell_t table;
}
args;
char *buf, *bufptr;
unsigned i;
VasEBoot_size_t sz;
if (VasEBoot_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 = (VasEBoot_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)
{
VasEBoot_ieee1275_close (ihandle);
return;
}
if (VasEBoot_add (VasEBoot_strlen (alias->path), 32, &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "overflow detected while creating buffer for vscsi");
VasEBoot_ieee1275_close (ihandle);
return;
}
buf = VasEBoot_malloc (sz);
if (!buf)
{
VasEBoot_ieee1275_close (ihandle);
return;
}
bufptr = VasEBoot_stpcpy (buf, alias->path);
for (i = 0; i < args.nentries; i++)
{
VasEBoot_uint64_t *ptr;
ptr = *(VasEBoot_uint64_t **) (args.table + 4 + 8 * i);
while (*ptr)
{
VasEBoot_snprintf (bufptr, 32, "/disk@%" PRIxVAS_EBOOT_UINT64_T, *ptr++);
dev_iterate_real (buf, buf);
}
}
VasEBoot_ieee1275_close (ihandle);
VasEBoot_free (buf);
return;
}
else if (VasEBoot_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 VasEBoot_ieee1275_common_hdr common;
VasEBoot_ieee1275_cell_t method;
VasEBoot_ieee1275_cell_t ihandle;
VasEBoot_ieee1275_cell_t max;
VasEBoot_ieee1275_cell_t table;
VasEBoot_ieee1275_cell_t catch_result;
VasEBoot_ieee1275_cell_t nentries;
}
args;
char *buf, *bufptr;
unsigned i;
VasEBoot_uint64_t *table;
VasEBoot_uint16_t table_size;
VasEBoot_ieee1275_ihandle_t ihandle;
VasEBoot_size_t sz;
if (VasEBoot_add (VasEBoot_strlen (alias->path), sizeof ("/disk@7766554433221100"), &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, "overflow detected while creating buffer for sas_ioa");
return;
}
buf = VasEBoot_malloc (sz);
if (!buf)
return;
bufptr = VasEBoot_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 = VasEBoot_calloc (table_size, sizeof (VasEBoot_uint64_t));
if (!table)
{
VasEBoot_free (buf);
return;
}
if (VasEBoot_ieee1275_open (alias->path, &ihandle))
{
VasEBoot_free (buf);
VasEBoot_free (table);
return;
}
INIT_IEEE1275_COMMON (&args.common, "call-method", 4, 2);
args.method = (VasEBoot_ieee1275_cell_t) "get-sas-children";
args.ihandle = ihandle;
args.max = table_size;
args.table = (VasEBoot_ieee1275_cell_t) table;
args.catch_result = 0;
args.nentries = 0;
if (IEEE1275_CALL_ENTRY_FN (&args) == -1)
{
VasEBoot_ieee1275_close (ihandle);
VasEBoot_free (table);
VasEBoot_free (buf);
return;
}
for (i = 0; i < args.nentries; i++)
{
VasEBoot_snprintf (bufptr, sizeof ("/disk@7766554433221100"),
"/disk@%" PRIxVAS_EBOOT_UINT64_T, table[i]);
dev_iterate_real (buf, buf);
}
VasEBoot_ieee1275_close (ihandle);
VasEBoot_free (table);
VasEBoot_free (buf);
}
if (!VasEBoot_ieee1275_test_flag (VAS_EBOOT_IEEE1275_FLAG_NO_TREE_SCANNING_FOR_DISKS)
&& VasEBoot_strcmp (alias->type, "block") == 0)
{
dev_iterate_real (alias->path, alias->path);
return;
}
{
struct VasEBoot_ieee1275_devalias child;
FOR_IEEE1275_DEVCHILDREN(alias->path, child)
dev_iterate (&child);
}
}
static void
scan (void)
{
struct VasEBoot_ieee1275_devalias alias;
FOR_IEEE1275_DEVALIASES(alias)
{
if (VasEBoot_strcmp (alias.type, "block") != 0)
continue;
dev_iterate_real (alias.name, alias.path);
}
FOR_IEEE1275_DEVCHILDREN("/", alias)
dev_iterate (&alias);
}
static int
VasEBoot_ofdisk_iterate (VasEBoot_disk_dev_iterate_hook_t hook, void *hook_data,
VasEBoot_disk_pull_t pull)
{
unsigned i;
if (pull != VAS_EBOOT_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 (VasEBoot_ieee1275_test_flag (VAS_EBOOT_IEEE1275_FLAG_OFDISK_SDCARD_ONLY))
{
VasEBoot_ieee1275_phandle_t dev;
char tmp[8];
if (VasEBoot_ieee1275_finddevice (ent->devpath, &dev))
{
VasEBoot_dprintf ("disk", "finddevice (%s) failed\n",
ent->devpath);
continue;
}
if (VasEBoot_ieee1275_get_property (dev, "iconname", tmp,
sizeof tmp, 0))
{
VasEBoot_dprintf ("disk", "get iconname failed\n");
continue;
}
if (VasEBoot_strcmp (tmp, "sdmmc") != 0)
{
VasEBoot_dprintf ("disk", "device is not an SD card\n");
continue;
}
}
if (!ent->is_boot && ent->is_removable)
continue;
if (hook (ent->VasEBoot_shortest, hook_data))
return 1;
}
}
return 0;
}
static char *
compute_dev_path (const char *name)
{
char *devpath;
char *p, c;
VasEBoot_size_t sz;
if (VasEBoot_add (VasEBoot_strlen (name), 3, &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while obtaining size of device path"));
return NULL;
}
devpath = VasEBoot_malloc (sz);
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 VasEBoot_err_t
VasEBoot_ofdisk_open (const char *name, VasEBoot_disk_t disk)
{
VasEBoot_ieee1275_phandle_t dev;
char *devpath;
/* XXX: This should be large enough for any possible case. */
char prop[64];
VasEBoot_ssize_t actual;
VasEBoot_uint32_t block_size = 0;
VasEBoot_err_t err;
if (VasEBoot_strncmp (name, "ieee1275/", sizeof ("ieee1275/") - 1) != 0)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE,
"not IEEE1275 device");
devpath = compute_dev_path (name + sizeof ("ieee1275/") - 1);
if (! devpath)
return VasEBoot_errno;
VasEBoot_dprintf ("disk", "Opening `%s'.\n", devpath);
if (VasEBoot_ieee1275_finddevice (devpath, &dev))
{
VasEBoot_free (devpath);
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE,
"can't read device properties");
}
if (VasEBoot_ieee1275_get_property (dev, "device_type", prop, sizeof (prop),
&actual))
{
VasEBoot_free (devpath);
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "can't read the device type");
}
if (VasEBoot_strcmp (prop, "block"))
{
VasEBoot_free (devpath);
return VasEBoot_error (VAS_EBOOT_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 = VAS_EBOOT_DISK_SIZE_UNKNOWN;
{
struct ofdisk_hash_ent *op;
op = ofdisk_hash_find (devpath);
if (!op)
op = ofdisk_hash_add (devpath, NULL);
if (!op)
{
VasEBoot_free (devpath);
return VasEBoot_errno;
}
disk->id = (unsigned long) op;
disk->data = op->open_path;
err = VasEBoot_ofdisk_get_block_size (devpath, &block_size, op);
if (err)
{
VasEBoot_free (devpath);
return err;
}
if (block_size != 0)
disk->log_sector_size = VasEBoot_log2ull (block_size);
else
disk->log_sector_size = 9;
}
VasEBoot_free (devpath);
return 0;
}
static void
VasEBoot_ofdisk_close (VasEBoot_disk_t disk)
{
if (disk->data == last_devpath)
{
if (last_ihandle)
VasEBoot_ieee1275_close (last_ihandle);
last_ihandle = 0;
last_devpath = NULL;
}
disk->data = 0;
}
static VasEBoot_err_t
VasEBoot_ofdisk_prepare (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector)
{
VasEBoot_ssize_t status;
unsigned long long pos;
if (disk->data != last_devpath)
{
if (last_ihandle)
VasEBoot_ieee1275_close (last_ihandle);
last_ihandle = 0;
last_devpath = NULL;
VasEBoot_ieee1275_open (disk->data, &last_ihandle);
if (! last_ihandle)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "can't open device");
last_devpath = disk->data;
}
pos = sector << disk->log_sector_size;
VasEBoot_ieee1275_seek (last_ihandle, pos, &status);
if (status < 0)
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR,
"seek error, can't seek block %llu",
(long long) sector);
return 0;
}
static VasEBoot_err_t
VasEBoot_ofdisk_read (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector,
VasEBoot_size_t size, char *buf)
{
VasEBoot_err_t err;
VasEBoot_ssize_t actual;
err = VasEBoot_ofdisk_prepare (disk, sector);
if (err)
return err;
VasEBoot_ieee1275_read (last_ihandle, buf, size << disk->log_sector_size,
&actual);
if (actual != (VasEBoot_ssize_t) (size << disk->log_sector_size))
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
"from `%s'"),
(unsigned long long) sector,
disk->name);
return 0;
}
static VasEBoot_err_t
VasEBoot_ofdisk_write (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector,
VasEBoot_size_t size, const char *buf)
{
VasEBoot_err_t err;
VasEBoot_ssize_t actual;
err = VasEBoot_ofdisk_prepare (disk, sector);
if (err)
return err;
VasEBoot_ieee1275_write (last_ihandle, buf, size << disk->log_sector_size,
&actual);
if (actual != (VasEBoot_ssize_t) (size << disk->log_sector_size))
return VasEBoot_error (VAS_EBOOT_ERR_WRITE_ERROR, N_("failure writing sector 0x%llx "
"to `%s'"),
(unsigned long long) sector,
disk->name);
return 0;
}
static struct VasEBoot_disk_dev VasEBoot_ofdisk_dev =
{
.name = "ofdisk",
.id = VAS_EBOOT_DISK_DEVICE_OFDISK_ID,
.disk_iterate = VasEBoot_ofdisk_iterate,
.disk_open = VasEBoot_ofdisk_open,
.disk_close = VasEBoot_ofdisk_close,
.disk_read = VasEBoot_ofdisk_read,
.disk_write = VasEBoot_ofdisk_write,
.next = 0
};
static void
insert_bootpath (void)
{
char *bootpath;
VasEBoot_ssize_t bootpath_size;
char *type;
VasEBoot_size_t sz;
if (VasEBoot_ieee1275_get_property_length (VasEBoot_ieee1275_chosen, "bootpath",
&bootpath_size)
|| bootpath_size <= 0)
{
/* Should never happen. */
VasEBoot_printf ("/chosen/bootpath property missing!\n");
return;
}
if (VasEBoot_add (bootpath_size, 64, &sz))
{
VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE, N_("overflow detected while obtaining bootpath size"));
return;
}
bootpath = (char *) VasEBoot_malloc (sz);
if (! bootpath)
{
VasEBoot_print_error ();
return;
}
VasEBoot_ieee1275_get_property (VasEBoot_ieee1275_chosen, "bootpath", bootpath,
(VasEBoot_size_t) bootpath_size + 1, 0);
bootpath[bootpath_size] = '\0';
/* Transform an OF device path to a VAS_EBOOT path. */
type = VasEBoot_ieee1275_get_device_type (bootpath);
if (!(type && VasEBoot_strcmp (type, "network") == 0))
{
struct ofdisk_hash_ent *op;
char *device = VasEBoot_ieee1275_get_devname (bootpath);
op = ofdisk_hash_add (device, NULL);
op->is_boot = 1;
}
VasEBoot_free (type);
VasEBoot_free (bootpath);
}
void
VasEBoot_ofdisk_fini (void)
{
if (last_ihandle)
VasEBoot_ieee1275_close (last_ihandle);
last_ihandle = 0;
last_devpath = NULL;
VasEBoot_disk_dev_unregister (&VasEBoot_ofdisk_dev);
}
void
VasEBoot_ofdisk_init (void)
{
VasEBoot_disk_firmware_fini = VasEBoot_ofdisk_fini;
insert_bootpath ();
VasEBoot_disk_dev_register (&VasEBoot_ofdisk_dev);
}
static VasEBoot_err_t
VasEBoot_ofdisk_get_block_size (const char *device, VasEBoot_uint32_t *block_size,
struct ofdisk_hash_ent *op)
{
struct size_args_ieee1275
{
struct VasEBoot_ieee1275_common_hdr common;
VasEBoot_ieee1275_cell_t method;
VasEBoot_ieee1275_cell_t ihandle;
VasEBoot_ieee1275_cell_t result;
VasEBoot_ieee1275_cell_t size1;
VasEBoot_ieee1275_cell_t size2;
} args_ieee1275;
if (last_ihandle)
VasEBoot_ieee1275_close (last_ihandle);
last_ihandle = 0;
last_devpath = NULL;
VasEBoot_ieee1275_open (device, &last_ihandle);
if (! last_ihandle)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "can't open device");
*block_size = 0;
if (op->block_size_fails >= 2)
return VAS_EBOOT_ERR_NONE;
INIT_IEEE1275_COMMON (&args_ieee1275.common, "call-method", 2, 2);
args_ieee1275.method = (VasEBoot_ieee1275_cell_t) "block-size";
args_ieee1275.ihandle = last_ihandle;
args_ieee1275.result = 1;
if (IEEE1275_CALL_ENTRY_FN (&args_ieee1275) == -1)
{
VasEBoot_dprintf ("disk", "can't get block size: failed call-method\n");
op->block_size_fails++;
}
else if (args_ieee1275.result)
{
VasEBoot_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;
}