162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/processor.h>
362306a36Sopenharmony_ci#include <linux/errno.h>
462306a36Sopenharmony_ci#include <linux/init.h>
562306a36Sopenharmony_ci#include <asm/physmem_info.h>
662306a36Sopenharmony_ci#include <asm/stacktrace.h>
762306a36Sopenharmony_ci#include <asm/boot_data.h>
862306a36Sopenharmony_ci#include <asm/sparsemem.h>
962306a36Sopenharmony_ci#include <asm/sections.h>
1062306a36Sopenharmony_ci#include <asm/setup.h>
1162306a36Sopenharmony_ci#include <asm/sclp.h>
1262306a36Sopenharmony_ci#include <asm/uv.h>
1362306a36Sopenharmony_ci#include "decompressor.h"
1462306a36Sopenharmony_ci#include "boot.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistruct physmem_info __bootdata(physmem_info);
1762306a36Sopenharmony_cistatic unsigned int physmem_alloc_ranges;
1862306a36Sopenharmony_cistatic unsigned long physmem_alloc_pos;
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* up to 256 storage elements, 1020 subincrements each */
2162306a36Sopenharmony_ci#define ENTRIES_EXTENDED_MAX						       \
2262306a36Sopenharmony_ci	(256 * (1020 / 2) * sizeof(struct physmem_range))
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic struct physmem_range *__get_physmem_range_ptr(u32 n)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	if (n < MEM_INLINED_ENTRIES)
2762306a36Sopenharmony_ci		return &physmem_info.online[n];
2862306a36Sopenharmony_ci	if (unlikely(!physmem_info.online_extended)) {
2962306a36Sopenharmony_ci		physmem_info.online_extended = (struct physmem_range *)physmem_alloc_range(
3062306a36Sopenharmony_ci			RR_MEM_DETECT_EXTENDED, ENTRIES_EXTENDED_MAX, sizeof(long), 0,
3162306a36Sopenharmony_ci			physmem_alloc_pos, true);
3262306a36Sopenharmony_ci	}
3362306a36Sopenharmony_ci	return &physmem_info.online_extended[n - MEM_INLINED_ENTRIES];
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/*
3762306a36Sopenharmony_ci * sequential calls to add_physmem_online_range with adjacent memory ranges
3862306a36Sopenharmony_ci * are merged together into single memory range.
3962306a36Sopenharmony_ci */
4062306a36Sopenharmony_civoid add_physmem_online_range(u64 start, u64 end)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	struct physmem_range *range;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	if (physmem_info.range_count) {
4562306a36Sopenharmony_ci		range = __get_physmem_range_ptr(physmem_info.range_count - 1);
4662306a36Sopenharmony_ci		if (range->end == start) {
4762306a36Sopenharmony_ci			range->end = end;
4862306a36Sopenharmony_ci			return;
4962306a36Sopenharmony_ci		}
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	range = __get_physmem_range_ptr(physmem_info.range_count);
5362306a36Sopenharmony_ci	range->start = start;
5462306a36Sopenharmony_ci	range->end = end;
5562306a36Sopenharmony_ci	physmem_info.range_count++;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic int __diag260(unsigned long rx1, unsigned long rx2)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	unsigned long reg1, reg2, ry;
6162306a36Sopenharmony_ci	union register_pair rx;
6262306a36Sopenharmony_ci	psw_t old;
6362306a36Sopenharmony_ci	int rc;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	rx.even = rx1;
6662306a36Sopenharmony_ci	rx.odd	= rx2;
6762306a36Sopenharmony_ci	ry = 0x10; /* storage configuration */
6862306a36Sopenharmony_ci	rc = -1;   /* fail */
6962306a36Sopenharmony_ci	asm volatile(
7062306a36Sopenharmony_ci		"	mvc	0(16,%[psw_old]),0(%[psw_pgm])\n"
7162306a36Sopenharmony_ci		"	epsw	%[reg1],%[reg2]\n"
7262306a36Sopenharmony_ci		"	st	%[reg1],0(%[psw_pgm])\n"
7362306a36Sopenharmony_ci		"	st	%[reg2],4(%[psw_pgm])\n"
7462306a36Sopenharmony_ci		"	larl	%[reg1],1f\n"
7562306a36Sopenharmony_ci		"	stg	%[reg1],8(%[psw_pgm])\n"
7662306a36Sopenharmony_ci		"	diag	%[rx],%[ry],0x260\n"
7762306a36Sopenharmony_ci		"	ipm	%[rc]\n"
7862306a36Sopenharmony_ci		"	srl	%[rc],28\n"
7962306a36Sopenharmony_ci		"1:	mvc	0(16,%[psw_pgm]),0(%[psw_old])\n"
8062306a36Sopenharmony_ci		: [reg1] "=&d" (reg1),
8162306a36Sopenharmony_ci		  [reg2] "=&a" (reg2),
8262306a36Sopenharmony_ci		  [rc] "+&d" (rc),
8362306a36Sopenharmony_ci		  [ry] "+&d" (ry),
8462306a36Sopenharmony_ci		  "+Q" (S390_lowcore.program_new_psw),
8562306a36Sopenharmony_ci		  "=Q" (old)
8662306a36Sopenharmony_ci		: [rx] "d" (rx.pair),
8762306a36Sopenharmony_ci		  [psw_old] "a" (&old),
8862306a36Sopenharmony_ci		  [psw_pgm] "a" (&S390_lowcore.program_new_psw)
8962306a36Sopenharmony_ci		: "cc", "memory");
9062306a36Sopenharmony_ci	return rc == 0 ? ry : -1;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic int diag260(void)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	int rc, i;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	struct {
9862306a36Sopenharmony_ci		unsigned long start;
9962306a36Sopenharmony_ci		unsigned long end;
10062306a36Sopenharmony_ci	} storage_extents[8] __aligned(16); /* VM supports up to 8 extends */
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	memset(storage_extents, 0, sizeof(storage_extents));
10362306a36Sopenharmony_ci	rc = __diag260((unsigned long)storage_extents, sizeof(storage_extents));
10462306a36Sopenharmony_ci	if (rc == -1)
10562306a36Sopenharmony_ci		return -1;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	for (i = 0; i < min_t(int, rc, ARRAY_SIZE(storage_extents)); i++)
10862306a36Sopenharmony_ci		add_physmem_online_range(storage_extents[i].start, storage_extents[i].end + 1);
10962306a36Sopenharmony_ci	return 0;
11062306a36Sopenharmony_ci}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic int tprot(unsigned long addr)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	unsigned long reg1, reg2;
11562306a36Sopenharmony_ci	int rc = -EFAULT;
11662306a36Sopenharmony_ci	psw_t old;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	asm volatile(
11962306a36Sopenharmony_ci		"	mvc	0(16,%[psw_old]),0(%[psw_pgm])\n"
12062306a36Sopenharmony_ci		"	epsw	%[reg1],%[reg2]\n"
12162306a36Sopenharmony_ci		"	st	%[reg1],0(%[psw_pgm])\n"
12262306a36Sopenharmony_ci		"	st	%[reg2],4(%[psw_pgm])\n"
12362306a36Sopenharmony_ci		"	larl	%[reg1],1f\n"
12462306a36Sopenharmony_ci		"	stg	%[reg1],8(%[psw_pgm])\n"
12562306a36Sopenharmony_ci		"	tprot	0(%[addr]),0\n"
12662306a36Sopenharmony_ci		"	ipm	%[rc]\n"
12762306a36Sopenharmony_ci		"	srl	%[rc],28\n"
12862306a36Sopenharmony_ci		"1:	mvc	0(16,%[psw_pgm]),0(%[psw_old])\n"
12962306a36Sopenharmony_ci		: [reg1] "=&d" (reg1),
13062306a36Sopenharmony_ci		  [reg2] "=&a" (reg2),
13162306a36Sopenharmony_ci		  [rc] "+&d" (rc),
13262306a36Sopenharmony_ci		  "=Q" (S390_lowcore.program_new_psw.addr),
13362306a36Sopenharmony_ci		  "=Q" (old)
13462306a36Sopenharmony_ci		: [psw_old] "a" (&old),
13562306a36Sopenharmony_ci		  [psw_pgm] "a" (&S390_lowcore.program_new_psw),
13662306a36Sopenharmony_ci		  [addr] "a" (addr)
13762306a36Sopenharmony_ci		: "cc", "memory");
13862306a36Sopenharmony_ci	return rc;
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic unsigned long search_mem_end(void)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	unsigned long range = 1 << (MAX_PHYSMEM_BITS - 20); /* in 1MB blocks */
14462306a36Sopenharmony_ci	unsigned long offset = 0;
14562306a36Sopenharmony_ci	unsigned long pivot;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	while (range > 1) {
14862306a36Sopenharmony_ci		range >>= 1;
14962306a36Sopenharmony_ci		pivot = offset + range;
15062306a36Sopenharmony_ci		if (!tprot(pivot << 20))
15162306a36Sopenharmony_ci			offset = pivot;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci	return (offset + 1) << 20;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ciunsigned long detect_max_physmem_end(void)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	unsigned long max_physmem_end = 0;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (!sclp_early_get_memsize(&max_physmem_end)) {
16162306a36Sopenharmony_ci		physmem_info.info_source = MEM_DETECT_SCLP_READ_INFO;
16262306a36Sopenharmony_ci	} else {
16362306a36Sopenharmony_ci		max_physmem_end = search_mem_end();
16462306a36Sopenharmony_ci		physmem_info.info_source = MEM_DETECT_BIN_SEARCH;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci	return max_physmem_end;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_civoid detect_physmem_online_ranges(unsigned long max_physmem_end)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	if (!sclp_early_read_storage_info()) {
17262306a36Sopenharmony_ci		physmem_info.info_source = MEM_DETECT_SCLP_STOR_INFO;
17362306a36Sopenharmony_ci	} else if (!diag260()) {
17462306a36Sopenharmony_ci		physmem_info.info_source = MEM_DETECT_DIAG260;
17562306a36Sopenharmony_ci	} else if (max_physmem_end) {
17662306a36Sopenharmony_ci		add_physmem_online_range(0, max_physmem_end);
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_civoid physmem_set_usable_limit(unsigned long limit)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	physmem_info.usable = limit;
18362306a36Sopenharmony_ci	physmem_alloc_pos = limit;
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic void die_oom(unsigned long size, unsigned long align, unsigned long min, unsigned long max)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	unsigned long start, end, total_mem = 0, total_reserved_mem = 0;
18962306a36Sopenharmony_ci	struct reserved_range *range;
19062306a36Sopenharmony_ci	enum reserved_range_type t;
19162306a36Sopenharmony_ci	int i;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	decompressor_printk("Linux version %s\n", kernel_version);
19462306a36Sopenharmony_ci	if (!is_prot_virt_guest() && early_command_line[0])
19562306a36Sopenharmony_ci		decompressor_printk("Kernel command line: %s\n", early_command_line);
19662306a36Sopenharmony_ci	decompressor_printk("Out of memory allocating %lx bytes %lx aligned in range %lx:%lx\n",
19762306a36Sopenharmony_ci			    size, align, min, max);
19862306a36Sopenharmony_ci	decompressor_printk("Reserved memory ranges:\n");
19962306a36Sopenharmony_ci	for_each_physmem_reserved_range(t, range, &start, &end) {
20062306a36Sopenharmony_ci		decompressor_printk("%016lx %016lx %s\n", start, end, get_rr_type_name(t));
20162306a36Sopenharmony_ci		total_reserved_mem += end - start;
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci	decompressor_printk("Usable online memory ranges (info source: %s [%x]):\n",
20462306a36Sopenharmony_ci			    get_physmem_info_source(), physmem_info.info_source);
20562306a36Sopenharmony_ci	for_each_physmem_usable_range(i, &start, &end) {
20662306a36Sopenharmony_ci		decompressor_printk("%016lx %016lx\n", start, end);
20762306a36Sopenharmony_ci		total_mem += end - start;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci	decompressor_printk("Usable online memory total: %lx Reserved: %lx Free: %lx\n",
21062306a36Sopenharmony_ci			    total_mem, total_reserved_mem,
21162306a36Sopenharmony_ci			    total_mem > total_reserved_mem ? total_mem - total_reserved_mem : 0);
21262306a36Sopenharmony_ci	print_stacktrace(current_frame_address());
21362306a36Sopenharmony_ci	sclp_early_printk("\n\n -- System halted\n");
21462306a36Sopenharmony_ci	disabled_wait();
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_civoid physmem_reserve(enum reserved_range_type type, unsigned long addr, unsigned long size)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	physmem_info.reserved[type].start = addr;
22062306a36Sopenharmony_ci	physmem_info.reserved[type].end = addr + size;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_civoid physmem_free(enum reserved_range_type type)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	physmem_info.reserved[type].start = 0;
22662306a36Sopenharmony_ci	physmem_info.reserved[type].end = 0;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic bool __physmem_alloc_intersects(unsigned long addr, unsigned long size,
23062306a36Sopenharmony_ci				       unsigned long *intersection_start)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	unsigned long res_addr, res_size;
23362306a36Sopenharmony_ci	int t;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	for (t = 0; t < RR_MAX; t++) {
23662306a36Sopenharmony_ci		if (!get_physmem_reserved(t, &res_addr, &res_size))
23762306a36Sopenharmony_ci			continue;
23862306a36Sopenharmony_ci		if (intersects(addr, size, res_addr, res_size)) {
23962306a36Sopenharmony_ci			*intersection_start = res_addr;
24062306a36Sopenharmony_ci			return true;
24162306a36Sopenharmony_ci		}
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci	return ipl_report_certs_intersects(addr, size, intersection_start);
24462306a36Sopenharmony_ci}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic unsigned long __physmem_alloc_range(unsigned long size, unsigned long align,
24762306a36Sopenharmony_ci					   unsigned long min, unsigned long max,
24862306a36Sopenharmony_ci					   unsigned int from_ranges, unsigned int *ranges_left,
24962306a36Sopenharmony_ci					   bool die_on_oom)
25062306a36Sopenharmony_ci{
25162306a36Sopenharmony_ci	unsigned int nranges = from_ranges ?: physmem_info.range_count;
25262306a36Sopenharmony_ci	unsigned long range_start, range_end;
25362306a36Sopenharmony_ci	unsigned long intersection_start;
25462306a36Sopenharmony_ci	unsigned long addr, pos = max;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	align = max(align, 8UL);
25762306a36Sopenharmony_ci	while (nranges) {
25862306a36Sopenharmony_ci		__get_physmem_range(nranges - 1, &range_start, &range_end, false);
25962306a36Sopenharmony_ci		pos = min(range_end, pos);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci		if (round_up(min, align) + size > pos)
26262306a36Sopenharmony_ci			break;
26362306a36Sopenharmony_ci		addr = round_down(pos - size, align);
26462306a36Sopenharmony_ci		if (range_start > addr) {
26562306a36Sopenharmony_ci			nranges--;
26662306a36Sopenharmony_ci			continue;
26762306a36Sopenharmony_ci		}
26862306a36Sopenharmony_ci		if (__physmem_alloc_intersects(addr, size, &intersection_start)) {
26962306a36Sopenharmony_ci			pos = intersection_start;
27062306a36Sopenharmony_ci			continue;
27162306a36Sopenharmony_ci		}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci		if (ranges_left)
27462306a36Sopenharmony_ci			*ranges_left = nranges;
27562306a36Sopenharmony_ci		return addr;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci	if (die_on_oom)
27862306a36Sopenharmony_ci		die_oom(size, align, min, max);
27962306a36Sopenharmony_ci	return 0;
28062306a36Sopenharmony_ci}
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ciunsigned long physmem_alloc_range(enum reserved_range_type type, unsigned long size,
28362306a36Sopenharmony_ci				  unsigned long align, unsigned long min, unsigned long max,
28462306a36Sopenharmony_ci				  bool die_on_oom)
28562306a36Sopenharmony_ci{
28662306a36Sopenharmony_ci	unsigned long addr;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	max = min(max, physmem_alloc_pos);
28962306a36Sopenharmony_ci	addr = __physmem_alloc_range(size, align, min, max, 0, NULL, die_on_oom);
29062306a36Sopenharmony_ci	if (addr)
29162306a36Sopenharmony_ci		physmem_reserve(type, addr, size);
29262306a36Sopenharmony_ci	return addr;
29362306a36Sopenharmony_ci}
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ciunsigned long physmem_alloc_top_down(enum reserved_range_type type, unsigned long size,
29662306a36Sopenharmony_ci				     unsigned long align)
29762306a36Sopenharmony_ci{
29862306a36Sopenharmony_ci	struct reserved_range *range = &physmem_info.reserved[type];
29962306a36Sopenharmony_ci	struct reserved_range *new_range;
30062306a36Sopenharmony_ci	unsigned int ranges_left;
30162306a36Sopenharmony_ci	unsigned long addr;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	addr = __physmem_alloc_range(size, align, 0, physmem_alloc_pos, physmem_alloc_ranges,
30462306a36Sopenharmony_ci				     &ranges_left, true);
30562306a36Sopenharmony_ci	/* if not a consecutive allocation of the same type or first allocation */
30662306a36Sopenharmony_ci	if (range->start != addr + size) {
30762306a36Sopenharmony_ci		if (range->end) {
30862306a36Sopenharmony_ci			physmem_alloc_pos = __physmem_alloc_range(
30962306a36Sopenharmony_ci				sizeof(struct reserved_range), 0, 0, physmem_alloc_pos,
31062306a36Sopenharmony_ci				physmem_alloc_ranges, &ranges_left, true);
31162306a36Sopenharmony_ci			new_range = (struct reserved_range *)physmem_alloc_pos;
31262306a36Sopenharmony_ci			*new_range = *range;
31362306a36Sopenharmony_ci			range->chain = new_range;
31462306a36Sopenharmony_ci			addr = __physmem_alloc_range(size, align, 0, physmem_alloc_pos,
31562306a36Sopenharmony_ci						     ranges_left, &ranges_left, true);
31662306a36Sopenharmony_ci		}
31762306a36Sopenharmony_ci		range->end = addr + size;
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci	range->start = addr;
32062306a36Sopenharmony_ci	physmem_alloc_pos = addr;
32162306a36Sopenharmony_ci	physmem_alloc_ranges = ranges_left;
32262306a36Sopenharmony_ci	return addr;
32362306a36Sopenharmony_ci}
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ciunsigned long get_physmem_alloc_pos(void)
32662306a36Sopenharmony_ci{
32762306a36Sopenharmony_ci	return physmem_alloc_pos;
32862306a36Sopenharmony_ci}
329