summaryrefslogtreecommitdiffstats
path: root/master/debian/4k_sectors.patch
diff options
context:
space:
mode:
Diffstat (limited to 'master/debian/4k_sectors.patch')
-rw-r--r--master/debian/4k_sectors.patch1029
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, &sector_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, &sector, &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, &sector, &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, &sector_size))
++# else
++ if (ioctl (fd, BLKSSZGET, &sector_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);