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