return sector >> (disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
}
+/* 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 grub_err_t
+grub_disk_read_small (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_off_t offset, grub_size_t size, void *buf)
+{
+ char *data;
+ char *tmp_buf;
+
+ /* Fetch the cache. */
+ data = grub_disk_cache_fetch (disk->dev->id, disk->id, sector);
+ if (data)
+ {
+ /* Just copy it! */
+ grub_memcpy (buf, data + offset, size);
+ grub_disk_cache_unlock (disk->dev->id, disk->id, sector);
+ return GRUB_ERR_NONE;
+ }
+
+ /* Allocate a temporary buffer. */
+ tmp_buf = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
+ if (! tmp_buf)
+ return grub_errno;
+
+ /* Otherwise read data from the disk actually. */
+ if (disk->total_sectors == GRUB_DISK_SIZE_UNKNOWN
+ || sector + GRUB_DISK_CACHE_SIZE
+ < (disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)))
+ {
+ grub_err_t err;
+ err = (disk->dev->read) (disk, transform_sector (disk, sector),
+ 1 << (GRUB_DISK_CACHE_BITS
+ + GRUB_DISK_SECTOR_BITS
+ - disk->log_sector_size), tmp_buf);
+ if (!err)
+ {
+ /* Copy it and store it in the disk cache. */
+ grub_memcpy (buf, tmp_buf + offset, size);
+ grub_disk_cache_store (disk->dev->id, disk->id,
+ sector, tmp_buf);
+ grub_free (tmp_buf);
+ return GRUB_ERR_NONE;
+ }
+ }
+
+ grub_errno = GRUB_ERR_NONE;
+
+ {
+ /* Uggh... Failed. Instead, just read necessary data. */
+ unsigned num;
+ grub_disk_addr_t aligned_sector;
+
+ sector += (offset >> GRUB_DISK_SECTOR_BITS);
+ offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
+ aligned_sector = (sector & ~((1 << (disk->log_sector_size
+ - GRUB_DISK_SECTOR_BITS))
+ - 1));
+ offset += ((sector - aligned_sector) << GRUB_DISK_SECTOR_BITS);
+ num = ((size + offset + (1 << (disk->log_sector_size))
+ - 1) >> (disk->log_sector_size));
+
+ tmp_buf = grub_malloc (num << disk->log_sector_size);
+ if (!tmp_buf)
+ return grub_errno;
+
+ if ((disk->dev->read) (disk, transform_sector (disk, aligned_sector),
+ num, tmp_buf))
+ {
+ grub_error_push ();
+ grub_dprintf ("disk", "%s read failed\n", disk->name);
+ grub_error_pop ();
+ return grub_errno;
+ }
+ grub_memcpy (buf, tmp_buf + offset, size);
+ return GRUB_ERR_NONE;
+ }
+}
+
/* Read data from the disk. */
grub_err_t
grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
grub_off_t offset, grub_size_t size, void *buf)
{
- char *tmp_buf;
- unsigned real_offset;
+ grub_off_t real_offset;
+ grub_disk_addr_t real_sector;
+ grub_size_t real_size;
/* First of all, check if the region is within the disk. */
if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE)
return grub_errno;
}
+ real_sector = sector;
real_offset = offset;
+ real_size = size;
- /* Allocate a temporary buffer. */
- tmp_buf = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
- if (! tmp_buf)
- return grub_errno;
-
- /* Until SIZE is zero... */
- while (size)
+ /* First read until first cache boundary. */
+ if (offset || (sector & (GRUB_DISK_CACHE_SIZE - 1)))
{
- char *data;
grub_disk_addr_t start_sector;
- grub_size_t len;
grub_size_t pos;
+ grub_err_t err;
+ grub_size_t len;
- /* For reading bulk data. */
start_sector = sector & ~(GRUB_DISK_CACHE_SIZE - 1);
pos = (sector - start_sector) << GRUB_DISK_SECTOR_BITS;
len = ((GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS)
- - pos - real_offset);
+ - pos - offset);
if (len > size)
len = size;
+ err = grub_disk_read_small (disk, start_sector,
+ offset + pos, len, buf);
+ if (err)
+ return err;
+ buf = (char *) buf + len;
+ size -= len;
+ offset += len;
+ sector += (offset >> GRUB_DISK_SECTOR_BITS);
+ offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
+ }
- /* Fetch the cache. */
- data = grub_disk_cache_fetch (disk->dev->id, disk->id, start_sector);
- if (data)
+ /* Until SIZE is zero... */
+ while (size >= (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS))
+ {
+ char *data = NULL;
+ grub_disk_addr_t agglomerate;
+ grub_err_t err;
+
+ /* agglomerate read until we find a first cached entry. */
+ for (agglomerate = 0; agglomerate
+ < (size >> (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS));
+ agglomerate++)
{
- /* Just copy it! */
- grub_memcpy (buf, data + pos + real_offset, len);
- grub_disk_cache_unlock (disk->dev->id, disk->id, start_sector);
+ data = grub_disk_cache_fetch (disk->dev->id, disk->id,
+ sector + (agglomerate
+ << GRUB_DISK_CACHE_BITS));
+ if (data)
+ break;
}
- else
- {
- /* Otherwise read data from the disk actually. */
- if ((disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN
- && start_sector + GRUB_DISK_CACHE_SIZE
- > (disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)))
- || (disk->dev->read) (disk, transform_sector (disk, start_sector),
- 1 << (GRUB_DISK_CACHE_BITS
- + GRUB_DISK_SECTOR_BITS
- - disk->log_sector_size), tmp_buf)
- != GRUB_ERR_NONE)
- {
- /* Uggh... Failed. Instead, just read necessary data. */
- unsigned num;
- char *p;
- grub_disk_addr_t aligned_sector;
-
- grub_errno = GRUB_ERR_NONE;
-
- aligned_sector = (sector & ~((1 << (disk->log_sector_size
- - GRUB_DISK_SECTOR_BITS))
- - 1));
- real_offset += ((sector - aligned_sector)
- << GRUB_DISK_SECTOR_BITS);
- num = ((size + real_offset + (1 << (disk->log_sector_size))
- - 1) >> (disk->log_sector_size));
-
- p = grub_realloc (tmp_buf, num << disk->log_sector_size);
- if (!p)
- goto finish;
-
- tmp_buf = p;
-
- if ((disk->dev->read) (disk, transform_sector (disk,
- aligned_sector),
- num, tmp_buf))
- {
- grub_error_push ();
- grub_dprintf ("disk", "%s read failed\n", disk->name);
- grub_error_pop ();
- goto finish;
- }
-
- grub_memcpy (buf, tmp_buf + real_offset, size);
-
- /* Call the read hook, if any. */
- if (disk->read_hook)
- while (size)
- {
- grub_size_t to_read = (size > GRUB_DISK_SECTOR_SIZE) ? GRUB_DISK_SECTOR_SIZE : size;
- (disk->read_hook) (sector, real_offset,
- to_read);
- if (grub_errno != GRUB_ERR_NONE)
- goto finish;
-
- sector++;
- size -= to_read - real_offset;
- real_offset = 0;
- }
-
- /* This must be the end. */
- goto finish;
- }
- /* Copy it and store it in the disk cache. */
- grub_memcpy (buf, tmp_buf + pos + real_offset, len);
- grub_disk_cache_store (disk->dev->id, disk->id,
- start_sector, tmp_buf);
+ if (agglomerate)
+ {
+ grub_disk_addr_t i;
+
+ err = (disk->dev->read) (disk, transform_sector (disk, sector),
+ agglomerate << (GRUB_DISK_CACHE_BITS
+ + GRUB_DISK_SECTOR_BITS
+ - disk->log_sector_size),
+ buf);
+ if (err)
+ return err;
+
+ for (i = 0; i < agglomerate; i ++)
+ grub_disk_cache_store (disk->dev->id, disk->id,
+ sector + (i << GRUB_DISK_CACHE_BITS),
+ (char *) buf
+ + (i << (GRUB_DISK_CACHE_BITS
+ + GRUB_DISK_SECTOR_BITS)));
+
+ sector += agglomerate << GRUB_DISK_CACHE_BITS;
+ size -= agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS);
+ buf = (char *) buf
+ + (agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS));
}
-
- /* Call the read hook, if any. */
- if (disk->read_hook)
+
+ if (data)
{
- grub_disk_addr_t s = sector;
- grub_size_t l = len;
-
- while (l)
- {
- (disk->read_hook) (s, real_offset,
- ((l > GRUB_DISK_SECTOR_SIZE)
- ? GRUB_DISK_SECTOR_SIZE
- : l));
-
- if (l < GRUB_DISK_SECTOR_SIZE - real_offset)
- break;
-
- s++;
- l -= GRUB_DISK_SECTOR_SIZE - real_offset;
- real_offset = 0;
- }
+ grub_memcpy (buf, data, GRUB_DISK_CACHE_SIZE);
+ sector += GRUB_DISK_CACHE_SIZE;
+ buf = (char *) buf + (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
+ size -= (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
+ grub_disk_cache_unlock (disk->dev->id, disk->id,
+ sector + (agglomerate
+ << GRUB_DISK_CACHE_BITS));
}
+ }
- sector = start_sector + GRUB_DISK_CACHE_SIZE;
- buf = (char *) buf + len;
- size -= len;
- real_offset = 0;
+ /* And now read the last part. */
+ if (size)
+ {
+ grub_err_t err;
+ err = grub_disk_read_small (disk, sector, 0, size, buf);
+ if (err)
+ return err;
}
- finish:
+ /* Call the read hook, if any. */
+ if (disk->read_hook)
+ {
+ grub_disk_addr_t s = real_sector;
+ grub_size_t l = real_size;
+ grub_off_t o = real_offset;
- grub_free (tmp_buf);
+ while (l)
+ {
+ (disk->read_hook) (s, o,
+ ((l > GRUB_DISK_SECTOR_SIZE)
+ ? GRUB_DISK_SECTOR_SIZE
+ : l));
+ s++;
+ l -= GRUB_DISK_SECTOR_SIZE - o;
+ o = 0;
+ }
+ }
return grub_errno;
}