linux/arch/powerpc/kernel/machine_kexec_file_64.c
Tom Lendacky 1d2e733b13 resource: Provide resource struct in resource walk callback
In preperation for a new function that will need additional resource
information during the resource walk, update the resource walk callback to
pass the resource structure.  Since the current callback start and end
arguments are pulled from the resource structure, the callback functions
can obtain them from the resource structure directly.

Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Borislav Petkov <bp@suse.de>
Tested-by: Borislav Petkov <bp@suse.de>
Cc: kvm@vger.kernel.org
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: linuxppc-dev@lists.ozlabs.org
Link: https://lkml.kernel.org/r/20171020143059.3291-10-brijesh.singh@amd.com
2017-11-07 15:35:57 +01:00

354 lines
9.5 KiB
C

/*
* 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) 2016 IBM Corporation
*
* Based on kexec-tools' kexec-elf-ppc64.c, fs2dt.c.
* Heavily modified for the kernel by
* Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation (version 2 of the License).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/slab.h>
#include <linux/kexec.h>
#include <linux/memblock.h>
#include <linux/of_fdt.h>
#include <linux/libfdt.h>
#include <asm/ima.h>
#define SLAVE_CODE_SIZE 256
static struct kexec_file_ops *kexec_file_loaders[] = {
&kexec_elf64_ops,
};
int arch_kexec_kernel_image_probe(struct kimage *image, void *buf,
unsigned long buf_len)
{
int i, ret = -ENOEXEC;
struct kexec_file_ops *fops;
/* We don't support crash kernels yet. */
if (image->type == KEXEC_TYPE_CRASH)
return -ENOTSUPP;
for (i = 0; i < ARRAY_SIZE(kexec_file_loaders); i++) {
fops = kexec_file_loaders[i];
if (!fops || !fops->probe)
continue;
ret = fops->probe(buf, buf_len);
if (!ret) {
image->fops = fops;
return ret;
}
}
return ret;
}
void *arch_kexec_kernel_image_load(struct kimage *image)
{
if (!image->fops || !image->fops->load)
return ERR_PTR(-ENOEXEC);
return image->fops->load(image, image->kernel_buf,
image->kernel_buf_len, image->initrd_buf,
image->initrd_buf_len, image->cmdline_buf,
image->cmdline_buf_len);
}
int arch_kimage_file_post_load_cleanup(struct kimage *image)
{
if (!image->fops || !image->fops->cleanup)
return 0;
return image->fops->cleanup(image->image_loader_data);
}
/**
* arch_kexec_walk_mem - call func(data) for each unreserved memory block
* @kbuf: Context info for the search. Also passed to @func.
* @func: Function to call for each memory block.
*
* This function is used by kexec_add_buffer and kexec_locate_mem_hole
* to find unreserved memory to load kexec segments into.
*
* Return: The memory walk will stop when func returns a non-zero value
* and that value will be returned. If all free regions are visited without
* func returning non-zero, then zero will be returned.
*/
int arch_kexec_walk_mem(struct kexec_buf *kbuf,
int (*func)(struct resource *, void *))
{
int ret = 0;
u64 i;
phys_addr_t mstart, mend;
struct resource res = { };
if (kbuf->top_down) {
for_each_free_mem_range_reverse(i, NUMA_NO_NODE, 0,
&mstart, &mend, NULL) {
/*
* In memblock, end points to the first byte after the
* range while in kexec, end points to the last byte
* in the range.
*/
res.start = mstart;
res.end = mend - 1;
ret = func(&res, kbuf);
if (ret)
break;
}
} else {
for_each_free_mem_range(i, NUMA_NO_NODE, 0, &mstart, &mend,
NULL) {
/*
* In memblock, end points to the first byte after the
* range while in kexec, end points to the last byte
* in the range.
*/
res.start = mstart;
res.end = mend - 1;
ret = func(&res, kbuf);
if (ret)
break;
}
}
return ret;
}
/**
* setup_purgatory - initialize the purgatory's global variables
* @image: kexec image.
* @slave_code: Slave code for the purgatory.
* @fdt: Flattened device tree for the next kernel.
* @kernel_load_addr: Address where the kernel is loaded.
* @fdt_load_addr: Address where the flattened device tree is loaded.
*
* Return: 0 on success, or negative errno on error.
*/
int setup_purgatory(struct kimage *image, const void *slave_code,
const void *fdt, unsigned long kernel_load_addr,
unsigned long fdt_load_addr)
{
unsigned int *slave_code_buf, master_entry;
int ret;
slave_code_buf = kmalloc(SLAVE_CODE_SIZE, GFP_KERNEL);
if (!slave_code_buf)
return -ENOMEM;
/* Get the slave code from the new kernel and put it in purgatory. */
ret = kexec_purgatory_get_set_symbol(image, "purgatory_start",
slave_code_buf, SLAVE_CODE_SIZE,
true);
if (ret) {
kfree(slave_code_buf);
return ret;
}
master_entry = slave_code_buf[0];
memcpy(slave_code_buf, slave_code, SLAVE_CODE_SIZE);
slave_code_buf[0] = master_entry;
ret = kexec_purgatory_get_set_symbol(image, "purgatory_start",
slave_code_buf, SLAVE_CODE_SIZE,
false);
kfree(slave_code_buf);
ret = kexec_purgatory_get_set_symbol(image, "kernel", &kernel_load_addr,
sizeof(kernel_load_addr), false);
if (ret)
return ret;
ret = kexec_purgatory_get_set_symbol(image, "dt_offset", &fdt_load_addr,
sizeof(fdt_load_addr), false);
if (ret)
return ret;
return 0;
}
/**
* delete_fdt_mem_rsv - delete memory reservation with given address and size
*
* Return: 0 on success, or negative errno on error.
*/
int delete_fdt_mem_rsv(void *fdt, unsigned long start, unsigned long size)
{
int i, ret, num_rsvs = fdt_num_mem_rsv(fdt);
for (i = 0; i < num_rsvs; i++) {
uint64_t rsv_start, rsv_size;
ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size);
if (ret) {
pr_err("Malformed device tree.\n");
return -EINVAL;
}
if (rsv_start == start && rsv_size == size) {
ret = fdt_del_mem_rsv(fdt, i);
if (ret) {
pr_err("Error deleting device tree reservation.\n");
return -EINVAL;
}
return 0;
}
}
return -ENOENT;
}
/*
* setup_new_fdt - modify /chosen and memory reservation for the next kernel
* @image: kexec image being loaded.
* @fdt: Flattened device tree for the next kernel.
* @initrd_load_addr: Address where the next initrd will be loaded.
* @initrd_len: Size of the next initrd, or 0 if there will be none.
* @cmdline: Command line for the next kernel, or NULL if there will
* be none.
*
* Return: 0 on success, or negative errno on error.
*/
int setup_new_fdt(const struct kimage *image, void *fdt,
unsigned long initrd_load_addr, unsigned long initrd_len,
const char *cmdline)
{
int ret, chosen_node;
const void *prop;
/* Remove memory reservation for the current device tree. */
ret = delete_fdt_mem_rsv(fdt, __pa(initial_boot_params),
fdt_totalsize(initial_boot_params));
if (ret == 0)
pr_debug("Removed old device tree reservation.\n");
else if (ret != -ENOENT)
return ret;
chosen_node = fdt_path_offset(fdt, "/chosen");
if (chosen_node == -FDT_ERR_NOTFOUND) {
chosen_node = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"),
"chosen");
if (chosen_node < 0) {
pr_err("Error creating /chosen.\n");
return -EINVAL;
}
} else if (chosen_node < 0) {
pr_err("Malformed device tree: error reading /chosen.\n");
return -EINVAL;
}
/* Did we boot using an initrd? */
prop = fdt_getprop(fdt, chosen_node, "linux,initrd-start", NULL);
if (prop) {
uint64_t tmp_start, tmp_end, tmp_size;
tmp_start = fdt64_to_cpu(*((const fdt64_t *) prop));
prop = fdt_getprop(fdt, chosen_node, "linux,initrd-end", NULL);
if (!prop) {
pr_err("Malformed device tree.\n");
return -EINVAL;
}
tmp_end = fdt64_to_cpu(*((const fdt64_t *) prop));
/*
* kexec reserves exact initrd size, while firmware may
* reserve a multiple of PAGE_SIZE, so check for both.
*/
tmp_size = tmp_end - tmp_start;
ret = delete_fdt_mem_rsv(fdt, tmp_start, tmp_size);
if (ret == -ENOENT)
ret = delete_fdt_mem_rsv(fdt, tmp_start,
round_up(tmp_size, PAGE_SIZE));
if (ret == 0)
pr_debug("Removed old initrd reservation.\n");
else if (ret != -ENOENT)
return ret;
/* If there's no new initrd, delete the old initrd's info. */
if (initrd_len == 0) {
ret = fdt_delprop(fdt, chosen_node,
"linux,initrd-start");
if (ret) {
pr_err("Error deleting linux,initrd-start.\n");
return -EINVAL;
}
ret = fdt_delprop(fdt, chosen_node, "linux,initrd-end");
if (ret) {
pr_err("Error deleting linux,initrd-end.\n");
return -EINVAL;
}
}
}
if (initrd_len) {
ret = fdt_setprop_u64(fdt, chosen_node,
"linux,initrd-start",
initrd_load_addr);
if (ret < 0) {
pr_err("Error setting up the new device tree.\n");
return -EINVAL;
}
/* initrd-end is the first address after the initrd image. */
ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-end",
initrd_load_addr + initrd_len);
if (ret < 0) {
pr_err("Error setting up the new device tree.\n");
return -EINVAL;
}
ret = fdt_add_mem_rsv(fdt, initrd_load_addr, initrd_len);
if (ret) {
pr_err("Error reserving initrd memory: %s\n",
fdt_strerror(ret));
return -EINVAL;
}
}
if (cmdline != NULL) {
ret = fdt_setprop_string(fdt, chosen_node, "bootargs", cmdline);
if (ret < 0) {
pr_err("Error setting up the new device tree.\n");
return -EINVAL;
}
} else {
ret = fdt_delprop(fdt, chosen_node, "bootargs");
if (ret && ret != -FDT_ERR_NOTFOUND) {
pr_err("Error deleting bootargs.\n");
return -EINVAL;
}
}
ret = setup_ima_buffer(image, fdt, chosen_node);
if (ret) {
pr_err("Error setting up the new device tree.\n");
return ret;
}
ret = fdt_setprop(fdt, chosen_node, "linux,booted-from-kexec", NULL, 0);
if (ret) {
pr_err("Error setting up the new device tree.\n");
return -EINVAL;
}
return 0;
}