diff options
Diffstat (limited to 'master/debian/4k_sectors.patch')
-rw-r--r-- | master/debian/4k_sectors.patch | 1029 |
1 files changed, 1029 insertions, 0 deletions
diff --git a/master/debian/4k_sectors.patch b/master/debian/4k_sectors.patch new file mode 100644 index 0000000..7281b3f --- /dev/null +++ b/master/debian/4k_sectors.patch @@ -0,0 +1,1029 @@ +Description: Support non-512B sectors and agglomerate reads +Author: Vladimir Serbinenko <phcoder@gmail.com> +Origin: backport, http://bazaar.launchpad.net/~vcs-imports/grub/grub2-bzr/revision/3325 +Origin: upstream, http://bazaar.launchpad.net/~vcs-imports/grub/grub2-bzr/revision/3476 +Origin: upstream, http://bazaar.launchpad.net/~vcs-imports/grub/grub2-bzr/revision/3709 +Forwarded: not-needed +Applied-Upstream: http://bazaar.launchpad.net/~vcs-imports/grub/grub2-bzr/revision/3325 +Last-Update: 2012-04-02 + +Index: b/Makefile.util.def +=================================================================== +--- a/Makefile.util.def ++++ b/Makefile.util.def +@@ -34,6 +34,7 @@ + common_nodist = grub_script.tab.h; + + common = grub-core/commands/blocklist.c; ++ common = grub-core/commands/testload.c; + common = grub-core/commands/extcmd.c; + common = grub-core/commands/ls.c; + common = grub-core/disk/dmraid_nvidia.c; +Index: b/grub-core/disk/efi/efidisk.c +=================================================================== +--- a/grub-core/disk/efi/efidisk.c ++++ b/grub-core/disk/efi/efidisk.c +@@ -33,12 +33,10 @@ + grub_efi_device_path_t *device_path; + grub_efi_device_path_t *last_device_path; + grub_efi_block_io_t *block_io; +- grub_efi_disk_io_t *disk_io; + struct grub_efidisk_data *next; + }; + +-/* GUIDs. */ +-static grub_efi_guid_t disk_io_guid = GRUB_EFI_DISK_IO_GUID; ++/* GUID. */ + static grub_efi_guid_t block_io_guid = GRUB_EFI_BLOCK_IO_GUID; + + static struct grub_efidisk_data *fd_devices; +@@ -143,7 +141,7 @@ + struct grub_efidisk_data *devices = 0; + + /* Find handles which support the disk io interface. */ +- handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &disk_io_guid, ++ handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &block_io_guid, + 0, &num_handles); + if (! handles) + return 0; +@@ -155,7 +153,6 @@ + grub_efi_device_path_t *ldp; + struct grub_efidisk_data *d; + grub_efi_block_io_t *bio; +- grub_efi_disk_io_t *dio; + + dp = grub_efi_get_device_path (*handle); + if (! dp) +@@ -168,9 +165,7 @@ + + bio = grub_efi_open_protocol (*handle, &block_io_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); +- dio = grub_efi_open_protocol (*handle, &disk_io_guid, +- GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); +- if (! bio || ! dio) ++ if (! bio) + /* This should not happen... Why? */ + continue; + +@@ -186,7 +181,6 @@ + d->device_path = dp; + d->last_device_path = ldp; + d->block_io = bio; +- d->disk_io = dio; + d->next = devices; + devices = d; + } +@@ -536,8 +530,13 @@ + and total sectors should be replaced with total blocks. */ + grub_dprintf ("efidisk", "m = %p, last block = %llx, block size = %x\n", + m, (unsigned long long) m->last_block, m->block_size); +- disk->total_sectors = (m->last_block +- * (m->block_size >> GRUB_DISK_SECTOR_BITS)); ++ disk->total_sectors = m->last_block; ++ if (m->block_size & (m->block_size - 1) || !m->block_size) ++ return grub_error (GRUB_ERR_IO, "invalid sector size %d", ++ m->block_size); ++ for (disk->log_sector_size = 0; ++ (1U << disk->log_sector_size) < m->block_size; ++ disk->log_sector_size++); + disk->data = d; + + grub_dprintf ("efidisk", "opening %s succeeded\n", name); +@@ -558,22 +557,20 @@ + { + /* For now, use the disk io interface rather than the block io's. */ + struct grub_efidisk_data *d; +- grub_efi_disk_io_t *dio; + grub_efi_block_io_t *bio; + grub_efi_status_t status; + + d = disk->data; +- dio = d->disk_io; + bio = d->block_io; + + grub_dprintf ("efidisk", + "reading 0x%lx sectors at the sector 0x%llx from %s\n", + (unsigned long) size, (unsigned long long) sector, disk->name); + +- status = efi_call_5 (dio->read, dio, bio->media->media_id, +- (grub_efi_uint64_t) sector << GRUB_DISK_SECTOR_BITS, +- (grub_efi_uintn_t) size << GRUB_DISK_SECTOR_BITS, +- buf); ++ status = efi_call_5 (bio->read_blocks, bio, bio->media->media_id, ++ (grub_efi_uint64_t) sector, ++ (grub_efi_uintn_t) size << disk->log_sector_size, ++ buf); + if (status != GRUB_EFI_SUCCESS) + return grub_error (GRUB_ERR_READ_ERROR, "efidisk read error"); + +@@ -586,21 +583,19 @@ + { + /* For now, use the disk io interface rather than the block io's. */ + struct grub_efidisk_data *d; +- grub_efi_disk_io_t *dio; + grub_efi_block_io_t *bio; + grub_efi_status_t status; + + d = disk->data; +- dio = d->disk_io; + bio = d->block_io; + + grub_dprintf ("efidisk", + "writing 0x%lx sectors at the sector 0x%llx to %s\n", + (unsigned long) size, (unsigned long long) sector, disk->name); + +- status = efi_call_5 (dio->write, dio, bio->media->media_id, +- (grub_efi_uint64_t) sector << GRUB_DISK_SECTOR_BITS, +- (grub_efi_uintn_t) size << GRUB_DISK_SECTOR_BITS, ++ status = efi_call_5 (bio->write_blocks, bio, bio->media->media_id, ++ (grub_efi_uint64_t) sector, ++ (grub_efi_uintn_t) size << disk->log_sector_size, + (void *) buf); + if (status != GRUB_EFI_SUCCESS) + return grub_error (GRUB_ERR_WRITE_ERROR, "efidisk write error"); +Index: b/grub-core/disk/i386/pc/biosdisk.c +=================================================================== +--- a/grub-core/disk/i386/pc/biosdisk.c ++++ b/grub-core/disk/i386/pc/biosdisk.c +@@ -340,7 +340,8 @@ + if ((cd_drive) && (drive == cd_drive)) + { + data->flags = GRUB_BIOSDISK_FLAG_LBA | GRUB_BIOSDISK_FLAG_CDROM; +- data->sectors = 32; ++ data->sectors = 8; ++ disk->log_sector_size = 11; + /* TODO: get the correct size. */ + total_sectors = GRUB_DISK_SIZE_UNKNOWN; + } +@@ -349,6 +350,8 @@ + /* HDD */ + int version; + ++ disk->log_sector_size = 9; ++ + version = grub_biosdisk_check_int13_extensions (drive); + if (version) + { +@@ -369,6 +372,15 @@ + correctly but returns zero. So if it is zero, compute + it by C/H/S returned by the LBA BIOS call. */ + total_sectors = drp->cylinders * drp->heads * drp->sectors; ++ if (drp->bytes_per_sector ++ && !(drp->bytes_per_sector & (drp->bytes_per_sector - 1)) ++ && drp->bytes_per_sector >= 512 ++ && drp->bytes_per_sector <= 16384) ++ { ++ for (disk->log_sector_size = 0; ++ (1 << disk->log_sector_size) < drp->bytes_per_sector; ++ disk->log_sector_size++); ++ } + } + } + } +@@ -431,7 +443,7 @@ + + dap = (struct grub_biosdisk_dap *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + + (data->sectors +- << GRUB_DISK_SECTOR_BITS)); ++ << disk->log_sector_size)); + dap->length = sizeof (*dap); + dap->reserved = 0; + dap->blocks = size; +@@ -445,9 +457,6 @@ + if (cmd) + return grub_error (GRUB_ERR_WRITE_ERROR, "can\'t write to cdrom"); + +- dap->blocks = ALIGN_UP (dap->blocks, 4) >> 2; +- dap->block >>= 2; +- + for (i = 0; i < GRUB_BIOSDISK_CDROM_RETRY_COUNT; i++) + if (! grub_biosdisk_rw_int13_extensions (0x42, data->drive, dap)) + break; +@@ -503,19 +512,21 @@ + + /* Return the number of sectors which can be read safely at a time. */ + static grub_size_t +-get_safe_sectors (grub_disk_addr_t sector, grub_uint32_t sectors) ++get_safe_sectors (grub_disk_t disk, grub_disk_addr_t sector) + { + grub_size_t size; +- grub_uint32_t offset; ++ grub_uint64_t offset; ++ struct grub_biosdisk_data *data = disk->data; ++ grub_uint32_t sectors = data->sectors; + + /* OFFSET = SECTOR % SECTORS */ +- grub_divmod64 (sector, sectors, &offset); ++ grub_divmod64_full (sector, sectors, &offset); + + size = sectors - offset; + + /* Limit the max to 0x7f because of Phoenix EDD. */ +- if (size > 0x7f) +- size = 0x7f; ++ if (size > ((0x7fU << GRUB_DISK_SECTOR_BITS) >> disk->log_sector_size)) ++ size = ((0x7fU << GRUB_DISK_SECTOR_BITS) >> disk->log_sector_size); + + return size; + } +@@ -524,21 +535,11 @@ + grub_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector, + grub_size_t size, char *buf) + { +- struct grub_biosdisk_data *data = disk->data; +- + while (size) + { + grub_size_t len; +- grub_size_t cdoff = 0; + +- len = get_safe_sectors (sector, data->sectors); +- +- if (data->flags & GRUB_BIOSDISK_FLAG_CDROM) +- { +- cdoff = (sector & 3) << GRUB_DISK_SECTOR_BITS; +- len = ALIGN_UP (sector + len, 4) - (sector & ~3); +- sector &= ~3; +- } ++ len = get_safe_sectors (disk, sector); + + if (len > size) + len = size; +@@ -547,9 +548,10 @@ + GRUB_MEMORY_MACHINE_SCRATCH_SEG)) + return grub_errno; + +- grub_memcpy (buf, (void *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR + cdoff), +- len << GRUB_DISK_SECTOR_BITS); +- buf += len << GRUB_DISK_SECTOR_BITS; ++ grub_memcpy (buf, (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, ++ len << disk->log_sector_size); ++ ++ buf += len << disk->log_sector_size; + sector += len; + size -= len; + } +@@ -570,18 +572,18 @@ + { + grub_size_t len; + +- len = get_safe_sectors (sector, data->sectors); ++ len = get_safe_sectors (disk, sector); + if (len > size) + len = size; + + grub_memcpy ((void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, buf, +- len << GRUB_DISK_SECTOR_BITS); ++ len << disk->log_sector_size); + + if (grub_biosdisk_rw (GRUB_BIOSDISK_WRITE, disk, sector, len, + GRUB_MEMORY_MACHINE_SCRATCH_SEG)) + return grub_errno; + +- buf += len << GRUB_DISK_SECTOR_BITS; ++ buf += len << disk->log_sector_size; + sector += len; + size -= len; + } +Index: b/grub-core/disk/scsi.c +=================================================================== +--- a/grub-core/disk/scsi.c ++++ b/grub-core/disk/scsi.c +@@ -465,15 +465,20 @@ + return err; + } + +- /* SCSI blocks can be something else than 512, although GRUB +- wants 512 byte blocks. */ +- disk->total_sectors = ((grub_uint64_t)scsi->size +- * (grub_uint64_t)scsi->blocksize) +- >> GRUB_DISK_SECTOR_BITS; ++ disk->total_sectors = scsi->size; ++ if (scsi->blocksize & (scsi->blocksize - 1) || !scsi->blocksize) ++ { ++ grub_free (scsi); ++ return grub_error (GRUB_ERR_IO, "invalid sector size %d", ++ scsi->blocksize); ++ } ++ for (disk->log_sector_size = 0; ++ (1 << disk->log_sector_size) < scsi->blocksize; ++ disk->log_sector_size++); + + grub_dprintf ("scsi", "blocks=%u, blocksize=%u\n", + scsi->size, scsi->blocksize); +- grub_dprintf ("scsi", "Disk total 512 sectors = %llu\n", ++ grub_dprintf ("scsi", "Disk total sectors = %llu\n", + (unsigned long long) disk->total_sectors); + + return GRUB_ERR_NONE; +@@ -503,25 +508,6 @@ + + scsi = disk->data; + +- /* SCSI sectors are variable in size. GRUB uses 512 byte +- sectors. */ +- if (scsi->blocksize != GRUB_DISK_SECTOR_SIZE) +- { +- unsigned spb = scsi->blocksize >> GRUB_DISK_SECTOR_BITS; +- if (spb == 0 || (scsi->blocksize & (GRUB_DISK_SECTOR_SIZE - 1)) != 0) +- return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, +- "unsupported SCSI block size"); +- +- grub_uint32_t sector_mod = 0; +- sector = grub_divmod64 (sector, spb, §or_mod); +- +- if (! (sector_mod == 0 && size % spb == 0)) +- return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, +- "unaligned SCSI read not supported"); +- +- size /= spb; +- } +- + /* Depending on the type, select a read function. */ + switch (scsi->devtype) + { +Index: b/grub-core/kern/disk.c +=================================================================== +--- a/grub-core/kern/disk.c ++++ b/grub-core/kern/disk.c +@@ -247,6 +247,7 @@ + disk = (grub_disk_t) grub_zalloc (sizeof (*disk)); + if (! disk) + return 0; ++ disk->log_sector_size = GRUB_DISK_SECTOR_BITS; + + p = find_part_sep (name); + if (p) +@@ -266,7 +267,6 @@ + if (! disk->name) + goto fail; + +- + for (dev = grub_disk_dev_list; dev; dev = dev->next) + { + if ((dev->open) (raw, disk) == GRUB_ERR_NONE) +@@ -282,6 +282,14 @@ + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such disk"); + goto fail; + } ++ if (disk->log_sector_size > GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS ++ || disk->log_sector_size < GRUB_DISK_SECTOR_BITS) ++ { ++ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, ++ "sector sizes of %d bytes aren't supported yet", ++ (1 << disk->log_sector_size)); ++ goto fail; ++ } + + disk->dev = dev; + +@@ -373,21 +381,113 @@ + *sector += start; + } + +- if (disk->total_sectors <= *sector +- || ((*offset + size + GRUB_DISK_SECTOR_SIZE - 1) +- >> GRUB_DISK_SECTOR_BITS) > disk->total_sectors - *sector) ++ if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN ++ && ((disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)) <= *sector ++ || ((*offset + size + GRUB_DISK_SECTOR_SIZE - 1) ++ >> GRUB_DISK_SECTOR_BITS) > (disk->total_sectors ++ << (disk->log_sector_size ++ - GRUB_DISK_SECTOR_BITS)) - *sector)) + return grub_error (GRUB_ERR_OUT_OF_RANGE, "out of disk"); + + return GRUB_ERR_NONE; + } + ++static inline grub_disk_addr_t ++transform_sector (grub_disk_t disk, grub_disk_addr_t sector) ++{ ++ 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_free (tmp_buf); ++ 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 (); ++ grub_free (tmp_buf); ++ return grub_errno; ++ } ++ grub_memcpy (buf, tmp_buf + offset, size); ++ grub_free (tmp_buf); ++ 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) +@@ -399,126 +499,126 @@ + 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 ++ ++ if (data) + { +- /* Otherwise read data from the disk actually. */ +- if (start_sector + GRUB_DISK_CACHE_SIZE > disk->total_sectors +- || (disk->dev->read) (disk, start_sector, +- GRUB_DISK_CACHE_SIZE, tmp_buf) +- != GRUB_ERR_NONE) +- { +- /* Uggh... Failed. Instead, just read necessary data. */ +- unsigned num; +- char *p; +- +- grub_errno = GRUB_ERR_NONE; +- +- num = ((size + real_offset + GRUB_DISK_SECTOR_SIZE - 1) +- >> GRUB_DISK_SECTOR_BITS); +- +- p = grub_realloc (tmp_buf, num << GRUB_DISK_SECTOR_BITS); +- if (!p) +- goto finish; +- +- tmp_buf = p; +- +- if ((disk->dev->read) (disk, 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; +- } ++ grub_memcpy ((char *) buf ++ + (agglomerate << (GRUB_DISK_CACHE_BITS ++ + GRUB_DISK_SECTOR_BITS)), ++ data, GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS); ++ grub_disk_cache_unlock (disk->dev->id, disk->id, ++ sector + (agglomerate ++ << GRUB_DISK_CACHE_BITS)); ++ } + +- /* This must be the end. */ +- goto finish; +- } ++ if (agglomerate) ++ { ++ grub_disk_addr_t i; + +- /* 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); ++ 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; +- } ++ 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); + } ++ } + +- 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) ++ { ++ grub_size_t cl; ++ cl = GRUB_DISK_SECTOR_SIZE - o; ++ if (cl > l) ++ cl = l; ++ (disk->read_hook) (s, o, cl); ++ s++; ++ l -= cl; ++ o = 0; ++ } ++ } + + return grub_errno; + } +@@ -528,25 +628,31 @@ + grub_off_t offset, grub_size_t size, const void *buf) + { + unsigned real_offset; ++ grub_disk_addr_t aligned_sector; + + grub_dprintf ("disk", "Writing `%s'...\n", disk->name); + + if (grub_disk_adjust_range (disk, §or, &offset, size) != GRUB_ERR_NONE) + return -1; + +- real_offset = offset; ++ aligned_sector = (sector & ~((1 << (disk->log_sector_size ++ - GRUB_DISK_SECTOR_BITS)) - 1)); ++ real_offset = offset + ((sector - aligned_sector) << GRUB_DISK_SECTOR_BITS); ++ sector = aligned_sector; + + while (size) + { +- if (real_offset != 0 || (size < GRUB_DISK_SECTOR_SIZE && size != 0)) ++ if (real_offset != 0 || (size < (1U << disk->log_sector_size) ++ && size != 0)) + { +- char tmp_buf[GRUB_DISK_SECTOR_SIZE]; ++ char tmp_buf[1 << disk->log_sector_size]; + grub_size_t len; + grub_partition_t part; + + part = disk->partition; + disk->partition = 0; +- if (grub_disk_read (disk, sector, 0, GRUB_DISK_SECTOR_SIZE, tmp_buf) ++ if (grub_disk_read (disk, sector, ++ 0, (1 << disk->log_sector_size), tmp_buf) + != GRUB_ERR_NONE) + { + disk->partition = part; +@@ -554,7 +660,7 @@ + } + disk->partition = part; + +- len = GRUB_DISK_SECTOR_SIZE - real_offset; ++ len = (1 << disk->log_sector_size) - real_offset; + if (len > size) + len = size; + +@@ -565,7 +671,7 @@ + if ((disk->dev->write) (disk, sector, 1, tmp_buf) != GRUB_ERR_NONE) + goto finish; + +- sector++; ++ sector += (1 << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); + buf = (char *) buf + len; + size -= len; + real_offset = 0; +@@ -575,8 +681,8 @@ + grub_size_t len; + grub_size_t n; + +- len = size & ~(GRUB_DISK_SECTOR_SIZE - 1); +- n = size >> GRUB_DISK_SECTOR_BITS; ++ len = size & ~((1 << disk->log_sector_size) - 1); ++ n = size >> disk->log_sector_size; + + if ((disk->dev->write) (disk, sector, n, buf) != GRUB_ERR_NONE) + goto finish; +@@ -599,6 +705,8 @@ + { + if (disk->partition) + return grub_partition_get_len (disk->partition); ++ else if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN) ++ return disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS); + else +- return disk->total_sectors; ++ return GRUB_DISK_SIZE_UNKNOWN; + } +Index: b/grub-core/kern/emu/hostdisk.c +=================================================================== +--- a/grub-core/kern/emu/hostdisk.c ++++ b/grub-core/kern/emu/hostdisk.c +@@ -43,6 +43,7 @@ + + #ifdef __linux__ + # include <sys/ioctl.h> /* ioctl */ ++# include <sys/mount.h> + # if !defined(__GLIBC__) || \ + ((__GLIBC__ < 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 1))) + /* Maybe libc doesn't have large file support. */ +@@ -267,6 +268,7 @@ + # else + unsigned long long nr; + # endif ++ int sector_size; + int fd; + + fd = open (map[drive].device, O_RDONLY); +@@ -299,16 +301,32 @@ + goto fail; + } + ++# if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) ++ if (ioctl (fd, DIOCGSECTORSIZE, §or_size)) ++# else ++ if (ioctl (fd, BLKSSZGET, §or_size)) ++# endif ++ { ++ close (fd); ++ goto fail; ++ } ++ + close (fd); + ++ if (sector_size & (sector_size - 1) || !sector_size) ++ goto fail; ++ for (disk->log_sector_size = 0; ++ (1 << disk->log_sector_size) < sector_size; ++ disk->log_sector_size++); ++ + # if defined (__APPLE__) + disk->total_sectors = nr; + # elif defined(__NetBSD__) + disk->total_sectors = label.d_secperunit; + # else +- disk->total_sectors = nr / 512; ++ disk->total_sectors = nr >> disk->log_sector_size; + +- if (nr % 512) ++ if (nr & ((1 << disk->log_sector_size) - 1)) + grub_util_error ("unaligned device size"); + # endif + +@@ -325,7 +343,7 @@ + if (stat (map[drive].device, &st) < 0) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "cannot stat `%s'", map[drive].device); + +- disk->total_sectors = st.st_size >> GRUB_DISK_SECTOR_BITS; ++ disk->total_sectors = st.st_size >> disk->log_sector_size; + + grub_util_info ("the size of %s is %lu", name, disk->total_sectors); + +@@ -798,7 +816,7 @@ + _syscall5 (int, _llseek, uint, filedes, ulong, hi, ulong, lo, + loff_t *, res, uint, wh); + +- offset = (loff_t) sector << GRUB_DISK_SECTOR_BITS; ++ offset = (loff_t) sector << disk->log_sector_size; + if (_llseek (fd, offset >> 32, offset & 0xffffffff, &result, SEEK_SET)) + { + grub_error (GRUB_ERR_BAD_DEVICE, "cannot seek `%s'", map[disk->id].device); +@@ -808,7 +826,7 @@ + } + #else + { +- off_t offset = (off_t) sector << GRUB_DISK_SECTOR_BITS; ++ off_t offset = (off_t) sector << disk->log_sector_size; + + if (lseek (fd, offset, SEEK_SET) != offset) + { +@@ -908,19 +926,20 @@ + sectors that are read together with the MBR in one read. It + should only remap the MBR, so we split the read in two + parts. -jochen */ +- if (nread (fd, buf, GRUB_DISK_SECTOR_SIZE) != GRUB_DISK_SECTOR_SIZE) ++ if (nread (fd, buf, (1 << disk->log_sector_size)) ++ != (1 << disk->log_sector_size)) + { + grub_error (GRUB_ERR_READ_ERROR, "cannot read `%s'", map[disk->id].device); + return grub_errno; + } + +- buf += GRUB_DISK_SECTOR_SIZE; ++ buf += (1 << disk->log_sector_size); + size--; + } + #endif /* __linux__ */ + +- if (nread (fd, buf, size << GRUB_DISK_SECTOR_BITS) +- != (ssize_t) (size << GRUB_DISK_SECTOR_BITS)) ++ if (nread (fd, buf, size << disk->log_sector_size) ++ != (ssize_t) (size << disk->log_sector_size)) + grub_error (GRUB_ERR_READ_ERROR, "cannot read from `%s'", map[disk->id].device); + + return grub_errno; +@@ -953,8 +972,8 @@ + if (fd < 0) + return grub_errno; + +- if (nwrite (fd, buf, size << GRUB_DISK_SECTOR_BITS) +- != (ssize_t) (size << GRUB_DISK_SECTOR_BITS)) ++ if (nwrite (fd, buf, size << disk->log_sector_size) ++ != (ssize_t) (size << disk->log_sector_size)) + grub_error (GRUB_ERR_WRITE_ERROR, "cannot write to `%s'", map[disk->id].device); + + return grub_errno; +Index: b/grub-core/partmap/msdos.c +=================================================================== +--- a/grub-core/partmap/msdos.c ++++ b/grub-core/partmap/msdos.c +@@ -152,8 +152,11 @@ + { + e = mbr.entries + p.index; + +- p.start = p.offset + grub_le_to_cpu32 (e->start) - delta; +- p.len = grub_le_to_cpu32 (e->length); ++ p.start = p.offset ++ + (grub_le_to_cpu32 (e->start) ++ << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)) - delta; ++ p.len = grub_le_to_cpu32 (e->length) ++ << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS); + p.msdostype = e->type; + + grub_dprintf ("partition", +@@ -188,7 +191,9 @@ + + if (grub_msdos_partition_is_extended (e->type)) + { +- p.offset = ext_offset + grub_le_to_cpu32 (e->start); ++ p.offset = ext_offset ++ + (grub_le_to_cpu32 (e->start) ++ << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); + if (! ext_offset) + ext_offset = p.offset; + +@@ -267,8 +272,11 @@ + e = mbr.entries + i; + + if (!grub_msdos_partition_is_empty (e->type) +- && end > offset + grub_le_to_cpu32 (e->start)) +- end = offset + grub_le_to_cpu32 (e->start); ++ && end > offset ++ + (grub_le_to_cpu32 (e->start) ++ << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS))) ++ end = offset + (grub_le_to_cpu32 (e->start) ++ << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); + + /* If this is a GPT partition, this MBR is just a dummy. */ + if (e->type == GRUB_PC_PARTITION_TYPE_GPT_DISK && i == 0) +@@ -282,7 +290,9 @@ + + if (grub_msdos_partition_is_extended (e->type)) + { +- offset = ext_offset + grub_le_to_cpu32 (e->start); ++ offset = ext_offset ++ + (grub_le_to_cpu32 (e->start) ++ << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)); + if (! ext_offset) + ext_offset = offset; + +Index: b/include/grub/disk.h +=================================================================== +--- a/include/grub/disk.h ++++ b/include/grub/disk.h +@@ -100,6 +100,9 @@ + /* The total number of sectors. */ + grub_uint64_t total_sectors; + ++ /* Logarithm of sector size. */ ++ unsigned int log_sector_size; ++ + /* The id used by the disk cache manager. */ + unsigned long id; + +@@ -132,9 +135,10 @@ + /* The maximum number of disk caches. */ + #define GRUB_DISK_CACHE_NUM 1021 + +-/* The size of a disk cache in sector units. */ +-#define GRUB_DISK_CACHE_SIZE 8 +-#define GRUB_DISK_CACHE_BITS 3 ++/* The size of a disk cache in 512B units. Must be at least as big as the ++ largest supported sector size, currently 16K. */ ++#define GRUB_DISK_CACHE_BITS 6 ++#define GRUB_DISK_CACHE_SIZE (1 << GRUB_DISK_CACHE_BITS) + + /* Return value of grub_disk_get_size() in case disk size is unknown. */ + #define GRUB_DISK_SIZE_UNKNOWN 0xffffffffffffffffULL +Index: b/util/grub-fstest.c +=================================================================== +--- a/util/grub-fstest.c ++++ b/util/grub-fstest.c +@@ -60,6 +60,7 @@ + #define CMD_HEX 4 + #define CMD_CRC 6 + #define CMD_BLOCKLIST 7 ++#define CMD_TESTLOAD 8 + + #define BUF_SIZE 32256 + +@@ -333,6 +334,9 @@ + case CMD_BLOCKLIST: + execute_command ("blocklist", n, args); + grub_printf ("\n"); ++ case CMD_TESTLOAD: ++ execute_command ("testload", n, args); ++ grub_printf ("\n"); + } + + for (i = 0; i < num_disks; i++) +@@ -488,6 +492,11 @@ + cmd = CMD_BLOCKLIST; + nparm = 1; + } ++ else if (!grub_strcmp (arg, "testload")) ++ { ++ cmd = CMD_TESTLOAD; ++ nparm = 1; ++ } + else + { + fprintf (stderr, _("Invalid command %s.\n"), arg); |