]> git.proxmox.com Git - grub2.git/blob - grub-core/kern/disk.c
Import grub2_2.02+dfsg1.orig.tar.xz
[grub2.git] / grub-core / kern / disk.c
1 /*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2002,2003,2004,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
4 *
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.
9 *
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.
14 *
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/>.
17 */
18
19 #include <grub/disk.h>
20 #include <grub/err.h>
21 #include <grub/mm.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>
28
29 #define GRUB_CACHE_TIMEOUT 2
30
31 /* The last time the disk was used. */
32 static grub_uint64_t grub_last_time = 0;
33
34 struct grub_disk_cache grub_disk_cache_table[GRUB_DISK_CACHE_NUM];
35
36 void (*grub_disk_firmware_fini) (void);
37 int grub_disk_firmware_is_tainted;
38
39 #if DISK_CACHE_STATS
40 static unsigned long grub_disk_cache_hits;
41 static unsigned long grub_disk_cache_misses;
42
43 void
44 grub_disk_cache_get_performance (unsigned long *hits, unsigned long *misses)
45 {
46 *hits = grub_disk_cache_hits;
47 *misses = grub_disk_cache_misses;
48 }
49 #endif
50
51 grub_err_t (*grub_disk_write_weak) (grub_disk_t disk,
52 grub_disk_addr_t sector,
53 grub_off_t offset,
54 grub_size_t size,
55 const void *buf);
56 #include "disk_common.c"
57
58 void
59 grub_disk_cache_invalidate_all (void)
60 {
61 unsigned i;
62
63 for (i = 0; i < GRUB_DISK_CACHE_NUM; i++)
64 {
65 struct grub_disk_cache *cache = grub_disk_cache_table + i;
66
67 if (cache->data && ! cache->lock)
68 {
69 grub_free (cache->data);
70 cache->data = 0;
71 }
72 }
73 }
74
75 static char *
76 grub_disk_cache_fetch (unsigned long dev_id, unsigned long disk_id,
77 grub_disk_addr_t sector)
78 {
79 struct grub_disk_cache *cache;
80 unsigned cache_index;
81
82 cache_index = grub_disk_cache_get_index (dev_id, disk_id, sector);
83 cache = grub_disk_cache_table + cache_index;
84
85 if (cache->dev_id == dev_id && cache->disk_id == disk_id
86 && cache->sector == sector)
87 {
88 cache->lock = 1;
89 #if DISK_CACHE_STATS
90 grub_disk_cache_hits++;
91 #endif
92 return cache->data;
93 }
94
95 #if DISK_CACHE_STATS
96 grub_disk_cache_misses++;
97 #endif
98
99 return 0;
100 }
101
102 static void
103 grub_disk_cache_unlock (unsigned long dev_id, unsigned long disk_id,
104 grub_disk_addr_t sector)
105 {
106 struct grub_disk_cache *cache;
107 unsigned cache_index;
108
109 cache_index = grub_disk_cache_get_index (dev_id, disk_id, sector);
110 cache = grub_disk_cache_table + cache_index;
111
112 if (cache->dev_id == dev_id && cache->disk_id == disk_id
113 && cache->sector == sector)
114 cache->lock = 0;
115 }
116
117 static grub_err_t
118 grub_disk_cache_store (unsigned long dev_id, unsigned long disk_id,
119 grub_disk_addr_t sector, const char *data)
120 {
121 unsigned cache_index;
122 struct grub_disk_cache *cache;
123
124 cache_index = grub_disk_cache_get_index (dev_id, disk_id, sector);
125 cache = grub_disk_cache_table + cache_index;
126
127 cache->lock = 1;
128 grub_free (cache->data);
129 cache->data = 0;
130 cache->lock = 0;
131
132 cache->data = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
133 if (! cache->data)
134 return grub_errno;
135
136 grub_memcpy (cache->data, data,
137 GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
138 cache->dev_id = dev_id;
139 cache->disk_id = disk_id;
140 cache->sector = sector;
141
142 return GRUB_ERR_NONE;
143 }
144
145 \f
146
147 grub_disk_dev_t grub_disk_dev_list;
148
149 void
150 grub_disk_dev_register (grub_disk_dev_t dev)
151 {
152 dev->next = grub_disk_dev_list;
153 grub_disk_dev_list = dev;
154 }
155
156 void
157 grub_disk_dev_unregister (grub_disk_dev_t dev)
158 {
159 grub_disk_dev_t *p, q;
160
161 for (p = &grub_disk_dev_list, q = *p; q; p = &(q->next), q = q->next)
162 if (q == dev)
163 {
164 *p = q->next;
165 break;
166 }
167 }
168
169 /* Return the location of the first ',', if any, which is not
170 escaped by a '\'. */
171 static const char *
172 find_part_sep (const char *name)
173 {
174 const char *p = name;
175 char c;
176
177 while ((c = *p++) != '\0')
178 {
179 if (c == '\\' && *p == ',')
180 p++;
181 else if (c == ',')
182 return p - 1;
183 }
184 return NULL;
185 }
186
187 grub_disk_t
188 grub_disk_open (const char *name)
189 {
190 const char *p;
191 grub_disk_t disk;
192 grub_disk_dev_t dev;
193 char *raw = (char *) name;
194 grub_uint64_t current_time;
195
196 grub_dprintf ("disk", "Opening `%s'...\n", name);
197
198 disk = (grub_disk_t) grub_zalloc (sizeof (*disk));
199 if (! disk)
200 return 0;
201 disk->log_sector_size = GRUB_DISK_SECTOR_BITS;
202 /* Default 1MiB of maximum agglomerate. */
203 disk->max_agglomerate = 1048576 >> (GRUB_DISK_SECTOR_BITS
204 + GRUB_DISK_CACHE_BITS);
205
206 p = find_part_sep (name);
207 if (p)
208 {
209 grub_size_t len = p - name;
210
211 raw = grub_malloc (len + 1);
212 if (! raw)
213 goto fail;
214
215 grub_memcpy (raw, name, len);
216 raw[len] = '\0';
217 disk->name = grub_strdup (raw);
218 }
219 else
220 disk->name = grub_strdup (name);
221 if (! disk->name)
222 goto fail;
223
224 for (dev = grub_disk_dev_list; dev; dev = dev->next)
225 {
226 if ((dev->open) (raw, disk) == GRUB_ERR_NONE)
227 break;
228 else if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
229 grub_errno = GRUB_ERR_NONE;
230 else
231 goto fail;
232 }
233
234 if (! dev)
235 {
236 grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("disk `%s' not found"),
237 name);
238 goto fail;
239 }
240 if (disk->log_sector_size > GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS
241 || disk->log_sector_size < GRUB_DISK_SECTOR_BITS)
242 {
243 grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
244 "sector sizes of %d bytes aren't supported yet",
245 (1 << disk->log_sector_size));
246 goto fail;
247 }
248
249 disk->dev = dev;
250
251 if (p)
252 {
253 disk->partition = grub_partition_probe (disk, p + 1);
254 if (! disk->partition)
255 {
256 /* TRANSLATORS: It means that the specified partition e.g.
257 hd0,msdos1=/dev/sda1 doesn't exist. */
258 grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("no such partition"));
259 goto fail;
260 }
261 }
262
263 /* The cache will be invalidated about 2 seconds after a device was
264 closed. */
265 current_time = grub_get_time_ms ();
266
267 if (current_time > (grub_last_time
268 + GRUB_CACHE_TIMEOUT * 1000))
269 grub_disk_cache_invalidate_all ();
270
271 grub_last_time = current_time;
272
273 fail:
274
275 if (raw && raw != name)
276 grub_free (raw);
277
278 if (grub_errno != GRUB_ERR_NONE)
279 {
280 grub_error_push ();
281 grub_dprintf ("disk", "Opening `%s' failed.\n", name);
282 grub_error_pop ();
283
284 grub_disk_close (disk);
285 return 0;
286 }
287
288 return disk;
289 }
290
291 void
292 grub_disk_close (grub_disk_t disk)
293 {
294 grub_partition_t part;
295 grub_dprintf ("disk", "Closing `%s'.\n", disk->name);
296
297 if (disk->dev && disk->dev->close)
298 (disk->dev->close) (disk);
299
300 /* Reset the timer. */
301 grub_last_time = grub_get_time_ms ();
302
303 while (disk->partition)
304 {
305 part = disk->partition->parent;
306 grub_free (disk->partition);
307 disk->partition = part;
308 }
309 grub_free ((void *) disk->name);
310 grub_free (disk);
311 }
312
313 /* Small read (less than cache size and not pass across cache unit boundaries).
314 sector is already adjusted and is divisible by cache unit size.
315 */
316 static grub_err_t
317 grub_disk_read_small_real (grub_disk_t disk, grub_disk_addr_t sector,
318 grub_off_t offset, grub_size_t size, void *buf)
319 {
320 char *data;
321 char *tmp_buf;
322
323 /* Fetch the cache. */
324 data = grub_disk_cache_fetch (disk->dev->id, disk->id, sector);
325 if (data)
326 {
327 /* Just copy it! */
328 grub_memcpy (buf, data + offset, size);
329 grub_disk_cache_unlock (disk->dev->id, disk->id, sector);
330 return GRUB_ERR_NONE;
331 }
332
333 /* Allocate a temporary buffer. */
334 tmp_buf = grub_malloc (GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS);
335 if (! tmp_buf)
336 return grub_errno;
337
338 /* Otherwise read data from the disk actually. */
339 if (disk->total_sectors == GRUB_DISK_SIZE_UNKNOWN
340 || sector + GRUB_DISK_CACHE_SIZE
341 < (disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS)))
342 {
343 grub_err_t err;
344 err = (disk->dev->read) (disk, transform_sector (disk, sector),
345 1U << (GRUB_DISK_CACHE_BITS
346 + GRUB_DISK_SECTOR_BITS
347 - disk->log_sector_size), tmp_buf);
348 if (!err)
349 {
350 /* Copy it and store it in the disk cache. */
351 grub_memcpy (buf, tmp_buf + offset, size);
352 grub_disk_cache_store (disk->dev->id, disk->id,
353 sector, tmp_buf);
354 grub_free (tmp_buf);
355 return GRUB_ERR_NONE;
356 }
357 }
358
359 grub_free (tmp_buf);
360 grub_errno = GRUB_ERR_NONE;
361
362 {
363 /* Uggh... Failed. Instead, just read necessary data. */
364 unsigned num;
365 grub_disk_addr_t aligned_sector;
366
367 sector += (offset >> GRUB_DISK_SECTOR_BITS);
368 offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
369 aligned_sector = (sector & ~((1ULL << (disk->log_sector_size
370 - GRUB_DISK_SECTOR_BITS))
371 - 1));
372 offset += ((sector - aligned_sector) << GRUB_DISK_SECTOR_BITS);
373 num = ((size + offset + (1ULL << (disk->log_sector_size))
374 - 1) >> (disk->log_sector_size));
375
376 tmp_buf = grub_malloc (num << disk->log_sector_size);
377 if (!tmp_buf)
378 return grub_errno;
379
380 if ((disk->dev->read) (disk, transform_sector (disk, aligned_sector),
381 num, tmp_buf))
382 {
383 grub_error_push ();
384 grub_dprintf ("disk", "%s read failed\n", disk->name);
385 grub_error_pop ();
386 grub_free (tmp_buf);
387 return grub_errno;
388 }
389 grub_memcpy (buf, tmp_buf + offset, size);
390 grub_free (tmp_buf);
391 return GRUB_ERR_NONE;
392 }
393 }
394
395 static grub_err_t
396 grub_disk_read_small (grub_disk_t disk, grub_disk_addr_t sector,
397 grub_off_t offset, grub_size_t size, void *buf)
398 {
399 grub_err_t err;
400
401 err = grub_disk_read_small_real (disk, sector, offset, size, buf);
402 if (err)
403 return err;
404 if (disk->read_hook)
405 (disk->read_hook) (sector + (offset >> GRUB_DISK_SECTOR_BITS),
406 offset & (GRUB_DISK_SECTOR_SIZE - 1),
407 size, disk->read_hook_data);
408 return GRUB_ERR_NONE;
409 }
410
411 /* Read data from the disk. */
412 grub_err_t
413 grub_disk_read (grub_disk_t disk, grub_disk_addr_t sector,
414 grub_off_t offset, grub_size_t size, void *buf)
415 {
416 /* First of all, check if the region is within the disk. */
417 if (grub_disk_adjust_range (disk, &sector, &offset, size) != GRUB_ERR_NONE)
418 {
419 grub_error_push ();
420 grub_dprintf ("disk", "Read out of range: sector 0x%llx (%s).\n",
421 (unsigned long long) sector, grub_errmsg);
422 grub_error_pop ();
423 return grub_errno;
424 }
425
426 /* First read until first cache boundary. */
427 if (offset || (sector & (GRUB_DISK_CACHE_SIZE - 1)))
428 {
429 grub_disk_addr_t start_sector;
430 grub_size_t pos;
431 grub_err_t err;
432 grub_size_t len;
433
434 start_sector = sector & ~((grub_disk_addr_t) GRUB_DISK_CACHE_SIZE - 1);
435 pos = (sector - start_sector) << GRUB_DISK_SECTOR_BITS;
436 len = ((GRUB_DISK_SECTOR_SIZE << GRUB_DISK_CACHE_BITS)
437 - pos - offset);
438 if (len > size)
439 len = size;
440 err = grub_disk_read_small (disk, start_sector,
441 offset + pos, len, buf);
442 if (err)
443 return err;
444 buf = (char *) buf + len;
445 size -= len;
446 offset += len;
447 sector += (offset >> GRUB_DISK_SECTOR_BITS);
448 offset &= ((1 << GRUB_DISK_SECTOR_BITS) - 1);
449 }
450
451 /* Until SIZE is zero... */
452 while (size >= (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS))
453 {
454 char *data = NULL;
455 grub_disk_addr_t agglomerate;
456 grub_err_t err;
457
458 /* agglomerate read until we find a first cached entry. */
459 for (agglomerate = 0; agglomerate
460 < (size >> (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS))
461 && agglomerate < disk->max_agglomerate;
462 agglomerate++)
463 {
464 data = grub_disk_cache_fetch (disk->dev->id, disk->id,
465 sector + (agglomerate
466 << GRUB_DISK_CACHE_BITS));
467 if (data)
468 break;
469 }
470
471 if (data)
472 {
473 grub_memcpy ((char *) buf
474 + (agglomerate << (GRUB_DISK_CACHE_BITS
475 + GRUB_DISK_SECTOR_BITS)),
476 data, GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
477 grub_disk_cache_unlock (disk->dev->id, disk->id,
478 sector + (agglomerate
479 << GRUB_DISK_CACHE_BITS));
480 }
481
482 if (agglomerate)
483 {
484 grub_disk_addr_t i;
485
486 err = (disk->dev->read) (disk, transform_sector (disk, sector),
487 agglomerate << (GRUB_DISK_CACHE_BITS
488 + GRUB_DISK_SECTOR_BITS
489 - disk->log_sector_size),
490 buf);
491 if (err)
492 return err;
493
494 for (i = 0; i < agglomerate; i ++)
495 grub_disk_cache_store (disk->dev->id, disk->id,
496 sector + (i << GRUB_DISK_CACHE_BITS),
497 (char *) buf
498 + (i << (GRUB_DISK_CACHE_BITS
499 + GRUB_DISK_SECTOR_BITS)));
500
501
502 if (disk->read_hook)
503 (disk->read_hook) (sector, 0, agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS),
504 disk->read_hook_data);
505
506 sector += agglomerate << GRUB_DISK_CACHE_BITS;
507 size -= agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS);
508 buf = (char *) buf
509 + (agglomerate << (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS));
510 }
511
512 if (data)
513 {
514 if (disk->read_hook)
515 (disk->read_hook) (sector, 0, (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS),
516 disk->read_hook_data);
517 sector += GRUB_DISK_CACHE_SIZE;
518 buf = (char *) buf + (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
519 size -= (GRUB_DISK_CACHE_SIZE << GRUB_DISK_SECTOR_BITS);
520 }
521 }
522
523 /* And now read the last part. */
524 if (size)
525 {
526 grub_err_t err;
527 err = grub_disk_read_small (disk, sector, 0, size, buf);
528 if (err)
529 return err;
530 }
531
532 return grub_errno;
533 }
534
535 grub_uint64_t
536 grub_disk_get_size (grub_disk_t disk)
537 {
538 if (disk->partition)
539 return grub_partition_get_len (disk->partition);
540 else if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN)
541 return disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS);
542 else
543 return GRUB_DISK_SIZE_UNKNOWN;
544 }