| /* Mmap management. */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2009 Free Software Foundation, Inc. |
| * |
| * GRUB is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * GRUB is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with GRUB. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/memory.h> |
| #include <grub/machine/memory.h> |
| #include <grub/err.h> |
| #include <grub/misc.h> |
| #include <grub/mm.h> |
| #include <grub/command.h> |
| #include <grub/dl.h> |
| #include <grub/i18n.h> |
| |
| GRUB_MOD_LICENSE ("GPLv3+"); |
| |
| #ifndef GRUB_MMAP_REGISTER_BY_FIRMWARE |
| |
| struct grub_mmap_region *grub_mmap_overlays = 0; |
| static int curhandle = 1; |
| |
| #endif |
| |
| static int current_priority = 1; |
| |
| /* Scanline events. */ |
| struct grub_mmap_scan |
| { |
| /* At which memory address. */ |
| grub_uint64_t pos; |
| /* 0 = region starts, 1 = region ends. */ |
| int type; |
| /* Which type of memory region? */ |
| grub_memory_type_t memtype; |
| /* Priority. 0 means coming from firmware. */ |
| int priority; |
| }; |
| |
| /* Context for grub_mmap_iterate. */ |
| struct grub_mmap_iterate_ctx |
| { |
| struct grub_mmap_scan *scanline_events; |
| int i; |
| }; |
| |
| /* Helper for grub_mmap_iterate. */ |
| static int |
| count_hook (grub_uint64_t addr __attribute__ ((unused)), |
| grub_uint64_t size __attribute__ ((unused)), |
| grub_memory_type_t type __attribute__ ((unused)), void *data) |
| { |
| int *mmap_num = data; |
| |
| (*mmap_num)++; |
| return 0; |
| } |
| |
| /* Helper for grub_mmap_iterate. */ |
| static int |
| fill_hook (grub_uint64_t addr, grub_uint64_t size, grub_memory_type_t type, |
| void *data) |
| { |
| struct grub_mmap_iterate_ctx *ctx = data; |
| |
| if (type == GRUB_MEMORY_HOLE) |
| { |
| grub_dprintf ("mmap", "Unknown memory type %d. Assuming unusable\n", |
| type); |
| type = GRUB_MEMORY_RESERVED; |
| } |
| |
| ctx->scanline_events[ctx->i].pos = addr; |
| ctx->scanline_events[ctx->i].type = 0; |
| ctx->scanline_events[ctx->i].memtype = type; |
| ctx->scanline_events[ctx->i].priority = 0; |
| |
| ctx->i++; |
| |
| ctx->scanline_events[ctx->i].pos = addr + size; |
| ctx->scanline_events[ctx->i].type = 1; |
| ctx->scanline_events[ctx->i].memtype = type; |
| ctx->scanline_events[ctx->i].priority = 0; |
| ctx->i++; |
| |
| return 0; |
| } |
| |
| struct mm_list |
| { |
| struct mm_list *next; |
| grub_memory_type_t val; |
| int present; |
| }; |
| |
| grub_err_t |
| grub_mmap_iterate (grub_memory_hook_t hook, void *hook_data) |
| { |
| /* This function resolves overlapping regions and sorts the memory map. |
| It uses scanline (sweeping) algorithm. |
| */ |
| struct grub_mmap_iterate_ctx ctx; |
| int i, done; |
| |
| struct grub_mmap_scan t; |
| |
| /* Previous scanline event. */ |
| grub_uint64_t lastaddr; |
| int lasttype; |
| /* Current scanline event. */ |
| int curtype; |
| /* How many regions of given type/priority overlap at current location? */ |
| /* Normally there shouldn't be more than one region per priority but be robust. */ |
| struct mm_list *present; |
| /* Number of mmap chunks. */ |
| int mmap_num; |
| |
| #ifndef GRUB_MMAP_REGISTER_BY_FIRMWARE |
| struct grub_mmap_region *cur; |
| #endif |
| |
| mmap_num = 0; |
| |
| #ifndef GRUB_MMAP_REGISTER_BY_FIRMWARE |
| for (cur = grub_mmap_overlays; cur; cur = cur->next) |
| mmap_num++; |
| #endif |
| |
| grub_machine_mmap_iterate (count_hook, &mmap_num); |
| |
| /* Initialize variables. */ |
| ctx.scanline_events = (struct grub_mmap_scan *) |
| grub_malloc (sizeof (struct grub_mmap_scan) * 2 * mmap_num); |
| |
| present = grub_zalloc (sizeof (present[0]) * current_priority); |
| |
| if (! ctx.scanline_events || !present) |
| { |
| grub_free (ctx.scanline_events); |
| grub_free (present); |
| return grub_errno; |
| } |
| |
| ctx.i = 0; |
| #ifndef GRUB_MMAP_REGISTER_BY_FIRMWARE |
| /* Register scanline events. */ |
| for (cur = grub_mmap_overlays; cur; cur = cur->next) |
| { |
| ctx.scanline_events[ctx.i].pos = cur->start; |
| ctx.scanline_events[ctx.i].type = 0; |
| ctx.scanline_events[ctx.i].memtype = cur->type; |
| ctx.scanline_events[ctx.i].priority = cur->priority; |
| ctx.i++; |
| |
| ctx.scanline_events[ctx.i].pos = cur->end; |
| ctx.scanline_events[ctx.i].type = 1; |
| ctx.scanline_events[ctx.i].memtype = cur->type; |
| ctx.scanline_events[ctx.i].priority = cur->priority; |
| ctx.i++; |
| } |
| #endif /* ! GRUB_MMAP_REGISTER_BY_FIRMWARE */ |
| |
| grub_machine_mmap_iterate (fill_hook, &ctx); |
| |
| /* Primitive bubble sort. It has complexity O(n^2) but since we're |
| unlikely to have more than 100 chunks it's probably one of the |
| fastest for one purpose. */ |
| done = 1; |
| while (done) |
| { |
| done = 0; |
| for (i = 0; i < 2 * mmap_num - 1; i++) |
| if (ctx.scanline_events[i + 1].pos < ctx.scanline_events[i].pos |
| || (ctx.scanline_events[i + 1].pos == ctx.scanline_events[i].pos |
| && ctx.scanline_events[i + 1].type == 0 |
| && ctx.scanline_events[i].type == 1)) |
| { |
| t = ctx.scanline_events[i + 1]; |
| ctx.scanline_events[i + 1] = ctx.scanline_events[i]; |
| ctx.scanline_events[i] = t; |
| done = 1; |
| } |
| } |
| |
| lastaddr = ctx.scanline_events[0].pos; |
| lasttype = ctx.scanline_events[0].memtype; |
| for (i = 0; i < 2 * mmap_num; i++) |
| { |
| /* Process event. */ |
| if (ctx.scanline_events[i].type) |
| { |
| if (present[ctx.scanline_events[i].priority].present) |
| { |
| if (present[ctx.scanline_events[i].priority].val == ctx.scanline_events[i].memtype) |
| { |
| if (present[ctx.scanline_events[i].priority].next) |
| { |
| struct mm_list *p = present[ctx.scanline_events[i].priority].next; |
| present[ctx.scanline_events[i].priority] = *p; |
| grub_free (p); |
| } |
| else |
| { |
| present[ctx.scanline_events[i].priority].present = 0; |
| } |
| } |
| else |
| { |
| struct mm_list **q = &(present[ctx.scanline_events[i].priority].next), *p; |
| for (; *q; q = &((*q)->next)) |
| if ((*q)->val == ctx.scanline_events[i].memtype) |
| { |
| p = *q; |
| *q = p->next; |
| grub_free (p); |
| break; |
| } |
| } |
| } |
| } |
| else |
| { |
| if (!present[ctx.scanline_events[i].priority].present) |
| { |
| present[ctx.scanline_events[i].priority].present = 1; |
| present[ctx.scanline_events[i].priority].val = ctx.scanline_events[i].memtype; |
| } |
| else |
| { |
| struct mm_list *n = grub_malloc (sizeof (*n)); |
| n->val = ctx.scanline_events[i].memtype; |
| n->present = 1; |
| n->next = present[ctx.scanline_events[i].priority].next; |
| present[ctx.scanline_events[i].priority].next = n; |
| } |
| } |
| |
| /* Determine current region type. */ |
| curtype = -1; |
| { |
| int k; |
| for (k = current_priority - 1; k >= 0; k--) |
| if (present[k].present) |
| { |
| curtype = present[k].val; |
| break; |
| } |
| } |
| |
| /* Announce region to the hook if necessary. */ |
| if ((curtype == -1 || curtype != lasttype) |
| && lastaddr != ctx.scanline_events[i].pos |
| && lasttype != -1 |
| && lasttype != GRUB_MEMORY_HOLE |
| && hook (lastaddr, ctx.scanline_events[i].pos - lastaddr, lasttype, |
| hook_data)) |
| { |
| grub_free (ctx.scanline_events); |
| return GRUB_ERR_NONE; |
| } |
| |
| /* Update last values if necessary. */ |
| if (curtype == -1 || curtype != lasttype) |
| { |
| lasttype = curtype; |
| lastaddr = ctx.scanline_events[i].pos; |
| } |
| } |
| |
| grub_free (ctx.scanline_events); |
| return GRUB_ERR_NONE; |
| } |
| |
| #ifndef GRUB_MMAP_REGISTER_BY_FIRMWARE |
| int |
| grub_mmap_register (grub_uint64_t start, grub_uint64_t size, int type) |
| { |
| struct grub_mmap_region *cur; |
| |
| grub_dprintf ("mmap", "registering\n"); |
| |
| cur = (struct grub_mmap_region *) |
| grub_malloc (sizeof (struct grub_mmap_region)); |
| if (! cur) |
| return 0; |
| |
| cur->next = grub_mmap_overlays; |
| cur->start = start; |
| cur->end = start + size; |
| cur->type = type; |
| cur->handle = curhandle++; |
| cur->priority = current_priority++; |
| grub_mmap_overlays = cur; |
| |
| if (grub_machine_mmap_register (start, size, type, curhandle)) |
| { |
| grub_mmap_overlays = cur->next; |
| grub_free (cur); |
| return 0; |
| } |
| |
| return cur->handle; |
| } |
| |
| grub_err_t |
| grub_mmap_unregister (int handle) |
| { |
| struct grub_mmap_region *cur, *prev; |
| |
| for (cur = grub_mmap_overlays, prev = 0; cur; prev = cur, cur = cur->next) |
| if (handle == cur->handle) |
| { |
| grub_err_t err; |
| err = grub_machine_mmap_unregister (handle); |
| if (err) |
| return err; |
| |
| if (prev) |
| prev->next = cur->next; |
| else |
| grub_mmap_overlays = cur->next; |
| grub_free (cur); |
| return GRUB_ERR_NONE; |
| } |
| return grub_error (GRUB_ERR_BUG, "mmap overlay not found"); |
| } |
| |
| #endif /* ! GRUB_MMAP_REGISTER_BY_FIRMWARE */ |
| |
| #define CHUNK_SIZE 0x400 |
| |
| struct badram_entry { |
| grub_uint64_t addr, mask; |
| }; |
| |
| static inline grub_uint64_t |
| fill_mask (struct badram_entry *entry, grub_uint64_t iterator) |
| { |
| int i, j; |
| grub_uint64_t ret = (entry->addr & entry->mask); |
| |
| /* Find first fixed bit. */ |
| for (i = 0; i < 64; i++) |
| if ((entry->mask & (1ULL << i)) != 0) |
| break; |
| j = 0; |
| for (; i < 64; i++) |
| if ((entry->mask & (1ULL << i)) == 0) |
| { |
| if ((iterator & (1ULL << j)) != 0) |
| ret |= 1ULL << i; |
| j++; |
| } |
| return ret; |
| } |
| |
| /* Helper for grub_cmd_badram. */ |
| static int |
| badram_iter (grub_uint64_t addr, grub_uint64_t size, |
| grub_memory_type_t type __attribute__ ((unused)), void *data) |
| { |
| struct badram_entry *entry = data; |
| grub_uint64_t iterator, low, high, cur; |
| int tail, var; |
| int i; |
| grub_dprintf ("badram", "hook %llx+%llx\n", (unsigned long long) addr, |
| (unsigned long long) size); |
| |
| /* How many trailing zeros? */ |
| for (tail = 0; ! (entry->mask & (1ULL << tail)); tail++); |
| |
| /* How many zeros in mask? */ |
| var = 0; |
| for (i = 0; i < 64; i++) |
| if (! (entry->mask & (1ULL << i))) |
| var++; |
| |
| if (fill_mask (entry, 0) >= addr) |
| iterator = 0; |
| else |
| { |
| low = 0; |
| high = ~0ULL; |
| /* Find starting value. Keep low and high such that |
| fill_mask (low) < addr and fill_mask (high) >= addr; |
| */ |
| while (high - low > 1) |
| { |
| cur = (low + high) / 2; |
| if (fill_mask (entry, cur) >= addr) |
| high = cur; |
| else |
| low = cur; |
| } |
| iterator = high; |
| } |
| |
| for (; iterator < (1ULL << (var - tail)) |
| && (cur = fill_mask (entry, iterator)) < addr + size; |
| iterator++) |
| { |
| grub_dprintf ("badram", "%llx (size %llx) is a badram range\n", |
| (unsigned long long) cur, (1ULL << tail)); |
| grub_mmap_register (cur, (1ULL << tail), GRUB_MEMORY_HOLE); |
| } |
| return 0; |
| } |
| |
| static grub_err_t |
| grub_cmd_badram (grub_command_t cmd __attribute__ ((unused)), |
| int argc, char **args) |
| { |
| char * str; |
| struct badram_entry entry; |
| |
| if (argc != 1) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); |
| |
| grub_dprintf ("badram", "executing badram\n"); |
| |
| str = args[0]; |
| |
| while (1) |
| { |
| /* Parse address and mask. */ |
| entry.addr = grub_strtoull (str, &str, 16); |
| if (*str == ',') |
| str++; |
| entry.mask = grub_strtoull (str, &str, 16); |
| if (*str == ',') |
| str++; |
| |
| if (grub_errno == GRUB_ERR_BAD_NUMBER) |
| { |
| grub_errno = 0; |
| return GRUB_ERR_NONE; |
| } |
| |
| /* When part of a page is tainted, we discard the whole of it. There's |
| no point in providing sub-page chunks. */ |
| entry.mask &= ~(CHUNK_SIZE - 1); |
| |
| grub_dprintf ("badram", "badram %llx:%llx\n", |
| (unsigned long long) entry.addr, |
| (unsigned long long) entry.mask); |
| |
| grub_mmap_iterate (badram_iter, &entry); |
| } |
| } |
| |
| static grub_uint64_t |
| parsemem (const char *str) |
| { |
| grub_uint64_t ret; |
| char *ptr; |
| |
| ret = grub_strtoul (str, &ptr, 0); |
| |
| switch (*ptr) |
| { |
| case 'K': |
| return ret << 10; |
| case 'M': |
| return ret << 20; |
| case 'G': |
| return ret << 30; |
| case 'T': |
| return ret << 40; |
| } |
| return ret; |
| } |
| |
| struct cutmem_range { |
| grub_uint64_t from, to; |
| }; |
| |
| /* Helper for grub_cmd_cutmem. */ |
| static int |
| cutmem_iter (grub_uint64_t addr, grub_uint64_t size, |
| grub_memory_type_t type __attribute__ ((unused)), void *data) |
| { |
| struct cutmem_range *range = data; |
| grub_uint64_t end = addr + size; |
| |
| if (addr <= range->from) |
| addr = range->from; |
| if (end >= range->to) |
| end = range->to; |
| |
| if (end <= addr) |
| return 0; |
| |
| grub_mmap_register (addr, end - addr, GRUB_MEMORY_HOLE); |
| return 0; |
| } |
| |
| static grub_err_t |
| grub_cmd_cutmem (grub_command_t cmd __attribute__ ((unused)), |
| int argc, char **args) |
| { |
| struct cutmem_range range; |
| |
| if (argc != 2) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("two arguments expected")); |
| |
| range.from = parsemem (args[0]); |
| if (grub_errno) |
| return grub_errno; |
| |
| range.to = parsemem (args[1]); |
| if (grub_errno) |
| return grub_errno; |
| |
| grub_mmap_iterate (cutmem_iter, &range); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_command_t cmd, cmd_cut; |
| |
| |
| GRUB_MOD_INIT(mmap) |
| { |
| cmd = grub_register_command ("badram", grub_cmd_badram, |
| N_("ADDR1,MASK1[,ADDR2,MASK2[,...]]"), |
| N_("Declare memory regions as faulty (badram).")); |
| cmd_cut = grub_register_command ("cutmem", grub_cmd_cutmem, |
| N_("FROM[K|M|G] TO[K|M|G]"), |
| N_("Remove any memory regions in specified range.")); |
| |
| } |
| |
| GRUB_MOD_FINI(mmap) |
| { |
| grub_unregister_command (cmd); |
| grub_unregister_command (cmd_cut); |
| } |
| |