blob: f8320265b45db91380f6e54d85c0d50aa6f8278d [file] [log] [blame]
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../../kexec.h"
#include "../../kexec-syscall.h"
#include <libfdt.h>
#include "ops.h"
#include "page.h"
#include "fixup_dtb.h"
#include "kexec-ppc.h"
const char proc_dts[] = "/proc/device-tree";
static void print_fdt_reserve_regions(char *blob_buf)
{
int i, num;
if (!kexec_debug)
return;
/* Print out a summary of the final reserve regions */
num = fdt_num_mem_rsv(blob_buf);
dbgprintf ("reserve regions: %d\n", num);
for (i = 0; i < num; i++) {
uint64_t offset, size;
if (fdt_get_mem_rsv(blob_buf, i, &offset, &size) == 0) {
dbgprintf("%d: offset: %llx, size: %llx\n", i, offset, size);
} else {
dbgprintf("Error retreiving reserved region\n");
}
}
}
static void fixup_nodes(char *nodes[])
{
int index = 0;
char *fname;
char *prop_name;
char *node_name;
void *node;
int len;
char *content;
off_t content_size;
int ret;
while (nodes[index]) {
len = asprintf(&fname, "%s%s", proc_dts, nodes[index]);
if (len < 0)
fatal("asprintf() failed\n");
content = slurp_file(fname, &content_size);
if (!content) {
fprintf(stderr, "Can't open %s: %s\n",
fname, strerror(errno));
exit(1);
}
prop_name = fname + len;
while (*prop_name != '/')
prop_name--;
*prop_name = '\0';
prop_name++;
node_name = fname + sizeof(proc_dts) - 1;
node = finddevice(node_name);
if (!node)
node = create_node(NULL, node_name + 1);
ret = setprop(node, prop_name, content, content_size);
if (ret < 0)
fatal("setprop of %s/%s size: %ld failed: %s\n",
node_name, prop_name, content_size,
fdt_strerror(ret));
free(content);
free(fname);
index++;
};
}
/*
* command line priority:
* - use the supplied command line
* - if none available use the command line from .dtb
* - if not available use the current command line
*/
static void fixup_cmdline(const char *cmdline)
{
void *chosen;
char *fixup_cmd_node[] = {
"/chosen/bootargs",
NULL,
};
chosen = finddevice("/chosen");
if (!cmdline) {
if (!chosen)
fixup_nodes(fixup_cmd_node);
} else {
if (!chosen)
chosen = create_node(NULL, "chosen");
setprop_str(chosen, "bootargs", cmdline);
}
return;
}
#define EXPAND_GRANULARITY 1024
static char *expand_buf(int minexpand, char *blob_buf, off_t *blob_size)
{
int size = fdt_totalsize(blob_buf);
int rc;
size = _ALIGN(size + minexpand, EXPAND_GRANULARITY);
blob_buf = realloc(blob_buf, size);
if (!blob_buf)
fatal("Couldn't find %d bytes to expand device tree\n\r", size);
rc = fdt_open_into(blob_buf, blob_buf, size);
if (rc != 0)
fatal("Couldn't expand fdt into new buffer: %s\n\r",
fdt_strerror(rc));
*blob_size = fdt_totalsize(blob_buf);
return blob_buf;
}
static void fixup_reserve_regions(struct kexec_info *info, char *blob_buf)
{
int ret, i;
int nodeoffset;
u64 val = 0;
/* If this is a KEXEC kernel we add all regions since they will
* all need to be saved */
if (info->kexec_flags & KEXEC_ON_CRASH) {
for (i = 0; i < info->nr_segments; i++) {
uint64_t address = (unsigned long)info->segment[i].mem;
uint64_t size = info->segment[i].memsz;
while ((i+1) < info->nr_segments &&
(address + size == (unsigned long)info->segment[i+1].mem)) {
size += info->segment[++i].memsz;
}
ret = fdt_add_mem_rsv(blob_buf, address, size);
if (ret) {
printf("%s: Error adding memory range to memreserve!\n",
fdt_strerror(ret));
goto out;
}
}
} else if (ramdisk || reuse_initrd) {
/* Otherwise we just add back the ramdisk and the device tree
* is already in the list */
ret = fdt_add_mem_rsv(blob_buf, ramdisk_base, ramdisk_size);
if (ret) {
printf("%s: Unable to add new reserved memory for initrd flat device tree\n",
fdt_strerror(ret));
goto out;
}
}
#if 0
/* XXX: Do not reserve spin-table for CPUs. */
/* Add reserve regions for cpu-release-addr */
nodeoffset = fdt_node_offset_by_prop_value(blob_buf, -1, "device_type", "cpu", 4);
while (nodeoffset != -FDT_ERR_NOTFOUND) {
const void *buf;
int sz, ret;
u64 tmp;
buf = fdt_getprop(blob_buf, nodeoffset, "cpu-release-addr", &sz);
if (buf) {
if (sz == 4) {
tmp = *(u32 *)buf;
} else if (sz == 8) {
tmp = *(u64 *)buf;
}
/* crude check to see if last value is repeated */
if (_ALIGN_DOWN(tmp, PAGE_SIZE) != _ALIGN_DOWN(val, PAGE_SIZE)) {
val = tmp;
ret = fdt_add_mem_rsv(blob_buf, _ALIGN_DOWN(val, PAGE_SIZE), PAGE_SIZE);
if (ret)
printf("%s: Unable to add reserve for cpu-release-addr!\n",
fdt_strerror(ret));
}
}
nodeoffset = fdt_node_offset_by_prop_value(blob_buf, nodeoffset,
"device_type", "cpu", 4);
}
#endif
out:
print_fdt_reserve_regions(blob_buf);
}
static void fixup_memory(struct kexec_info *info, char *blob_buf)
{
if (info->kexec_flags & KEXEC_ON_CRASH) {
int nodeoffset, len = 0;
u8 tmp[16];
const unsigned long *addrcell, *sizecell;
nodeoffset = fdt_path_offset(blob_buf, "/memory");
if (nodeoffset < 0) {
printf("Error searching for memory node!\n");
return;
}
addrcell = fdt_getprop(blob_buf, 0, "#address-cells", NULL);
/* use shifts and mask to ensure endianness */
if ((addrcell) && (*addrcell == 2)) {
tmp[0] = (crash_base >> 56) & 0xff;
tmp[1] = (crash_base >> 48) & 0xff;
tmp[2] = (crash_base >> 40) & 0xff;
tmp[3] = (crash_base >> 32) & 0xff;
tmp[4] = (crash_base >> 24) & 0xff;
tmp[5] = (crash_base >> 16) & 0xff;
tmp[6] = (crash_base >> 8) & 0xff;
tmp[7] = (crash_base ) & 0xff;
len = 8;
} else {
tmp[0] = (crash_base >> 24) & 0xff;
tmp[1] = (crash_base >> 16) & 0xff;
tmp[2] = (crash_base >> 8) & 0xff;
tmp[3] = (crash_base ) & 0xff;
len = 4;
}
sizecell = fdt_getprop(blob_buf, 0, "#size-cells", NULL);
/* use shifts and mask to ensure endianness */
if ((sizecell) && (*sizecell == 2)) {
tmp[0+len] = (crash_size >> 56) & 0xff;
tmp[1+len] = (crash_size >> 48) & 0xff;
tmp[2+len] = (crash_size >> 40) & 0xff;
tmp[3+len] = (crash_size >> 32) & 0xff;
tmp[4+len] = (crash_size >> 24) & 0xff;
tmp[5+len] = (crash_size >> 16) & 0xff;
tmp[6+len] = (crash_size >> 8) & 0xff;
tmp[7+len] = (crash_size ) & 0xff;
len += 8;
} else {
tmp[0+len] = (crash_size >> 24) & 0xff;
tmp[1+len] = (crash_size >> 16) & 0xff;
tmp[2+len] = (crash_size >> 8) & 0xff;
tmp[3+len] = (crash_size ) & 0xff;
len += 4;
}
if (fdt_setprop(blob_buf, nodeoffset, "reg", tmp, len) != 0) {
printf ("Error setting memory node!\n");
}
fdt_delprop(blob_buf, nodeoffset, "linux,usable-memory");
}
}
/* removes crashkernel nodes if they exist and we are *rebooting*
* into a crashkernel. These nodes should not exist after we
* crash and reboot into a new kernel
*/
static void fixup_crashkernel(struct kexec_info *info, char *blob_buf)
{
int nodeoffset;
nodeoffset = fdt_path_offset(blob_buf, "/chosen");
if (info->kexec_flags & KEXEC_ON_CRASH) {
if (nodeoffset < 0) {
printf("fdt_crashkernel: %s\n", fdt_strerror(nodeoffset));
return;
}
fdt_delprop(blob_buf, nodeoffset, "linux,crashkernel-base");
fdt_delprop(blob_buf, nodeoffset, "linux,crashkernel-size");
}
}
/* remove the old chosen nodes if they exist and add correct chosen
* nodes if we have an initd
*/
static void fixup_initrd(char *blob_buf)
{
int err, nodeoffset;
unsigned long tmp;
nodeoffset = fdt_path_offset(blob_buf, "/chosen");
if (nodeoffset < 0) {
printf("fdt_initrd: %s\n", fdt_strerror(nodeoffset));
return;
}
fdt_delprop(blob_buf, nodeoffset, "linux,initrd-start");
fdt_delprop(blob_buf, nodeoffset, "linux,initrd-end");
if ((reuse_initrd || ramdisk) &&
((ramdisk_base != 0) && (ramdisk_size != 0))) {
tmp = ramdisk_base;
err = fdt_setprop(blob_buf, nodeoffset,
"linux,initrd-start", &tmp, sizeof(tmp));
if (err < 0) {
printf("WARNING: "
"could not set linux,initrd-start %s.\n",
fdt_strerror(err));
return;
}
tmp = ramdisk_base + ramdisk_size;
err = fdt_setprop(blob_buf, nodeoffset,
"linux,initrd-end", &tmp, sizeof(tmp));
if (err < 0) {
printf("WARNING: could not set linux,initrd-end %s.\n",
fdt_strerror(err));
return;
}
}
}
char *fixup_dtb_init(struct kexec_info *info, char *blob_buf, off_t *blob_size,
unsigned long hole_addr, unsigned long *dtb_addr)
{
int ret, i, num = fdt_num_mem_rsv(blob_buf);
fdt_init(blob_buf);
/* Remove the existing reserve regions as they will no longer
* be valid after we reboot */
for (i = num - 1; i >= 0; i--) {
ret = fdt_del_mem_rsv(blob_buf, i);
if (ret) {
printf("%s: Error deleting memory reserve region %d from device tree!\n",
fdt_strerror(ret), i);
}
}
/* Pack the FDT first, so we don't grow excessively if there is already free space */
ret = fdt_pack(blob_buf);
if (ret)
printf("%s: Unable to pack flat device tree\n", fdt_strerror(ret));
/* info->nr_segments just a guide, will grow by at least EXPAND_GRANULARITY */
blob_buf = expand_buf(info->nr_segments * sizeof(struct fdt_reserve_entry),
blob_buf, blob_size);
/* add reserve region for *THIS* fdt */
*dtb_addr = locate_hole(info, *blob_size, 0,
hole_addr, hole_addr+KERNEL_ACCESS_TOP, -1);
ret = fdt_add_mem_rsv(blob_buf, *dtb_addr, PAGE_ALIGN(*blob_size));
if (ret) {
printf("%s: Unable to add new reserved memory for the flat device tree\n",
fdt_strerror(ret));
}
return blob_buf;
}
static void save_fixed_up_dtb(char *blob_buf, off_t blob_size)
{
FILE *fp;
if (!kexec_debug)
return;
fp = fopen("debug.dtb", "w");
if (fp) {
if ( blob_size == fwrite(blob_buf, sizeof(char), blob_size, fp)) {
dbgprintf("debug.dtb written\n");
} else {
dbgprintf("Unable to write debug.dtb\n");
}
} else {
dbgprintf("Unable to dump flat device tree to debug.dtb\n");
}
}
char *fixup_dtb_finalize(struct kexec_info *info, char *blob_buf, off_t *blob_size,
char *nodes[], char *cmdline)
{
fixup_nodes(nodes);
fixup_cmdline(cmdline);
fixup_reserve_regions(info, blob_buf);
fixup_memory(info, blob_buf);
fixup_initrd(blob_buf);
fixup_crashkernel(info, blob_buf);
blob_buf = (char *)dt_ops.finalize();
*blob_size = fdt_totalsize(blob_buf);
save_fixed_up_dtb(blob_buf, *blob_size);
return blob_buf;
}