/* Code for managing symbols and pointers in efiemu */ /* * 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/err.h> #include <grub/mm.h> #include <grub/misc.h> #include <grub/efiemu/efiemu.h> #include <grub/efiemu/runtime.h> static int ptv_written = 0; static int ptv_alloc = 0; static int ptv_handle = 0; static int relocated_handle = 0; static int ptv_requested = 0; static struct grub_efiemu_sym *efiemu_syms = 0; struct grub_efiemu_sym { struct grub_efiemu_sym *next; char *name; int handle; grub_off_t off; }; void grub_efiemu_free_syms (void) { struct grub_efiemu_sym *cur, *d; for (cur = efiemu_syms; cur;) { d = cur->next; grub_free (cur->name); grub_free (cur); cur = d; } efiemu_syms = 0; ptv_written = 0; ptv_alloc = 0; ptv_requested = 0; grub_efiemu_mm_return_request (ptv_handle); ptv_handle = 0; grub_efiemu_mm_return_request (relocated_handle); relocated_handle = 0; } /* Announce that the module will need NUM allocators */ /* Because of deferred memory allocation all the relocators have to be announced during phase 1*/ grub_err_t grub_efiemu_request_symbols (int num) { if (ptv_alloc) return grub_error (GRUB_ERR_BAD_ARGUMENT, "symbols have already been allocated"); if (num < 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, "can't request negative symbols"); ptv_requested += num; return GRUB_ERR_NONE; } /* Resolve the symbol name NAME and set HANDLE and OFF accordingly */ grub_err_t grub_efiemu_resolve_symbol (const char *name, int *handle, grub_off_t *off) { struct grub_efiemu_sym *cur; for (cur = efiemu_syms; cur; cur = cur->next) if (!grub_strcmp (name, cur->name)) { *handle = cur->handle; *off = cur->off; return GRUB_ERR_NONE; } grub_dprintf ("efiemu", "%s not found\n", name); return grub_error (GRUB_ERR_BAD_OS, "symbol %s isn't found", name); } /* Register symbol named NAME in memory handle HANDLE at offset OFF */ grub_err_t grub_efiemu_register_symbol (const char *name, int handle, grub_off_t off) { struct grub_efiemu_sym *cur; cur = (struct grub_efiemu_sym *) grub_malloc (sizeof (*cur)); grub_dprintf ("efiemu", "registering symbol '%s'\n", name); if (!cur) return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't register symbol"); cur->name = grub_strdup (name); cur->next = efiemu_syms; cur->handle = handle; cur->off = off; efiemu_syms = cur; return 0; } /* Go from phase 1 to phase 2. Must be called before similar function in mm.c */ grub_err_t grub_efiemu_alloc_syms (void) { ptv_alloc = ptv_requested; ptv_handle = grub_efiemu_request_memalign (1, (ptv_requested + 1) * sizeof (struct grub_efiemu_ptv_rel), GRUB_EFI_RUNTIME_SERVICES_DATA); relocated_handle = grub_efiemu_request_memalign (1, sizeof (grub_uint8_t), GRUB_EFI_RUNTIME_SERVICES_DATA); grub_efiemu_register_symbol ("efiemu_ptv_relocated", relocated_handle, 0); grub_efiemu_register_symbol ("efiemu_ptv_relloc", ptv_handle, 0); return grub_errno; } grub_err_t grub_efiemu_write_sym_markers (void) { struct grub_efiemu_ptv_rel *ptv_rels = grub_efiemu_mm_obtain_request (ptv_handle); grub_uint8_t *relocated = grub_efiemu_mm_obtain_request (relocated_handle); grub_memset (ptv_rels, 0, (ptv_requested + 1) * sizeof (struct grub_efiemu_ptv_rel)); *relocated = 0; return GRUB_ERR_NONE; } /* Write value (pointer to memory PLUS_HANDLE) - (pointer to memory MINUS_HANDLE) + VALUE to ADDR assuming that the size SIZE bytes. If PTV_NEEDED is 1 then announce it to runtime that this value needs to be recomputed before going to virtual mode */ grub_err_t grub_efiemu_write_value (void *addr, grub_uint32_t value, int plus_handle, int minus_handle, int ptv_needed, int size) { /* Announce relocator to runtime */ if (ptv_needed) { struct grub_efiemu_ptv_rel *ptv_rels = grub_efiemu_mm_obtain_request (ptv_handle); if (ptv_needed && ptv_written >= ptv_alloc) return grub_error (GRUB_ERR_OUT_OF_MEMORY, "your module didn't declare efiemu " " relocators correctly"); if (minus_handle) ptv_rels[ptv_written].minustype = grub_efiemu_mm_get_type (minus_handle); else ptv_rels[ptv_written].minustype = 0; if (plus_handle) ptv_rels[ptv_written].plustype = grub_efiemu_mm_get_type (plus_handle); else ptv_rels[ptv_written].plustype = 0; ptv_rels[ptv_written].addr = PTR_TO_UINT64 (addr); ptv_rels[ptv_written].size = size; ptv_written++; /* memset next value to zero to mark the end */ grub_memset (&ptv_rels[ptv_written], 0, sizeof (ptv_rels[ptv_written])); } /* Compute the value */ if (minus_handle) value -= PTR_TO_UINT32 (grub_efiemu_mm_obtain_request (minus_handle)); if (plus_handle) value += PTR_TO_UINT32 (grub_efiemu_mm_obtain_request (plus_handle)); /* Write the value */ switch (size) { case 8: *((grub_uint64_t *) addr) = value; break; case 4: *((grub_uint32_t *) addr) = value; break; case 2: *((grub_uint16_t *) addr) = value; break; case 1: *((grub_uint8_t *) addr) = value; break; default: return grub_error (GRUB_ERR_BAD_ARGUMENT, "wrong symbol size"); } return GRUB_ERR_NONE; } grub_err_t grub_efiemu_set_virtual_address_map (grub_efi_uintn_t memory_map_size, grub_efi_uintn_t descriptor_size, grub_efi_uint32_t descriptor_version __attribute__ ((unused)), grub_efi_memory_descriptor_t *virtual_map) { grub_uint8_t *ptv_relocated; struct grub_efiemu_ptv_rel *cur_relloc; struct grub_efiemu_ptv_rel *ptv_rels; ptv_relocated = grub_efiemu_mm_obtain_request (relocated_handle); ptv_rels = grub_efiemu_mm_obtain_request (ptv_handle); /* Ensure that we are called only once */ if (*ptv_relocated) return grub_error (GRUB_ERR_BAD_ARGUMENT, "EfiEmu is already relocated"); *ptv_relocated = 1; /* Correct addresses using information supplied by grub */ for (cur_relloc = ptv_rels; cur_relloc->size; cur_relloc++) { grub_int64_t corr = 0; grub_efi_memory_descriptor_t *descptr; /* Compute correction */ for (descptr = virtual_map; (grub_size_t) ((grub_uint8_t *) descptr - (grub_uint8_t *) virtual_map) < memory_map_size; descptr = (grub_efi_memory_descriptor_t *) ((grub_uint8_t *) descptr + descriptor_size)) { if (descptr->type == cur_relloc->plustype) corr += descptr->virtual_start - descptr->physical_start; if (descptr->type == cur_relloc->minustype) corr -= descptr->virtual_start - descptr->physical_start; } /* Apply correction */ switch (cur_relloc->size) { case 8: *((grub_uint64_t *) UINT_TO_PTR (cur_relloc->addr)) += corr; break; case 4: *((grub_uint32_t *) UINT_TO_PTR (cur_relloc->addr)) += corr; break; case 2: *((grub_uint16_t *) UINT_TO_PTR (cur_relloc->addr)) += corr; break; case 1: *((grub_uint8_t *) UINT_TO_PTR (cur_relloc->addr)) += corr; break; } } /* Recompute crc32 of system table and runtime services */ if (grub_efiemu_sizeof_uintn_t () == 4) return grub_efiemu_crc32 (); else return grub_efiemu_crc64 (); }