/* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2002,2003,2004,2006,2007,2008,2009,2010 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 . */ #include #include #include #include #include #include #include #include #include #define VAS_EBOOT_CACHE_TIMEOUT 2 /* Disk reads may trigger other disk reads. So, limit recursion depth. */ #define MAX_READ_RECURSION_DEPTH 16 static unsigned int read_recursion_depth = 0; /* The last time the disk was used. */ static VasEBoot_uint64_t VasEBoot_last_time = 0; struct VasEBoot_disk_cache VasEBoot_disk_cache_table[VAS_EBOOT_DISK_CACHE_NUM]; void (*VasEBoot_disk_firmware_fini) (void); int VasEBoot_disk_firmware_is_tainted; #if DISK_CACHE_STATS static unsigned long VasEBoot_disk_cache_hits; static unsigned long VasEBoot_disk_cache_misses; void VasEBoot_disk_cache_get_performance (unsigned long *hits, unsigned long *misses) { *hits = VasEBoot_disk_cache_hits; *misses = VasEBoot_disk_cache_misses; } #endif VasEBoot_err_t (*VasEBoot_disk_write_weak) (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector, VasEBoot_off_t offset, VasEBoot_size_t size, const void *buf); #include "disk_common.c" void VasEBoot_disk_cache_invalidate_all (void) { unsigned i; for (i = 0; i < VAS_EBOOT_DISK_CACHE_NUM; i++) { struct VasEBoot_disk_cache *cache = VasEBoot_disk_cache_table + i; if (cache->data && ! cache->lock) { VasEBoot_free (cache->data); cache->data = 0; } } } static char * VasEBoot_disk_cache_fetch (unsigned long dev_id, unsigned long disk_id, VasEBoot_disk_addr_t sector) { struct VasEBoot_disk_cache *cache; unsigned cache_index; cache_index = VasEBoot_disk_cache_get_index (dev_id, disk_id, sector); cache = VasEBoot_disk_cache_table + cache_index; if (cache->dev_id == dev_id && cache->disk_id == disk_id && cache->sector == sector) { cache->lock = 1; #if DISK_CACHE_STATS VasEBoot_disk_cache_hits++; #endif return cache->data; } #if DISK_CACHE_STATS VasEBoot_disk_cache_misses++; #endif return 0; } static void VasEBoot_disk_cache_unlock (unsigned long dev_id, unsigned long disk_id, VasEBoot_disk_addr_t sector) { struct VasEBoot_disk_cache *cache; unsigned cache_index; cache_index = VasEBoot_disk_cache_get_index (dev_id, disk_id, sector); cache = VasEBoot_disk_cache_table + cache_index; if (cache->dev_id == dev_id && cache->disk_id == disk_id && cache->sector == sector) cache->lock = 0; } static VasEBoot_err_t VasEBoot_disk_cache_store (unsigned long dev_id, unsigned long disk_id, VasEBoot_disk_addr_t sector, const char *data) { unsigned cache_index; struct VasEBoot_disk_cache *cache; cache_index = VasEBoot_disk_cache_get_index (dev_id, disk_id, sector); cache = VasEBoot_disk_cache_table + cache_index; cache->lock = 1; VasEBoot_free (cache->data); cache->data = 0; cache->lock = 0; cache->data = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE << VAS_EBOOT_DISK_CACHE_BITS); if (! cache->data) return VasEBoot_errno; VasEBoot_memcpy (cache->data, data, VAS_EBOOT_DISK_SECTOR_SIZE << VAS_EBOOT_DISK_CACHE_BITS); cache->dev_id = dev_id; cache->disk_id = disk_id; cache->sector = sector; return VAS_EBOOT_ERR_NONE; } VasEBoot_disk_dev_t VasEBoot_disk_dev_list; void VasEBoot_disk_dev_register (VasEBoot_disk_dev_t dev) { dev->next = VasEBoot_disk_dev_list; VasEBoot_disk_dev_list = dev; } void VasEBoot_disk_dev_unregister (VasEBoot_disk_dev_t dev) { VasEBoot_disk_dev_t *p, q; for (p = &VasEBoot_disk_dev_list, q = *p; q; p = &(q->next), q = q->next) if (q == dev) { *p = q->next; break; } } /* Return the location of the first ',', if any, which is not escaped by a '\'. */ static const char * find_part_sep (const char *name) { const char *p = name; char c; while ((c = *p++) != '\0') { if (c == '\\' && *p == ',') p++; else if (c == ',') return p - 1; } return NULL; } VasEBoot_disk_t VasEBoot_disk_open (const char *name) { const char *p; VasEBoot_disk_t disk; VasEBoot_disk_dev_t dev; char *raw = (char *) name; VasEBoot_uint64_t current_time; VasEBoot_dprintf ("disk", "Opening `%s'...\n", name); disk = (VasEBoot_disk_t) VasEBoot_zalloc (sizeof (*disk)); if (! disk) return 0; disk->log_sector_size = VAS_EBOOT_DISK_SECTOR_BITS; /* Default 1MiB of maximum agglomerate. */ disk->max_agglomerate = 1048576 >> (VAS_EBOOT_DISK_SECTOR_BITS + VAS_EBOOT_DISK_CACHE_BITS); p = find_part_sep (name); if (p) { VasEBoot_size_t len = p - name; raw = VasEBoot_malloc (len + 1); if (! raw) goto fail; VasEBoot_memcpy (raw, name, len); raw[len] = '\0'; disk->name = VasEBoot_strdup (raw); } else disk->name = VasEBoot_strdup (name); if (! disk->name) goto fail; for (dev = VasEBoot_disk_dev_list; dev; dev = dev->next) { if ((dev->disk_open) (raw, disk) == VAS_EBOOT_ERR_NONE) break; else if (VasEBoot_errno == VAS_EBOOT_ERR_UNKNOWN_DEVICE) VasEBoot_errno = VAS_EBOOT_ERR_NONE; else goto fail; } if (! dev) { VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, N_("disk `%s' not found"), name); goto fail; } if (disk->log_sector_size > VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS || disk->log_sector_size < VAS_EBOOT_DISK_SECTOR_BITS) { VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "sector sizes of %d bytes aren't supported yet", (1 << disk->log_sector_size)); goto fail; } disk->dev = dev; if (p) { disk->partition = VasEBoot_partition_probe (disk, p + 1); if (! disk->partition) { /* TRANSLATORS: It means that the specified partition e.g. hd0,msdos1=/dev/sda1 doesn't exist. */ VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, N_("no such partition")); goto fail; } } /* The cache will be invalidated about 2 seconds after a device was closed. */ current_time = VasEBoot_get_time_ms (); if (current_time > (VasEBoot_last_time + VAS_EBOOT_CACHE_TIMEOUT * 1000)) VasEBoot_disk_cache_invalidate_all (); VasEBoot_last_time = current_time; fail: if (raw && raw != name) VasEBoot_free (raw); if (VasEBoot_errno != VAS_EBOOT_ERR_NONE) { VasEBoot_error_push (); VasEBoot_dprintf ("disk", "Opening `%s' failed.\n", name); VasEBoot_error_pop (); VasEBoot_disk_close (disk); return 0; } return disk; } void VasEBoot_disk_close (VasEBoot_disk_t disk) { VasEBoot_partition_t part; if (disk == NULL) return; VasEBoot_dprintf ("disk", "Closing `%s'.\n", disk->name); if (disk->dev && disk->dev->disk_close) (disk->dev->disk_close) (disk); /* Reset the timer. */ VasEBoot_last_time = VasEBoot_get_time_ms (); while (disk->partition) { part = disk->partition->parent; VasEBoot_free (disk->partition); disk->partition = part; } VasEBoot_free ((void *) disk->name); VasEBoot_free (disk); } /* Small read (less than cache size and not pass across cache unit boundaries). sector is already adjusted and is divisible by cache unit size. */ static VasEBoot_err_t VasEBoot_disk_read_small_real (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector, VasEBoot_off_t offset, VasEBoot_size_t size, void *buf) { char *data; char *tmp_buf; /* Fetch the cache. */ data = VasEBoot_disk_cache_fetch (disk->dev->id, disk->id, sector); if (data) { /* Just copy it! */ VasEBoot_memcpy (buf, data + offset, size); VasEBoot_disk_cache_unlock (disk->dev->id, disk->id, sector); return VAS_EBOOT_ERR_NONE; } /* Allocate a temporary buffer. */ tmp_buf = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE << VAS_EBOOT_DISK_CACHE_BITS); if (! tmp_buf) return VasEBoot_errno; /* Otherwise read data from the disk actually. */ if (disk->total_sectors == VAS_EBOOT_DISK_SIZE_UNKNOWN || sector + VAS_EBOOT_DISK_CACHE_SIZE < (disk->total_sectors << (disk->log_sector_size - VAS_EBOOT_DISK_SECTOR_BITS))) { VasEBoot_err_t err; err = (disk->dev->disk_read) (disk, VasEBoot_disk_to_native_sector (disk, sector), 1U << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS - disk->log_sector_size), tmp_buf); if (!err) { /* Copy it and store it in the disk cache. */ VasEBoot_memcpy (buf, tmp_buf + offset, size); VasEBoot_disk_cache_store (disk->dev->id, disk->id, sector, tmp_buf); VasEBoot_free (tmp_buf); return VAS_EBOOT_ERR_NONE; } } VasEBoot_free (tmp_buf); VasEBoot_errno = VAS_EBOOT_ERR_NONE; { /* Uggh... Failed. Instead, just read necessary data. */ unsigned num; VasEBoot_disk_addr_t aligned_sector; sector += (offset >> VAS_EBOOT_DISK_SECTOR_BITS); offset &= ((1 << VAS_EBOOT_DISK_SECTOR_BITS) - 1); aligned_sector = (sector & ~((1ULL << (disk->log_sector_size - VAS_EBOOT_DISK_SECTOR_BITS)) - 1)); offset += ((sector - aligned_sector) << VAS_EBOOT_DISK_SECTOR_BITS); num = ((size + offset + (1ULL << (disk->log_sector_size)) - 1) >> (disk->log_sector_size)); tmp_buf = VasEBoot_malloc (num << disk->log_sector_size); if (!tmp_buf) return VasEBoot_errno; if ((disk->dev->disk_read) (disk, VasEBoot_disk_to_native_sector (disk, aligned_sector), num, tmp_buf)) { VasEBoot_error_push (); VasEBoot_dprintf ("disk", "%s read failed\n", disk->name); VasEBoot_error_pop (); VasEBoot_free (tmp_buf); return VasEBoot_errno; } VasEBoot_memcpy (buf, tmp_buf + offset, size); VasEBoot_free (tmp_buf); return VAS_EBOOT_ERR_NONE; } } static VasEBoot_err_t VasEBoot_disk_read_small (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector, VasEBoot_off_t offset, VasEBoot_size_t size, void *buf) { VasEBoot_err_t err; err = VasEBoot_disk_read_small_real (disk, sector, offset, size, buf); if (err) return err; if (disk->read_hook) err = (disk->read_hook) (sector + (offset >> VAS_EBOOT_DISK_SECTOR_BITS), offset & (VAS_EBOOT_DISK_SECTOR_SIZE - 1), size, buf, disk->read_hook_data); return err; } /* Read data from the disk. */ VasEBoot_err_t VasEBoot_disk_read (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector, VasEBoot_off_t offset, VasEBoot_size_t size, void *buf) { VasEBoot_err_t err = VAS_EBOOT_ERR_NONE; /* First of all, check if the region is within the disk. */ if (VasEBoot_disk_adjust_range (disk, §or, &offset, size) != VAS_EBOOT_ERR_NONE) { VasEBoot_error_push (); VasEBoot_dprintf ("disk", "Read out of range: sector 0x%llx (%s).\n", (unsigned long long) sector, VasEBoot_errmsg); VasEBoot_error_pop (); return VasEBoot_errno; } if (++read_recursion_depth >= MAX_READ_RECURSION_DEPTH) { VasEBoot_error (VAS_EBOOT_ERR_RECURSION_DEPTH, "VasEBoot_disk_read(): Maximum recursion depth exceeded"); goto error; } /* First read until first cache boundary. */ if (offset || (sector & (VAS_EBOOT_DISK_CACHE_SIZE - 1))) { VasEBoot_disk_addr_t start_sector; VasEBoot_size_t pos; VasEBoot_size_t len; start_sector = sector & ~((VasEBoot_disk_addr_t) VAS_EBOOT_DISK_CACHE_SIZE - 1); pos = (sector - start_sector) << VAS_EBOOT_DISK_SECTOR_BITS; len = ((VAS_EBOOT_DISK_SECTOR_SIZE << VAS_EBOOT_DISK_CACHE_BITS) - pos - offset); if (len > size) len = size; err = VasEBoot_disk_read_small (disk, start_sector, offset + pos, len, buf); if (err) goto error; buf = (char *) buf + len; size -= len; offset += len; sector += (offset >> VAS_EBOOT_DISK_SECTOR_BITS); offset &= ((1 << VAS_EBOOT_DISK_SECTOR_BITS) - 1); } /* Until SIZE is zero... */ while (size >= (VAS_EBOOT_DISK_CACHE_SIZE << VAS_EBOOT_DISK_SECTOR_BITS)) { char *data = NULL; VasEBoot_disk_addr_t agglomerate; /* agglomerate read until we find a first cached entry. */ for (agglomerate = 0; agglomerate < (size >> (VAS_EBOOT_DISK_SECTOR_BITS + VAS_EBOOT_DISK_CACHE_BITS)) && agglomerate < disk->max_agglomerate; agglomerate++) { data = VasEBoot_disk_cache_fetch (disk->dev->id, disk->id, sector + (agglomerate << VAS_EBOOT_DISK_CACHE_BITS)); if (data) break; } if (data) { VasEBoot_memcpy ((char *) buf + (agglomerate << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS)), data, VAS_EBOOT_DISK_CACHE_SIZE << VAS_EBOOT_DISK_SECTOR_BITS); VasEBoot_disk_cache_unlock (disk->dev->id, disk->id, sector + (agglomerate << VAS_EBOOT_DISK_CACHE_BITS)); } if (agglomerate) { VasEBoot_disk_addr_t i; err = (disk->dev->disk_read) (disk, VasEBoot_disk_to_native_sector (disk, sector), agglomerate << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS - disk->log_sector_size), buf); if (err) goto error; for (i = 0; i < agglomerate; i ++) VasEBoot_disk_cache_store (disk->dev->id, disk->id, sector + (i << VAS_EBOOT_DISK_CACHE_BITS), (char *) buf + (i << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS))); if (disk->read_hook) (disk->read_hook) (sector, 0, agglomerate << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS), buf, disk->read_hook_data); sector += agglomerate << VAS_EBOOT_DISK_CACHE_BITS; size -= agglomerate << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS); buf = (char *) buf + (agglomerate << (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS)); } if (data) { if (disk->read_hook) (disk->read_hook) (sector, 0, (VAS_EBOOT_DISK_CACHE_SIZE << VAS_EBOOT_DISK_SECTOR_BITS), buf, disk->read_hook_data); sector += VAS_EBOOT_DISK_CACHE_SIZE; buf = (char *) buf + (VAS_EBOOT_DISK_CACHE_SIZE << VAS_EBOOT_DISK_SECTOR_BITS); size -= (VAS_EBOOT_DISK_CACHE_SIZE << VAS_EBOOT_DISK_SECTOR_BITS); } } /* And now read the last part. */ if (size) { err = VasEBoot_disk_read_small (disk, sector, 0, size, buf); if (err) goto error; } err = VasEBoot_errno; error: read_recursion_depth--; return err; } VasEBoot_uint64_t VasEBoot_disk_native_sectors (VasEBoot_disk_t disk) { if (disk->partition) return VasEBoot_partition_get_len (disk->partition); else if (disk->total_sectors != VAS_EBOOT_DISK_SIZE_UNKNOWN) return disk->total_sectors << (disk->log_sector_size - VAS_EBOOT_DISK_SECTOR_BITS); else return VAS_EBOOT_DISK_SIZE_UNKNOWN; }