![Matt Fleming](/assets/img/avatar_default.png)
While efi_memmap_init_{early,late}() exist for architecture code to install memory maps from firmware data and for the virtual memory regions respectively, drivers don't care which stage of the boot we're at and just want to swap the existing memmap for a modified one. efi_memmap_install() abstracts the details of how the new memory map should be mapped and the existing one unmapped. Tested-by: Dave Young <dyoung@redhat.com> [kexec/kdump] Tested-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> [arm] Acked-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Leif Lindholm <leif.lindholm@linaro.org> Cc: Peter Jones <pjones@redhat.com> Cc: Borislav Petkov <bp@alien8.de> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Taku Izumi <izumi.taku@jp.fujitsu.com> Signed-off-by: Matt Fleming <matt@codeblueprint.co.uk>
143 lines
3.5 KiB
C
143 lines
3.5 KiB
C
/*
|
|
* fake_mem.c
|
|
*
|
|
* Copyright (C) 2015 FUJITSU LIMITED
|
|
* Author: Taku Izumi <izumi.taku@jp.fujitsu.com>
|
|
*
|
|
* This code introduces new boot option named "efi_fake_mem"
|
|
* By specifying this parameter, you can add arbitrary attribute to
|
|
* specific memory range by updating original (firmware provided) EFI
|
|
* memmap.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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
|
|
* this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* The full GNU General Public License is included in this distribution in
|
|
* the file called "COPYING".
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/efi.h>
|
|
#include <linux/init.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/types.h>
|
|
#include <linux/sort.h>
|
|
#include <asm/efi.h>
|
|
|
|
#define EFI_MAX_FAKEMEM CONFIG_EFI_MAX_FAKE_MEM
|
|
|
|
static struct efi_mem_range fake_mems[EFI_MAX_FAKEMEM];
|
|
static int nr_fake_mem;
|
|
|
|
static int __init cmp_fake_mem(const void *x1, const void *x2)
|
|
{
|
|
const struct efi_mem_range *m1 = x1;
|
|
const struct efi_mem_range *m2 = x2;
|
|
|
|
if (m1->range.start < m2->range.start)
|
|
return -1;
|
|
if (m1->range.start > m2->range.start)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
void __init efi_fake_memmap(void)
|
|
{
|
|
int new_nr_map = efi.memmap.nr_map;
|
|
efi_memory_desc_t *md;
|
|
phys_addr_t new_memmap_phy;
|
|
void *new_memmap;
|
|
int i;
|
|
|
|
if (!nr_fake_mem)
|
|
return;
|
|
|
|
/* count up the number of EFI memory descriptor */
|
|
for (i = 0; i < nr_fake_mem; i++) {
|
|
for_each_efi_memory_desc(md) {
|
|
struct range *r = &fake_mems[i].range;
|
|
|
|
new_nr_map += efi_memmap_split_count(md, r);
|
|
}
|
|
}
|
|
|
|
/* allocate memory for new EFI memmap */
|
|
new_memmap_phy = memblock_alloc(efi.memmap.desc_size * new_nr_map,
|
|
PAGE_SIZE);
|
|
if (!new_memmap_phy)
|
|
return;
|
|
|
|
/* create new EFI memmap */
|
|
new_memmap = early_memremap(new_memmap_phy,
|
|
efi.memmap.desc_size * new_nr_map);
|
|
if (!new_memmap) {
|
|
memblock_free(new_memmap_phy, efi.memmap.desc_size * new_nr_map);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < nr_fake_mem; i++)
|
|
efi_memmap_insert(&efi.memmap, new_memmap, &fake_mems[i]);
|
|
|
|
/* swap into new EFI memmap */
|
|
early_memunmap(new_memmap, efi.memmap.desc_size * new_nr_map);
|
|
|
|
efi_memmap_install(new_memmap_phy, new_nr_map);
|
|
|
|
/* print new EFI memmap */
|
|
efi_print_memmap();
|
|
}
|
|
|
|
static int __init setup_fake_mem(char *p)
|
|
{
|
|
u64 start = 0, mem_size = 0, attribute = 0;
|
|
int i;
|
|
|
|
if (!p)
|
|
return -EINVAL;
|
|
|
|
while (*p != '\0') {
|
|
mem_size = memparse(p, &p);
|
|
if (*p == '@')
|
|
start = memparse(p+1, &p);
|
|
else
|
|
break;
|
|
|
|
if (*p == ':')
|
|
attribute = simple_strtoull(p+1, &p, 0);
|
|
else
|
|
break;
|
|
|
|
if (nr_fake_mem >= EFI_MAX_FAKEMEM)
|
|
break;
|
|
|
|
fake_mems[nr_fake_mem].range.start = start;
|
|
fake_mems[nr_fake_mem].range.end = start + mem_size - 1;
|
|
fake_mems[nr_fake_mem].attribute = attribute;
|
|
nr_fake_mem++;
|
|
|
|
if (*p == ',')
|
|
p++;
|
|
}
|
|
|
|
sort(fake_mems, nr_fake_mem, sizeof(struct efi_mem_range),
|
|
cmp_fake_mem, NULL);
|
|
|
|
for (i = 0; i < nr_fake_mem; i++)
|
|
pr_info("efi_fake_mem: add attr=0x%016llx to [mem 0x%016llx-0x%016llx]",
|
|
fake_mems[i].attribute, fake_mems[i].range.start,
|
|
fake_mems[i].range.end);
|
|
|
|
return *p == '\0' ? 0 : -EINVAL;
|
|
}
|
|
|
|
early_param("efi_fake_mem", setup_fake_mem);
|