On 15.04.2011 10:18, Seth Goldberg wrote:
> This problem is present, at least, on ZFS.
Could you test the attached patch then?

-- 
Regards
Vladimir 'φ-coder/phcoder' Serbinenko

=== modified file 'grub-core/disk/efi/efidisk.c'
--- grub-core/disk/efi/efidisk.c	2010-09-21 12:41:23 +0000
+++ grub-core/disk/efi/efidisk.c	2011-04-15 16:15:06 +0000
@@ -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");

=== modified file 'grub-core/disk/i386/pc/biosdisk.c'
--- grub-core/disk/i386/pc/biosdisk.c	2011-01-04 14:42:47 +0000
+++ grub-core/disk/i386/pc/biosdisk.c	2011-03-29 00:02:55 +0000
@@ -338,7 +338,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;
     }
@@ -347,6 +348,8 @@
       /* HDD */
       int version;
 
+      disk->log_sector_size = 9;
+
       version = grub_biosdisk_check_int13_extensions (drive);
       if (version)
 	{
@@ -367,6 +370,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++);
+		}
 	    }
 	}
     }
@@ -429,7 +441,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;
@@ -443,9 +455,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;
@@ -501,10 +510,12 @@
 
 /* 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;
+  struct grub_biosdisk_data *data = disk->data;
+  grub_uint32_t sectors = data->sectors;
 
   /* OFFSET = SECTOR % SECTORS */
   grub_divmod64 (sector, sectors, &offset);
@@ -512,8 +523,8 @@
   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;
 }
@@ -522,21 +533,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;
@@ -545,9 +546,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;
     }
@@ -568,18 +570,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;
     }

=== modified file 'grub-core/disk/scsi.c'
--- grub-core/disk/scsi.c	2011-01-22 12:22:46 +0000
+++ grub-core/disk/scsi.c	2011-03-29 00:02:55 +0000
@@ -463,15 +463,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;
@@ -501,25 +506,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)
     {

=== modified file 'grub-core/kern/disk.c'
--- grub-core/kern/disk.c	2010-09-20 19:45:06 +0000
+++ grub-core/kern/disk.c	2011-04-15 19:42:29 +0000
@@ -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,110 @@
       *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_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, &sector, &offset, size) != GRUB_ERR_NONE)
@@ -399,126 +496,118 @@
       return grub_errno;
     }
 
+  real_sector = sector;
   real_offset = offset;
-
-  /* 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)
+  real_size = 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 pos;
+      grub_err_t err;
       grub_size_t len;
-      grub_size_t pos;
 
-      /* 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;
-
-      /* Fetch the cache.  */
-      data = grub_disk_cache_fetch (disk->dev->id, disk->id, start_sector);
-      if (data)
-	{
-	  /* Just copy it!  */
-	  grub_memcpy (buf, data + pos + real_offset, len);
-	  grub_disk_cache_unlock (disk->dev->id, disk->id, start_sector);
-	}
-      else
-	{
-	  /* 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;
-		  }
-
-	      /* 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);
-	}
-
-      /* Call the read hook, if any.  */
-      if (disk->read_hook)
-	{
-	  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 = start_sector + GRUB_DISK_CACHE_SIZE;
+      err = grub_disk_read_small (disk, start_sector,
+				  offset + pos, len, buf);
+      if (err)
+	return err;
       buf = (char *) buf + len;
       size -= len;
-      real_offset = 0;
-    }
-
- finish:
-
-  grub_free (tmp_buf);
+      offset += len;
+      sector += (offset >> GRUB_DISK_SECTOR_BITS);
+      offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
+    }
+
+  /* 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++)
+	{
+	  data = grub_disk_cache_fetch (disk->dev->id, disk->id,
+					sector + (agglomerate
+						  << GRUB_DISK_CACHE_BITS));
+	  if (data)
+	    break;
+	}
+
+      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));
+	}
+	
+      if (data)
+	{
+	  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));
+	}
+    }
+
+  /* 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;
+    }
+
+  /* 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;
+
+      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;
 }
@@ -528,25 +617,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 +649,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 +660,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 +670,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 +694,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;
 }

=== modified file 'grub-core/kern/emu/hostdisk.c'
--- grub-core/kern/emu/hostdisk.c	2011-03-26 11:59:02 +0000
+++ grub-core/kern/emu/hostdisk.c	2011-03-29 00:02:55 +0000
@@ -42,6 +42,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.  */
@@ -264,6 +265,7 @@
 # else
     unsigned long long nr;
 # endif
+    int sector_size;
     int fd;
 
     fd = open (map[drive].device, O_RDONLY);
@@ -295,16 +297,28 @@
 	goto fail;
       }
 
+    if (ioctl (fd, BLKSSZGET, &sector_size))
+       {
+         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
 
@@ -321,7 +335,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);
 
@@ -760,7 +774,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);
@@ -770,7 +784,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)
       {
@@ -870,20 +884,21 @@
 	 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);
 	  close (fd);
 	  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;
@@ -916,8 +931,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;

=== modified file 'grub-core/partmap/msdos.c'
--- grub-core/partmap/msdos.c	2011-02-12 06:59:04 +0000
+++ grub-core/partmap/msdos.c	2011-03-29 00:02:55 +0000
@@ -90,8 +90,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",
@@ -126,7 +129,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;
 
@@ -204,8 +209,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)
@@ -219,7 +227,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;
 

=== modified file 'include/grub/disk.h'
--- include/grub/disk.h	2010-10-08 21:27:27 +0000
+++ include/grub/disk.h	2011-03-29 00:02:55 +0000
@@ -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

_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to