2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2002,2003,2004,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
19 #include <grub/disk.h>
22 #include <grub/types.h>
23 #include <grub/partition.h>
24 #include <grub/misc.h>
25 #include <grub/time.h>
26 #include <grub/file.h>
27 #include <grub/i18n.h>
29 #define GRUB_CACHE_TIMEOUT 2
31 /* The last time the disk was used. */
32 static grub_uint64_t grub_last_time
= 0;
36 struct grub_disk_cache
38 enum grub_disk_dev_id dev_id
;
39 unsigned long disk_id
;
40 grub_disk_addr_t sector
;
45 static struct grub_disk_cache grub_disk_cache_table
[GRUB_DISK_CACHE_NUM
];
47 void (*grub_disk_firmware_fini
) (void);
48 int grub_disk_firmware_is_tainted
;
51 static unsigned long grub_disk_cache_hits
;
52 static unsigned long grub_disk_cache_misses
;
55 grub_disk_cache_get_performance (unsigned long *hits
, unsigned long *misses
)
57 *hits
= grub_disk_cache_hits
;
58 *misses
= grub_disk_cache_misses
;
63 grub_disk_cache_get_index (unsigned long dev_id
, unsigned long disk_id
,
64 grub_disk_addr_t sector
)
66 return ((dev_id
* 524287UL + disk_id
* 2606459UL
67 + ((unsigned) (sector
>> GRUB_DISK_CACHE_BITS
)))
68 % GRUB_DISK_CACHE_NUM
);
72 grub_disk_cache_invalidate (unsigned long dev_id
, unsigned long disk_id
,
73 grub_disk_addr_t sector
)
76 struct grub_disk_cache
*cache
;
78 sector
&= ~(GRUB_DISK_CACHE_SIZE
- 1);
79 index
= grub_disk_cache_get_index (dev_id
, disk_id
, sector
);
80 cache
= grub_disk_cache_table
+ index
;
82 if (cache
->dev_id
== dev_id
&& cache
->disk_id
== disk_id
83 && cache
->sector
== sector
&& cache
->data
)
86 grub_free (cache
->data
);
93 grub_disk_cache_invalidate_all (void)
97 for (i
= 0; i
< GRUB_DISK_CACHE_NUM
; i
++)
99 struct grub_disk_cache
*cache
= grub_disk_cache_table
+ i
;
101 if (cache
->data
&& ! cache
->lock
)
103 grub_free (cache
->data
);
110 grub_disk_cache_fetch (unsigned long dev_id
, unsigned long disk_id
,
111 grub_disk_addr_t sector
)
113 struct grub_disk_cache
*cache
;
116 index
= grub_disk_cache_get_index (dev_id
, disk_id
, sector
);
117 cache
= grub_disk_cache_table
+ index
;
119 if (cache
->dev_id
== dev_id
&& cache
->disk_id
== disk_id
120 && cache
->sector
== sector
)
124 grub_disk_cache_hits
++;
130 grub_disk_cache_misses
++;
137 grub_disk_cache_unlock (unsigned long dev_id
, unsigned long disk_id
,
138 grub_disk_addr_t sector
)
140 struct grub_disk_cache
*cache
;
143 index
= grub_disk_cache_get_index (dev_id
, disk_id
, sector
);
144 cache
= grub_disk_cache_table
+ index
;
146 if (cache
->dev_id
== dev_id
&& cache
->disk_id
== disk_id
147 && cache
->sector
== sector
)
152 grub_disk_cache_store (unsigned long dev_id
, unsigned long disk_id
,
153 grub_disk_addr_t sector
, const char *data
)
156 struct grub_disk_cache
*cache
;
158 index
= grub_disk_cache_get_index (dev_id
, disk_id
, sector
);
159 cache
= grub_disk_cache_table
+ index
;
162 grub_free (cache
->data
);
166 cache
->data
= grub_malloc (GRUB_DISK_SECTOR_SIZE
<< GRUB_DISK_CACHE_BITS
);
170 grub_memcpy (cache
->data
, data
,
171 GRUB_DISK_SECTOR_SIZE
<< GRUB_DISK_CACHE_BITS
);
172 cache
->dev_id
= dev_id
;
173 cache
->disk_id
= disk_id
;
174 cache
->sector
= sector
;
176 return GRUB_ERR_NONE
;
181 grub_disk_dev_t grub_disk_dev_list
;
184 grub_disk_dev_register (grub_disk_dev_t dev
)
186 dev
->next
= grub_disk_dev_list
;
187 grub_disk_dev_list
= dev
;
191 grub_disk_dev_unregister (grub_disk_dev_t dev
)
193 grub_disk_dev_t
*p
, q
;
195 for (p
= &grub_disk_dev_list
, q
= *p
; q
; p
= &(q
->next
), q
= q
->next
)
203 /* Return the location of the first ',', if any, which is not
206 find_part_sep (const char *name
)
208 const char *p
= name
;
211 while ((c
= *p
++) != '\0')
213 if (c
== '\\' && *p
== ',')
222 grub_disk_open (const char *name
)
227 char *raw
= (char *) name
;
228 grub_uint64_t current_time
;
230 grub_dprintf ("disk", "Opening `%s'...\n", name
);
232 disk
= (grub_disk_t
) grub_zalloc (sizeof (*disk
));
235 disk
->log_sector_size
= GRUB_DISK_SECTOR_BITS
;
237 p
= find_part_sep (name
);
240 grub_size_t len
= p
- name
;
242 raw
= grub_malloc (len
+ 1);
246 grub_memcpy (raw
, name
, len
);
248 disk
->name
= grub_strdup (raw
);
251 disk
->name
= grub_strdup (name
);
255 for (dev
= grub_disk_dev_list
; dev
; dev
= dev
->next
)
257 if ((dev
->open
) (raw
, disk
) == GRUB_ERR_NONE
)
259 else if (grub_errno
== GRUB_ERR_UNKNOWN_DEVICE
)
260 grub_errno
= GRUB_ERR_NONE
;
267 grub_error (GRUB_ERR_UNKNOWN_DEVICE
, N_("disk `%s' not found"),
271 if (disk
->log_sector_size
> GRUB_DISK_CACHE_BITS
+ GRUB_DISK_SECTOR_BITS
272 || disk
->log_sector_size
< GRUB_DISK_SECTOR_BITS
)
274 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET
,
275 "sector sizes of %d bytes aren't supported yet",
276 (1 << disk
->log_sector_size
));
284 disk
->partition
= grub_partition_probe (disk
, p
+ 1);
285 if (! disk
->partition
)
287 /* TRANSLATORS: It means that the specified partition e.g.
288 hd0,msdos1=/dev/sda1 doesn't exist. */
289 grub_error (GRUB_ERR_UNKNOWN_DEVICE
, N_("no such partition"));
294 /* The cache will be invalidated about 2 seconds after a device was
296 current_time
= grub_get_time_ms ();
298 if (current_time
> (grub_last_time
299 + GRUB_CACHE_TIMEOUT
* 1000))
300 grub_disk_cache_invalidate_all ();
302 grub_last_time
= current_time
;
306 if (raw
&& raw
!= name
)
309 if (grub_errno
!= GRUB_ERR_NONE
)
312 grub_dprintf ("disk", "Opening `%s' failed.\n", name
);
315 grub_disk_close (disk
);
323 grub_disk_close (grub_disk_t disk
)
325 grub_partition_t part
;
326 grub_dprintf ("disk", "Closing `%s'.\n", disk
->name
);
328 if (disk
->dev
&& disk
->dev
->close
)
329 (disk
->dev
->close
) (disk
);
331 /* Reset the timer. */
332 grub_last_time
= grub_get_time_ms ();
334 while (disk
->partition
)
336 part
= disk
->partition
->parent
;
337 grub_free (disk
->partition
);
338 disk
->partition
= part
;
340 grub_free ((void *) disk
->name
);
344 /* This function performs three tasks:
345 - Make sectors disk relative from partition relative.
346 - Normalize offset to be less than the sector size.
347 - Verify that the range is inside the partition. */
349 grub_disk_adjust_range (grub_disk_t disk
, grub_disk_addr_t
*sector
,
350 grub_off_t
*offset
, grub_size_t size
)
352 grub_partition_t part
;
353 *sector
+= *offset
>> GRUB_DISK_SECTOR_BITS
;
354 *offset
&= GRUB_DISK_SECTOR_SIZE
- 1;
356 for (part
= disk
->partition
; part
; part
= part
->parent
)
358 grub_disk_addr_t start
;
365 || len
- *sector
< ((*offset
+ size
+ GRUB_DISK_SECTOR_SIZE
- 1)
366 >> GRUB_DISK_SECTOR_BITS
))
367 return grub_error (GRUB_ERR_OUT_OF_RANGE
,
368 N_("attempt to read or write outside of partition"));
373 if (disk
->total_sectors
!= GRUB_DISK_SIZE_UNKNOWN
374 && ((disk
->total_sectors
<< (disk
->log_sector_size
- GRUB_DISK_SECTOR_BITS
)) <= *sector
375 || ((*offset
+ size
+ GRUB_DISK_SECTOR_SIZE
- 1)
376 >> GRUB_DISK_SECTOR_BITS
) > (disk
->total_sectors
377 << (disk
->log_sector_size
378 - GRUB_DISK_SECTOR_BITS
)) - *sector
))
379 return grub_error (GRUB_ERR_OUT_OF_RANGE
,
380 N_("attempt to read or write outside of disk `%s'"), disk
->name
);
382 return GRUB_ERR_NONE
;
385 static inline grub_disk_addr_t
386 transform_sector (grub_disk_t disk
, grub_disk_addr_t sector
)
388 return sector
>> (disk
->log_sector_size
- GRUB_DISK_SECTOR_BITS
);
391 /* Small read (less than cache size and not pass across cache unit boundaries).
392 sector is already adjusted and is divisible by cache unit size.
395 grub_disk_read_small (grub_disk_t disk
, grub_disk_addr_t sector
,
396 grub_off_t offset
, grub_size_t size
, void *buf
)
401 /* Fetch the cache. */
402 data
= grub_disk_cache_fetch (disk
->dev
->id
, disk
->id
, sector
);
406 grub_memcpy (buf
, data
+ offset
, size
);
407 grub_disk_cache_unlock (disk
->dev
->id
, disk
->id
, sector
);
408 return GRUB_ERR_NONE
;
411 /* Allocate a temporary buffer. */
412 tmp_buf
= grub_malloc (GRUB_DISK_SECTOR_SIZE
<< GRUB_DISK_CACHE_BITS
);
416 /* Otherwise read data from the disk actually. */
417 if (disk
->total_sectors
== GRUB_DISK_SIZE_UNKNOWN
418 || sector
+ GRUB_DISK_CACHE_SIZE
419 < (disk
->total_sectors
<< (disk
->log_sector_size
- GRUB_DISK_SECTOR_BITS
)))
422 err
= (disk
->dev
->read
) (disk
, transform_sector (disk
, sector
),
423 1 << (GRUB_DISK_CACHE_BITS
424 + GRUB_DISK_SECTOR_BITS
425 - disk
->log_sector_size
), tmp_buf
);
428 /* Copy it and store it in the disk cache. */
429 grub_memcpy (buf
, tmp_buf
+ offset
, size
);
430 grub_disk_cache_store (disk
->dev
->id
, disk
->id
,
433 return GRUB_ERR_NONE
;
438 grub_errno
= GRUB_ERR_NONE
;
441 /* Uggh... Failed. Instead, just read necessary data. */
443 grub_disk_addr_t aligned_sector
;
445 sector
+= (offset
>> GRUB_DISK_SECTOR_BITS
);
446 offset
&= ((1 << GRUB_DISK_SECTOR_BITS
) - 1);
447 aligned_sector
= (sector
& ~((1 << (disk
->log_sector_size
448 - GRUB_DISK_SECTOR_BITS
))
450 offset
+= ((sector
- aligned_sector
) << GRUB_DISK_SECTOR_BITS
);
451 num
= ((size
+ offset
+ (1 << (disk
->log_sector_size
))
452 - 1) >> (disk
->log_sector_size
));
454 tmp_buf
= grub_malloc (num
<< disk
->log_sector_size
);
458 if ((disk
->dev
->read
) (disk
, transform_sector (disk
, aligned_sector
),
462 grub_dprintf ("disk", "%s read failed\n", disk
->name
);
467 grub_memcpy (buf
, tmp_buf
+ offset
, size
);
469 return GRUB_ERR_NONE
;
473 /* Read data from the disk. */
475 grub_disk_read (grub_disk_t disk
, grub_disk_addr_t sector
,
476 grub_off_t offset
, grub_size_t size
, void *buf
)
478 grub_off_t real_offset
;
479 grub_disk_addr_t real_sector
;
480 grub_size_t real_size
;
482 /* First of all, check if the region is within the disk. */
483 if (grub_disk_adjust_range (disk
, §or
, &offset
, size
) != GRUB_ERR_NONE
)
486 grub_dprintf ("disk", "Read out of range: sector 0x%llx (%s).\n",
487 (unsigned long long) sector
, grub_errmsg
);
492 real_sector
= sector
;
493 real_offset
= offset
;
496 /* First read until first cache boundary. */
497 if (offset
|| (sector
& (GRUB_DISK_CACHE_SIZE
- 1)))
499 grub_disk_addr_t start_sector
;
504 start_sector
= sector
& ~(GRUB_DISK_CACHE_SIZE
- 1);
505 pos
= (sector
- start_sector
) << GRUB_DISK_SECTOR_BITS
;
506 len
= ((GRUB_DISK_SECTOR_SIZE
<< GRUB_DISK_CACHE_BITS
)
510 err
= grub_disk_read_small (disk
, start_sector
,
511 offset
+ pos
, len
, buf
);
514 buf
= (char *) buf
+ len
;
517 sector
+= (offset
>> GRUB_DISK_SECTOR_BITS
);
518 offset
&= ((1 << GRUB_DISK_SECTOR_BITS
) - 1);
521 /* Until SIZE is zero... */
522 while (size
>= (GRUB_DISK_CACHE_SIZE
<< GRUB_DISK_SECTOR_BITS
))
525 grub_disk_addr_t agglomerate
;
528 /* agglomerate read until we find a first cached entry. */
529 for (agglomerate
= 0; agglomerate
530 < (size
>> (GRUB_DISK_SECTOR_BITS
+ GRUB_DISK_CACHE_BITS
));
533 data
= grub_disk_cache_fetch (disk
->dev
->id
, disk
->id
,
534 sector
+ (agglomerate
535 << GRUB_DISK_CACHE_BITS
));
542 grub_memcpy ((char *) buf
543 + (agglomerate
<< (GRUB_DISK_CACHE_BITS
544 + GRUB_DISK_SECTOR_BITS
)),
545 data
, GRUB_DISK_CACHE_SIZE
<< GRUB_DISK_SECTOR_BITS
);
546 grub_disk_cache_unlock (disk
->dev
->id
, disk
->id
,
547 sector
+ (agglomerate
548 << GRUB_DISK_CACHE_BITS
));
555 err
= (disk
->dev
->read
) (disk
, transform_sector (disk
, sector
),
556 agglomerate
<< (GRUB_DISK_CACHE_BITS
557 + GRUB_DISK_SECTOR_BITS
558 - disk
->log_sector_size
),
563 for (i
= 0; i
< agglomerate
; i
++)
564 grub_disk_cache_store (disk
->dev
->id
, disk
->id
,
565 sector
+ (i
<< GRUB_DISK_CACHE_BITS
),
567 + (i
<< (GRUB_DISK_CACHE_BITS
568 + GRUB_DISK_SECTOR_BITS
)));
570 sector
+= agglomerate
<< GRUB_DISK_CACHE_BITS
;
571 size
-= agglomerate
<< (GRUB_DISK_CACHE_BITS
+ GRUB_DISK_SECTOR_BITS
);
573 + (agglomerate
<< (GRUB_DISK_CACHE_BITS
+ GRUB_DISK_SECTOR_BITS
));
578 sector
+= GRUB_DISK_CACHE_SIZE
;
579 buf
= (char *) buf
+ (GRUB_DISK_CACHE_SIZE
<< GRUB_DISK_SECTOR_BITS
);
580 size
-= (GRUB_DISK_CACHE_SIZE
<< GRUB_DISK_SECTOR_BITS
);
584 /* And now read the last part. */
588 err
= grub_disk_read_small (disk
, sector
, 0, size
, buf
);
593 /* Call the read hook, if any. */
596 grub_disk_addr_t s
= real_sector
;
597 grub_size_t l
= real_size
;
598 grub_off_t o
= real_offset
;
603 cl
= GRUB_DISK_SECTOR_SIZE
- o
;
606 (disk
->read_hook
) (s
, o
, cl
);
617 grub_disk_write (grub_disk_t disk
, grub_disk_addr_t sector
,
618 grub_off_t offset
, grub_size_t size
, const void *buf
)
620 unsigned real_offset
;
621 grub_disk_addr_t aligned_sector
;
623 grub_dprintf ("disk", "Writing `%s'...\n", disk
->name
);
625 if (grub_disk_adjust_range (disk
, §or
, &offset
, size
) != GRUB_ERR_NONE
)
628 aligned_sector
= (sector
& ~((1 << (disk
->log_sector_size
629 - GRUB_DISK_SECTOR_BITS
)) - 1));
630 real_offset
= offset
+ ((sector
- aligned_sector
) << GRUB_DISK_SECTOR_BITS
);
631 sector
= aligned_sector
;
635 if (real_offset
!= 0 || (size
< (1U << disk
->log_sector_size
)
638 char tmp_buf
[1 << disk
->log_sector_size
];
640 grub_partition_t part
;
642 part
= disk
->partition
;
644 if (grub_disk_read (disk
, sector
,
645 0, (1 << disk
->log_sector_size
), tmp_buf
)
648 disk
->partition
= part
;
651 disk
->partition
= part
;
653 len
= (1 << disk
->log_sector_size
) - real_offset
;
657 grub_memcpy (tmp_buf
+ real_offset
, buf
, len
);
659 grub_disk_cache_invalidate (disk
->dev
->id
, disk
->id
, sector
);
661 if ((disk
->dev
->write
) (disk
, sector
, 1, tmp_buf
) != GRUB_ERR_NONE
)
664 sector
+= (1 << (disk
->log_sector_size
- GRUB_DISK_SECTOR_BITS
));
665 buf
= (const char *) buf
+ len
;
674 len
= size
& ~((1 << disk
->log_sector_size
) - 1);
675 n
= size
>> disk
->log_sector_size
;
677 if ((disk
->dev
->write
) (disk
, sector
, n
, buf
) != GRUB_ERR_NONE
)
681 grub_disk_cache_invalidate (disk
->dev
->id
, disk
->id
, sector
++);
683 buf
= (const char *) buf
+ len
;
694 grub_disk_get_size (grub_disk_t disk
)
697 return grub_partition_get_len (disk
->partition
);
698 else if (disk
->total_sectors
!= GRUB_DISK_SIZE_UNKNOWN
)
699 return disk
->total_sectors
<< (disk
->log_sector_size
- GRUB_DISK_SECTOR_BITS
);
701 return GRUB_DISK_SIZE_UNKNOWN
;