162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * fake_mem.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2015 FUJITSU LIMITED
662306a36Sopenharmony_ci * Author: Taku Izumi <izumi.taku@jp.fujitsu.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This code introduces new boot option named "efi_fake_mem"
962306a36Sopenharmony_ci * By specifying this parameter, you can add arbitrary attribute to
1062306a36Sopenharmony_ci * specific memory range by updating original (firmware provided) EFI
1162306a36Sopenharmony_ci * memmap.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/efi.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/memblock.h>
1862306a36Sopenharmony_ci#include <linux/types.h>
1962306a36Sopenharmony_ci#include <linux/sort.h>
2062306a36Sopenharmony_ci#include <asm/e820/api.h>
2162306a36Sopenharmony_ci#include <asm/efi.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define EFI_MAX_FAKEMEM CONFIG_EFI_MAX_FAKE_MEM
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic struct efi_mem_range efi_fake_mems[EFI_MAX_FAKEMEM];
2662306a36Sopenharmony_cistatic int nr_fake_mem;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int __init cmp_fake_mem(const void *x1, const void *x2)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	const struct efi_mem_range *m1 = x1;
3162306a36Sopenharmony_ci	const struct efi_mem_range *m2 = x2;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	if (m1->range.start < m2->range.start)
3462306a36Sopenharmony_ci		return -1;
3562306a36Sopenharmony_ci	if (m1->range.start > m2->range.start)
3662306a36Sopenharmony_ci		return 1;
3762306a36Sopenharmony_ci	return 0;
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic void __init efi_fake_range(struct efi_mem_range *efi_range)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct efi_memory_map_data data = { 0 };
4362306a36Sopenharmony_ci	int new_nr_map = efi.memmap.nr_map;
4462306a36Sopenharmony_ci	efi_memory_desc_t *md;
4562306a36Sopenharmony_ci	void *new_memmap;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	/* count up the number of EFI memory descriptor */
4862306a36Sopenharmony_ci	for_each_efi_memory_desc(md)
4962306a36Sopenharmony_ci		new_nr_map += efi_memmap_split_count(md, &efi_range->range);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	/* allocate memory for new EFI memmap */
5262306a36Sopenharmony_ci	if (efi_memmap_alloc(new_nr_map, &data) != 0)
5362306a36Sopenharmony_ci		return;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	/* create new EFI memmap */
5662306a36Sopenharmony_ci	new_memmap = early_memremap(data.phys_map, data.size);
5762306a36Sopenharmony_ci	if (!new_memmap) {
5862306a36Sopenharmony_ci		__efi_memmap_free(data.phys_map, data.size, data.flags);
5962306a36Sopenharmony_ci		return;
6062306a36Sopenharmony_ci	}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	efi_memmap_insert(&efi.memmap, new_memmap, efi_range);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	/* swap into new EFI memmap */
6562306a36Sopenharmony_ci	early_memunmap(new_memmap, data.size);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	efi_memmap_install(&data);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_civoid __init efi_fake_memmap(void)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	int i;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	if (!efi_enabled(EFI_MEMMAP) || !nr_fake_mem)
7562306a36Sopenharmony_ci		return;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	for (i = 0; i < nr_fake_mem; i++)
7862306a36Sopenharmony_ci		efi_fake_range(&efi_fake_mems[i]);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* print new EFI memmap */
8162306a36Sopenharmony_ci	efi_print_memmap();
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int __init setup_fake_mem(char *p)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	u64 start = 0, mem_size = 0, attribute = 0;
8762306a36Sopenharmony_ci	int i;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	if (!p)
9062306a36Sopenharmony_ci		return -EINVAL;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	while (*p != '\0') {
9362306a36Sopenharmony_ci		mem_size = memparse(p, &p);
9462306a36Sopenharmony_ci		if (*p == '@')
9562306a36Sopenharmony_ci			start = memparse(p+1, &p);
9662306a36Sopenharmony_ci		else
9762306a36Sopenharmony_ci			break;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		if (*p == ':')
10062306a36Sopenharmony_ci			attribute = simple_strtoull(p+1, &p, 0);
10162306a36Sopenharmony_ci		else
10262306a36Sopenharmony_ci			break;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		if (nr_fake_mem >= EFI_MAX_FAKEMEM)
10562306a36Sopenharmony_ci			break;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		efi_fake_mems[nr_fake_mem].range.start = start;
10862306a36Sopenharmony_ci		efi_fake_mems[nr_fake_mem].range.end = start + mem_size - 1;
10962306a36Sopenharmony_ci		efi_fake_mems[nr_fake_mem].attribute = attribute;
11062306a36Sopenharmony_ci		nr_fake_mem++;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci		if (*p == ',')
11362306a36Sopenharmony_ci			p++;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	sort(efi_fake_mems, nr_fake_mem, sizeof(struct efi_mem_range),
11762306a36Sopenharmony_ci	     cmp_fake_mem, NULL);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	for (i = 0; i < nr_fake_mem; i++)
12062306a36Sopenharmony_ci		pr_info("efi_fake_mem: add attr=0x%016llx to [mem 0x%016llx-0x%016llx]",
12162306a36Sopenharmony_ci			efi_fake_mems[i].attribute, efi_fake_mems[i].range.start,
12262306a36Sopenharmony_ci			efi_fake_mems[i].range.end);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	return *p == '\0' ? 0 : -EINVAL;
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ciearly_param("efi_fake_mem", setup_fake_mem);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_civoid __init efi_fake_memmap_early(void)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	int i;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/*
13462306a36Sopenharmony_ci	 * The late efi_fake_mem() call can handle all requests if
13562306a36Sopenharmony_ci	 * EFI_MEMORY_SP support is disabled.
13662306a36Sopenharmony_ci	 */
13762306a36Sopenharmony_ci	if (!efi_soft_reserve_enabled())
13862306a36Sopenharmony_ci		return;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!efi_enabled(EFI_MEMMAP) || !nr_fake_mem)
14162306a36Sopenharmony_ci		return;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	/*
14462306a36Sopenharmony_ci	 * Given that efi_fake_memmap() needs to perform memblock
14562306a36Sopenharmony_ci	 * allocations it needs to run after e820__memblock_setup().
14662306a36Sopenharmony_ci	 * However, if efi_fake_mem specifies EFI_MEMORY_SP for a given
14762306a36Sopenharmony_ci	 * address range that potentially needs to mark the memory as
14862306a36Sopenharmony_ci	 * reserved prior to e820__memblock_setup(). Update e820
14962306a36Sopenharmony_ci	 * directly if EFI_MEMORY_SP is specified for an
15062306a36Sopenharmony_ci	 * EFI_CONVENTIONAL_MEMORY descriptor.
15162306a36Sopenharmony_ci	 */
15262306a36Sopenharmony_ci	for (i = 0; i < nr_fake_mem; i++) {
15362306a36Sopenharmony_ci		struct efi_mem_range *mem = &efi_fake_mems[i];
15462306a36Sopenharmony_ci		efi_memory_desc_t *md;
15562306a36Sopenharmony_ci		u64 m_start, m_end;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci		if ((mem->attribute & EFI_MEMORY_SP) == 0)
15862306a36Sopenharmony_ci			continue;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		m_start = mem->range.start;
16162306a36Sopenharmony_ci		m_end = mem->range.end;
16262306a36Sopenharmony_ci		for_each_efi_memory_desc(md) {
16362306a36Sopenharmony_ci			u64 start, end, size;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci			if (md->type != EFI_CONVENTIONAL_MEMORY)
16662306a36Sopenharmony_ci				continue;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci			start = md->phys_addr;
16962306a36Sopenharmony_ci			end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - 1;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci			if (m_start <= end && m_end >= start)
17262306a36Sopenharmony_ci				/* fake range overlaps descriptor */;
17362306a36Sopenharmony_ci			else
17462306a36Sopenharmony_ci				continue;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci			/*
17762306a36Sopenharmony_ci			 * Trim the boundary of the e820 update to the
17862306a36Sopenharmony_ci			 * descriptor in case the fake range overlaps
17962306a36Sopenharmony_ci			 * !EFI_CONVENTIONAL_MEMORY
18062306a36Sopenharmony_ci			 */
18162306a36Sopenharmony_ci			start = max(start, m_start);
18262306a36Sopenharmony_ci			end = min(end, m_end);
18362306a36Sopenharmony_ci			size = end - start + 1;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci			if (end <= start)
18662306a36Sopenharmony_ci				continue;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci			/*
18962306a36Sopenharmony_ci			 * Ensure each efi_fake_mem instance results in
19062306a36Sopenharmony_ci			 * a unique e820 resource
19162306a36Sopenharmony_ci			 */
19262306a36Sopenharmony_ci			e820__range_remove(start, size, E820_TYPE_RAM, 1);
19362306a36Sopenharmony_ci			e820__range_add(start, size, E820_TYPE_SOFT_RESERVED);
19462306a36Sopenharmony_ci			e820__update_table(e820_table);
19562306a36Sopenharmony_ci		}
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci}
198