]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
contiguous read
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Fri, 15 Apr 2011 19:42:29 +0000 (21:42 +0200)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Fri, 15 Apr 2011 19:42:29 +0000 (21:42 +0200)
grub-core/kern/disk.c

index af56527adbcc492ca55e1f719872b8fdfff6d8f7..5439a02f6a77747a2bb1b4bdd3e52ae23218ac86 100644 (file)
@@ -398,13 +398,93 @@ 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)
@@ -416,138 +496,118 @@ grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
       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;
 }