Skip to content

Commit

Permalink
Add support for reworking flat device tree support
Browse files Browse the repository at this point in the history
Currently, the device tree is passed as is. You can optionally
update the command line and specifically listed nodes but nothing
is updated automatically.

This patch updates the memreserve regions, memory node, initrd
nodes and attempts to make the device tree look as it should. Some
code is borrowed from the u-boot routines which do similiar things

Also, now if no flat device tree is passed to kexec it will attempt
to rebuild one from the /proc/device-tree file system to use for
the kexec'ed kernel for both uImage and elf formats

[ [email protected]: kernel_addr is now outside #ifdef WITH_GAMECUBE ]
Signed-off-by: Matthew McClintock <[email protected]>
Signed-off-by: Simon Horman <[email protected]>
  • Loading branch information
Matthew McClintock authored and horms committed Jul 29, 2010
1 parent 47a4a25 commit 7c66e16
Show file tree
Hide file tree
Showing 6 changed files with 390 additions and 140 deletions.
298 changes: 297 additions & 1 deletion kexec/arch/ppc/fixup_dtb.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,35 @@
#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";

#ifdef DEBUG
static void print_fdt_reserve_regions(char *blob_buf)
{
int i, num;

/* Print out a summary of the final reserve regions */
num = fdt_num_mem_rsv(blob_buf);
printf ("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) {
printf("%d: offset: %llx, size: %llx\n", i, offset, size);
} else {
printf("Error retreiving reserved region\n");
}
}
}
#endif

static void fixup_nodes(char *nodes[])
{
int index = 0;
Expand Down Expand Up @@ -92,14 +114,288 @@ static void fixup_cmdline(const char *cmdline)
return;
}

char *fixup_dtb_nodes(char *blob_buf, off_t *blob_size, char *nodes[], char *cmdline)
#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, off_t *blob_size)
{
int ret, i;
int nodeoffset;

/* 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 {
/* 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;
}
}

/* 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 val = 0;

buf = fdt_getprop(blob_buf, nodeoffset, "cpu-release-addr", &sz);
if (sz == 4) {
val = *(u32 *)buf;
} else if (sz == 8) {
val = *(u64 *)buf;
}

if (val) {
ret = fdt_add_mem_rsv(blob_buf, PAGE_ALIGN(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);
}

out: ;

#ifdef DEBUG
print_fdt_reserve_regions(blob_buf);
#endif
}

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 ((reuse_initrd || ramdisk) &&
((ramdisk_base != 0) && (ramdisk_size != 0))) {
if (nodeoffset < 0) {
printf("fdt_initrd: %s\n", fdt_strerror(nodeoffset));
return;
}

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 + 1;
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;
}
} else {
fdt_delprop(blob_buf, nodeoffset, "linux,initrd-start");
fdt_delprop(blob_buf, nodeoffset, "linux,initrd-end");
}
}

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, 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;
}

#ifdef DEBUG
static void save_fixed_up_dtb(char *blob_buf, off_t blob_size)
{
FILE *fp;

fp = fopen("debug.dtb", "w");
if (fp) {
if ( blob_size == fwrite(blob_buf, sizeof(char), blob_size, fp)) {
printf("debug.dtb written\n");
} else {
printf("Unable to write debug.dtb\n");
}
} else {
printf("Unable to dump flat device tree to debug.dtb\n");
}
}
#endif

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, blob_size);
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);

#ifdef DEBUG
save_fixed_up_dtb(blob_buf, *blob_size);
#endif

return blob_buf;
}
6 changes: 5 additions & 1 deletion kexec/arch/ppc/fixup_dtb.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#ifndef __FIXUP_DTB_H
#define __FIXUP_DTB_H

char *fixup_dtb_nodes(char *blob_buf, off_t *blob_size, char *nodes[], char *cmdline);
char *fixup_dtb_init(struct kexec_info *info, char *blob_buf, off_t *blob_size,
unsigned long hole_addr, unsigned long *dtb_addr);

char *fixup_dtb_finalize(struct kexec_info *info, char *blob_buf, off_t *blob_size,
char *nodes[], char *cmdline);

#endif
Loading

0 comments on commit 7c66e16

Please sign in to comment.