dc71555507
Consistently use boot_printk() everywhere instead of sclp_early_printk() at some places. For some places it was required (e.g. als.c), in order to stay in code compiled for the same architecture level, for other places it is not obvious why sclp_early_printk() was used instead of decompressor_printk(). Given that the whole decompressor code is compiled for the same architecture level, there is no requirement left to use different printk functions. Reviewed-by: Sven Schnelle <svens@linux.ibm.com> Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
329 lines
8.9 KiB
C
329 lines
8.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/processor.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <asm/physmem_info.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/boot_data.h>
|
|
#include <asm/sparsemem.h>
|
|
#include <asm/sections.h>
|
|
#include <asm/setup.h>
|
|
#include <asm/sclp.h>
|
|
#include <asm/uv.h>
|
|
#include "decompressor.h"
|
|
#include "boot.h"
|
|
|
|
struct physmem_info __bootdata(physmem_info);
|
|
static unsigned int physmem_alloc_ranges;
|
|
static unsigned long physmem_alloc_pos;
|
|
|
|
/* up to 256 storage elements, 1020 subincrements each */
|
|
#define ENTRIES_EXTENDED_MAX \
|
|
(256 * (1020 / 2) * sizeof(struct physmem_range))
|
|
|
|
static struct physmem_range *__get_physmem_range_ptr(u32 n)
|
|
{
|
|
if (n < MEM_INLINED_ENTRIES)
|
|
return &physmem_info.online[n];
|
|
if (unlikely(!physmem_info.online_extended)) {
|
|
physmem_info.online_extended = (struct physmem_range *)physmem_alloc_range(
|
|
RR_MEM_DETECT_EXTENDED, ENTRIES_EXTENDED_MAX, sizeof(long), 0,
|
|
physmem_alloc_pos, true);
|
|
}
|
|
return &physmem_info.online_extended[n - MEM_INLINED_ENTRIES];
|
|
}
|
|
|
|
/*
|
|
* sequential calls to add_physmem_online_range with adjacent memory ranges
|
|
* are merged together into single memory range.
|
|
*/
|
|
void add_physmem_online_range(u64 start, u64 end)
|
|
{
|
|
struct physmem_range *range;
|
|
|
|
if (physmem_info.range_count) {
|
|
range = __get_physmem_range_ptr(physmem_info.range_count - 1);
|
|
if (range->end == start) {
|
|
range->end = end;
|
|
return;
|
|
}
|
|
}
|
|
|
|
range = __get_physmem_range_ptr(physmem_info.range_count);
|
|
range->start = start;
|
|
range->end = end;
|
|
physmem_info.range_count++;
|
|
}
|
|
|
|
static int __diag260(unsigned long rx1, unsigned long rx2)
|
|
{
|
|
unsigned long reg1, reg2, ry;
|
|
union register_pair rx;
|
|
psw_t old;
|
|
int rc;
|
|
|
|
rx.even = rx1;
|
|
rx.odd = rx2;
|
|
ry = 0x10; /* storage configuration */
|
|
rc = -1; /* fail */
|
|
asm volatile(
|
|
" mvc 0(16,%[psw_old]),0(%[psw_pgm])\n"
|
|
" epsw %[reg1],%[reg2]\n"
|
|
" st %[reg1],0(%[psw_pgm])\n"
|
|
" st %[reg2],4(%[psw_pgm])\n"
|
|
" larl %[reg1],1f\n"
|
|
" stg %[reg1],8(%[psw_pgm])\n"
|
|
" diag %[rx],%[ry],0x260\n"
|
|
" ipm %[rc]\n"
|
|
" srl %[rc],28\n"
|
|
"1: mvc 0(16,%[psw_pgm]),0(%[psw_old])\n"
|
|
: [reg1] "=&d" (reg1),
|
|
[reg2] "=&a" (reg2),
|
|
[rc] "+&d" (rc),
|
|
[ry] "+&d" (ry),
|
|
"+Q" (get_lowcore()->program_new_psw),
|
|
"=Q" (old)
|
|
: [rx] "d" (rx.pair),
|
|
[psw_old] "a" (&old),
|
|
[psw_pgm] "a" (&get_lowcore()->program_new_psw)
|
|
: "cc", "memory");
|
|
return rc == 0 ? ry : -1;
|
|
}
|
|
|
|
static int diag260(void)
|
|
{
|
|
int rc, i;
|
|
|
|
struct {
|
|
unsigned long start;
|
|
unsigned long end;
|
|
} storage_extents[8] __aligned(16); /* VM supports up to 8 extends */
|
|
|
|
memset(storage_extents, 0, sizeof(storage_extents));
|
|
rc = __diag260((unsigned long)storage_extents, sizeof(storage_extents));
|
|
if (rc == -1)
|
|
return -1;
|
|
|
|
for (i = 0; i < min_t(int, rc, ARRAY_SIZE(storage_extents)); i++)
|
|
add_physmem_online_range(storage_extents[i].start, storage_extents[i].end + 1);
|
|
return 0;
|
|
}
|
|
|
|
static int tprot(unsigned long addr)
|
|
{
|
|
unsigned long reg1, reg2;
|
|
int rc = -EFAULT;
|
|
psw_t old;
|
|
|
|
asm volatile(
|
|
" mvc 0(16,%[psw_old]),0(%[psw_pgm])\n"
|
|
" epsw %[reg1],%[reg2]\n"
|
|
" st %[reg1],0(%[psw_pgm])\n"
|
|
" st %[reg2],4(%[psw_pgm])\n"
|
|
" larl %[reg1],1f\n"
|
|
" stg %[reg1],8(%[psw_pgm])\n"
|
|
" tprot 0(%[addr]),0\n"
|
|
" ipm %[rc]\n"
|
|
" srl %[rc],28\n"
|
|
"1: mvc 0(16,%[psw_pgm]),0(%[psw_old])\n"
|
|
: [reg1] "=&d" (reg1),
|
|
[reg2] "=&a" (reg2),
|
|
[rc] "+&d" (rc),
|
|
"=Q" (get_lowcore()->program_new_psw.addr),
|
|
"=Q" (old)
|
|
: [psw_old] "a" (&old),
|
|
[psw_pgm] "a" (&get_lowcore()->program_new_psw),
|
|
[addr] "a" (addr)
|
|
: "cc", "memory");
|
|
return rc;
|
|
}
|
|
|
|
static unsigned long search_mem_end(void)
|
|
{
|
|
unsigned long range = 1 << (MAX_PHYSMEM_BITS - 20); /* in 1MB blocks */
|
|
unsigned long offset = 0;
|
|
unsigned long pivot;
|
|
|
|
while (range > 1) {
|
|
range >>= 1;
|
|
pivot = offset + range;
|
|
if (!tprot(pivot << 20))
|
|
offset = pivot;
|
|
}
|
|
return (offset + 1) << 20;
|
|
}
|
|
|
|
unsigned long detect_max_physmem_end(void)
|
|
{
|
|
unsigned long max_physmem_end = 0;
|
|
|
|
if (!sclp_early_get_memsize(&max_physmem_end)) {
|
|
physmem_info.info_source = MEM_DETECT_SCLP_READ_INFO;
|
|
} else {
|
|
max_physmem_end = search_mem_end();
|
|
physmem_info.info_source = MEM_DETECT_BIN_SEARCH;
|
|
}
|
|
return max_physmem_end;
|
|
}
|
|
|
|
void detect_physmem_online_ranges(unsigned long max_physmem_end)
|
|
{
|
|
if (!sclp_early_read_storage_info()) {
|
|
physmem_info.info_source = MEM_DETECT_SCLP_STOR_INFO;
|
|
} else if (!diag260()) {
|
|
physmem_info.info_source = MEM_DETECT_DIAG260;
|
|
} else if (max_physmem_end) {
|
|
add_physmem_online_range(0, max_physmem_end);
|
|
}
|
|
}
|
|
|
|
void physmem_set_usable_limit(unsigned long limit)
|
|
{
|
|
physmem_info.usable = limit;
|
|
physmem_alloc_pos = limit;
|
|
}
|
|
|
|
static void die_oom(unsigned long size, unsigned long align, unsigned long min, unsigned long max)
|
|
{
|
|
unsigned long start, end, total_mem = 0, total_reserved_mem = 0;
|
|
struct reserved_range *range;
|
|
enum reserved_range_type t;
|
|
int i;
|
|
|
|
boot_printk("Linux version %s\n", kernel_version);
|
|
if (!is_prot_virt_guest() && early_command_line[0])
|
|
boot_printk("Kernel command line: %s\n", early_command_line);
|
|
boot_printk("Out of memory allocating %lx bytes %lx aligned in range %lx:%lx\n",
|
|
size, align, min, max);
|
|
boot_printk("Reserved memory ranges:\n");
|
|
for_each_physmem_reserved_range(t, range, &start, &end) {
|
|
boot_printk("%016lx %016lx %s\n", start, end, get_rr_type_name(t));
|
|
total_reserved_mem += end - start;
|
|
}
|
|
boot_printk("Usable online memory ranges (info source: %s [%x]):\n",
|
|
get_physmem_info_source(), physmem_info.info_source);
|
|
for_each_physmem_usable_range(i, &start, &end) {
|
|
boot_printk("%016lx %016lx\n", start, end);
|
|
total_mem += end - start;
|
|
}
|
|
boot_printk("Usable online memory total: %lx Reserved: %lx Free: %lx\n",
|
|
total_mem, total_reserved_mem,
|
|
total_mem > total_reserved_mem ? total_mem - total_reserved_mem : 0);
|
|
print_stacktrace(current_frame_address());
|
|
boot_printk("\n\n -- System halted\n");
|
|
disabled_wait();
|
|
}
|
|
|
|
void physmem_reserve(enum reserved_range_type type, unsigned long addr, unsigned long size)
|
|
{
|
|
physmem_info.reserved[type].start = addr;
|
|
physmem_info.reserved[type].end = addr + size;
|
|
}
|
|
|
|
void physmem_free(enum reserved_range_type type)
|
|
{
|
|
physmem_info.reserved[type].start = 0;
|
|
physmem_info.reserved[type].end = 0;
|
|
}
|
|
|
|
static bool __physmem_alloc_intersects(unsigned long addr, unsigned long size,
|
|
unsigned long *intersection_start)
|
|
{
|
|
unsigned long res_addr, res_size;
|
|
int t;
|
|
|
|
for (t = 0; t < RR_MAX; t++) {
|
|
if (!get_physmem_reserved(t, &res_addr, &res_size))
|
|
continue;
|
|
if (intersects(addr, size, res_addr, res_size)) {
|
|
*intersection_start = res_addr;
|
|
return true;
|
|
}
|
|
}
|
|
return ipl_report_certs_intersects(addr, size, intersection_start);
|
|
}
|
|
|
|
static unsigned long __physmem_alloc_range(unsigned long size, unsigned long align,
|
|
unsigned long min, unsigned long max,
|
|
unsigned int from_ranges, unsigned int *ranges_left,
|
|
bool die_on_oom)
|
|
{
|
|
unsigned int nranges = from_ranges ?: physmem_info.range_count;
|
|
unsigned long range_start, range_end;
|
|
unsigned long intersection_start;
|
|
unsigned long addr, pos = max;
|
|
|
|
align = max(align, 8UL);
|
|
while (nranges) {
|
|
__get_physmem_range(nranges - 1, &range_start, &range_end, false);
|
|
pos = min(range_end, pos);
|
|
|
|
if (round_up(min, align) + size > pos)
|
|
break;
|
|
addr = round_down(pos - size, align);
|
|
if (range_start > addr) {
|
|
nranges--;
|
|
continue;
|
|
}
|
|
if (__physmem_alloc_intersects(addr, size, &intersection_start)) {
|
|
pos = intersection_start;
|
|
continue;
|
|
}
|
|
|
|
if (ranges_left)
|
|
*ranges_left = nranges;
|
|
return addr;
|
|
}
|
|
if (die_on_oom)
|
|
die_oom(size, align, min, max);
|
|
return 0;
|
|
}
|
|
|
|
unsigned long physmem_alloc_range(enum reserved_range_type type, unsigned long size,
|
|
unsigned long align, unsigned long min, unsigned long max,
|
|
bool die_on_oom)
|
|
{
|
|
unsigned long addr;
|
|
|
|
max = min(max, physmem_alloc_pos);
|
|
addr = __physmem_alloc_range(size, align, min, max, 0, NULL, die_on_oom);
|
|
if (addr)
|
|
physmem_reserve(type, addr, size);
|
|
return addr;
|
|
}
|
|
|
|
unsigned long physmem_alloc_top_down(enum reserved_range_type type, unsigned long size,
|
|
unsigned long align)
|
|
{
|
|
struct reserved_range *range = &physmem_info.reserved[type];
|
|
struct reserved_range *new_range;
|
|
unsigned int ranges_left;
|
|
unsigned long addr;
|
|
|
|
addr = __physmem_alloc_range(size, align, 0, physmem_alloc_pos, physmem_alloc_ranges,
|
|
&ranges_left, true);
|
|
/* if not a consecutive allocation of the same type or first allocation */
|
|
if (range->start != addr + size) {
|
|
if (range->end) {
|
|
physmem_alloc_pos = __physmem_alloc_range(
|
|
sizeof(struct reserved_range), 0, 0, physmem_alloc_pos,
|
|
physmem_alloc_ranges, &ranges_left, true);
|
|
new_range = (struct reserved_range *)physmem_alloc_pos;
|
|
*new_range = *range;
|
|
range->chain = new_range;
|
|
addr = __physmem_alloc_range(size, align, 0, physmem_alloc_pos,
|
|
ranges_left, &ranges_left, true);
|
|
}
|
|
range->end = addr + size;
|
|
}
|
|
range->start = addr;
|
|
physmem_alloc_pos = addr;
|
|
physmem_alloc_ranges = ranges_left;
|
|
return addr;
|
|
}
|
|
|
|
unsigned long get_physmem_alloc_pos(void)
|
|
{
|
|
return physmem_alloc_pos;
|
|
}
|