// SPDX-License-Identifier: GPL-2.0-only
/*
* ppc64 code to implement the kexec_file_load syscall
*
* Copyright (C) 2004 Adam Litke (agl@us.ibm.com)
* Copyright (C) 2004 IBM Corp.
* Copyright (C) 2004,2005 Milton D Miller II, IBM Corporation
* Copyright (C) 2005 R Sharada (sharada@in.ibm.com)
* Copyright (C) 2006 Mohan Kumar M (mohan@in.ibm.com)
* Copyright (C) 2020 IBM Corporation
*
* Based on kexec-tools' kexec-ppc64.c, kexec-elf-rel-ppc64.c, fs2dt.c.
* Heavily modified for the kernel by
* Hari Bathini, IBM Corporation.
*/
#include <linux/kexec.h>
#include <linux/of_fdt.h>
#include <linux/libfdt.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/memblock.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/setup.h>
#include <asm/drmem.h>
#include <asm/firmware.h>
#include <asm/kexec_ranges.h>
#include <asm/crashdump-ppc64.h>
#include <asm/mmzone.h>
#include <asm/iommu.h>
#include <asm/prom.h>
#include <asm/plpks.h>
#include <asm/cputhreads.h>
struct umem_info {
__be64 *buf; /* data buffer for usable-memory property */
u32 size; /* size allocated for the data buffer */
u32 max_entries; /* maximum no. of entries */
u32 idx; /* index of current entry */
/* usable memory ranges to look up */
unsigned int nr_ranges;
const struct range *ranges;
};
const struct kexec_file_ops * const kexec_file_loaders[] = {
&kexec_elf64_ops,
NULL
};
/**
* __locate_mem_hole_top_down - Looks top down for a large enough memory hole
* in the memory regions between buf_min & buf_max
* for the buffer. If found, sets kbuf->mem.
* @kbuf: Buffer contents and memory parameters.
* @buf_min: Minimum address for the buffer.
* @buf_max: Maximum address for the buffer.
*
* Returns 0 on success, negative errno on error.
*/
static int __locate_mem_hole_top_down(struct kexec_buf *kbuf,
u64 buf_min, u64 buf_max)
{
int ret = -EADDRNOTAVAIL;
phys_addr_t start, end;
u64 i;
for_each_mem_range_rev(i, &start, &end) {
/*
* memblock uses [start, end) convention while it is
* [start, end] here. Fix the off-by-one to have the
* same convention.
*/
end -= 1;
if (start > buf_max)
continue;
/* Memory hole not found */
if (end < buf_min)
break;
/* Adjust memory region based on the given range */
if (start < buf_min)
start = buf_min;
if (end > buf_max)
end = buf_max;
start = ALIGN(start, kbuf->buf_align);
if (start < end && (end - start + 1) >= kbuf->memsz) {
/* Suitable memory range found. Set kbuf->mem */
kbuf->mem = ALIGN_DOWN(end - kbuf->memsz + 1,
kbuf->buf_align);
ret = 0;
break;
}
}
return ret;
}
/**
* locate_mem_hole_top_down_ppc64 - Skip special memory regions to find a
* suitable buffer with top down approach.
* @kbuf: Buffer contents and memory parameters.
* @buf_min: Minimum address for the buffer.
* @buf_max: Maximum address for the buffer.
* @emem: Exclude memory ranges.
*
* Returns 0 on success, negative errno on error.
*/
static int locate_mem_hole_top_down_ppc64(struct kexec_buf *kbuf,
u64 buf_min, u64 buf_max,
const struct crash_mem *emem)
{
int i, ret = 0, err = -EADDRNOTAVAIL;
u64 start, end, tmin, tmax;
tmax = buf_max;
for (i = (emem->nr_ranges - 1); i >= 0; i--) {
start = emem->ranges[i].start;
end = emem->ranges[i].end;
if (start > tmax)
continue;
if (end < tmax) {
tmin = (end < buf_min ? buf_min : end + 1);
ret = __locate_mem_hole_top_down(kbuf, tmin, tmax);
if (!ret)
return 0;
}
tmax = start - 1;
if (tmax < buf_min) {
ret = err;
break;
}
ret = 0;
}
if (!ret) {
tmin = buf_min;
ret = __locate_mem_hole_top_down(kbuf, tmin, tmax);
}
return ret;
}
/**
* __locate_mem_hole_bottom_up - Looks bottom up for a large enough memory hole
* in the memory regions between buf_min & buf_max
* for the buffer. If found, sets kbuf->mem.
* @kbuf: Buffer contents and memory parameters.
* @buf_min: Minimum address for the buffer.
* @buf_max: Maximum address for the buffer.
*
* Returns 0 on success, negative errno on error.
*/
static int __locate_mem_hole_bottom_up(struct kexec_buf *kbuf,
u64 buf_min, u64 buf_max)
{
int ret = -EADDRNOTAVAIL;
phys_addr_t start, end;
u64 i;
for_each_mem_range(i, &start, &end) {
/*
* memblock uses [start, end) convention while it is
* [start, end] here. Fix the off-by-one to have the
* same convention.
*/
end -= 1;
if (end < buf_min)
continue;
/* Memory hole not found */
if (start > buf_max)
break;
/* Adjust memory region based on the given range */
if (start < buf_min)
start = buf_min;
if (end > buf_max)
end = buf_max;
start = ALIGN(start, kbuf->buf_align);
if (start < end && (end - start + 1) >= kbuf->memsz) {
/* Suitable memory range found. Set kbuf->mem */