/*
 * fs2dt: creates a flattened device-tree
 *
 * Copyright (C) 2004,2005  Milton D Miller II, IBM Corporation
 * Copyright (C) 2005  R Sharada (sharada@in.ibm.com), IBM Corporation
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>

#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "../../kexec.h"
#include "kexec-ppc64.h"
#include "crashdump-ppc64.h"

#define MAXPATH 1024		/* max path name length */
#define NAMESPACE 16384		/* max bytes for property names */
#define TREEWORDS 65536		/* max 32 bit words for property values */
#define MEMRESERVE 256		/* max number of reserved memory blocks */
#define MAX_MEMORY_RANGES 1024

static char pathname[MAXPATH], *pathstart;
static char propnames[NAMESPACE] = { 0 };
static unsigned dtstruct[TREEWORDS] __attribute__ ((aligned (8))), *dt;
static unsigned long long mem_rsrv[2*MEMRESERVE] = { 0, 0 };

static int crash_param = 0;
static char local_cmdline[COMMAND_LINE_SIZE] = { "" };
extern mem_rgns_t usablemem_rgns;
static struct bootblock bb[1];

void reserve(unsigned long long where, unsigned long long length)
{
	size_t offset;

	for (offset = 0; mem_rsrv[offset + 1]; offset += 2)
		;

	if (offset + 4 >= 2 * MEMRESERVE)
		die("unrecoverable error: exhasuted reservation meta data\n");

	mem_rsrv[offset] = where;
	mem_rsrv[offset + 1] = length;
	mem_rsrv[offset + 3] = 0;  /* N.B: don't care about offset + 2 */
}

/* look for properties we need to reserve memory space for */
static void checkprop(char *name, unsigned *data, int len)
{
	static unsigned long long base, size, end;

	if ((data == NULL) && (base || size || end))
		die("unrecoverable error: no property data");
	else if (!strcmp(name, "linux,rtas-base"))
		base = *data;
	else if (!strcmp(name, "linux,tce-base"))
		base = *(unsigned long long *) data;
	else if (!strcmp(name, "rtas-size") ||
			!strcmp(name, "linux,tce-size"))
		size = *data;
	else if (reuse_initrd && !strcmp(name, "linux,initrd-start"))
		if (len == 8)
			base = *(unsigned long long *) data;
		else
			base = *data;
	else if (reuse_initrd && !strcmp(name, "linux,initrd-end"))
		end = *(unsigned long long *) data;

	if (size && end)
		die("unrecoverable error: size and end set at same time\n");
	if (base && size) {
		reserve(base, size);
		base = size = 0;
	}
	if (base && end) {
		reserve(base, end-base);
		base = end = 0;
	}
}

/*
 * return the property index for a property name, creating a new one
 * if needed.
 */
static unsigned propnum(const char *name)
{
	unsigned offset = 0;

	while(propnames[offset])
		if (strcmp(name, propnames+offset))
			offset += strlen(propnames+offset)+1;
		else
			return offset;

	if (NAMESPACE - offset < strlen(name) + 1)
		die("unrecoverable error: propnames overrun\n");

	strcpy(propnames+offset, name);

	return offset;
}

static void add_dyn_reconf_usable_mem_property(int fd)
{
	char fname[MAXPATH], *bname;
	uint64_t buf[32];
	uint64_t ranges[2*MAX_MEMORY_RANGES];
	uint64_t base, end, loc_base, loc_end;
	int range, rlen = 0, i;
	int rngs_cnt, tmp_indx;

	strcpy(fname, pathname);
	bname = strrchr(fname, '/');
	bname[0] = '\0';
	bname = strrchr(fname, '/');
	if (strncmp(bname, "/ibm,dynamic-reconfiguration-memory", 36))
		return;

	if (lseek(fd, 4, SEEK_SET) < 0)
		die("unrecoverable error: error seeking in \"%s\": %s\n",
			pathname, strerror(errno));

	rlen = 0;
	for (i = 0; i < num_of_lmbs; i++) {
		if (read(fd, buf, 24) < 0)
			die("unrecoverable error: error reading \"%s\": %s\n",
				pathname, strerror(errno));

		base = (uint64_t) buf[0];
		end = base + lmb_size;
		if (~0ULL - base < end)
			die("unrecoverable error: mem property overflow\n");

		tmp_indx = rlen++;

		rngs_cnt = 0;
		for (range = 0; range < usablemem_rgns.size; range++) {
			loc_base = usablemem_rgns.ranges[range].start;
			loc_end = usablemem_rgns.ranges[range].end;
			if (loc_base >= base && loc_end <= end) {
				ranges[rlen++] = loc_base;
				ranges[rlen++] = loc_end - loc_base;
				rngs_cnt++;
			} else if (base < loc_end && end > loc_base) {
				if (loc_base < base)
					loc_base = base;
				if (loc_end > end)
					loc_end = end;
				ranges[rlen++] = loc_base;
				ranges[rlen++] = loc_end - loc_base;
				rngs_cnt++;
			}
		}
		/* Store the count of (base, size) duple */
		ranges[tmp_indx] = rngs_cnt;
	}
		
	rlen = rlen * sizeof(uint64_t);
	/*
	 * Add linux,drconf-usable-memory property.
	 */
	*dt++ = 3;
	*dt++ = rlen;
	*dt++ = propnum("linux,drconf-usable-memory");
	if ((rlen >= 8) && ((unsigned long)dt & 0x4))
		dt++;
	memcpy(dt, &ranges, rlen);
	dt += (rlen + 3)/4;
}

static void add_usable_mem_property(int fd, int len)
{
	char fname[MAXPATH], *bname;
	uint64_t buf[2];
	uint64_t ranges[2*MAX_MEMORY_RANGES];
	uint64_t base, end, loc_base, loc_end;
	int range, rlen = 0;

	strcpy(fname, pathname);
	bname = strrchr(fname,'/');
	bname[0] = '\0';
	bname = strrchr(fname,'/');
	if (strncmp(bname, "/memory@", 8))
		return;

	if (len < 2 * sizeof(uint64_t))
		die("unrecoverable error: not enough data for mem property\n");
	len = 2 * sizeof(uint64_t);

	if (lseek(fd, 0, SEEK_SET) < 0)
		die("unrecoverable error: error seeking in \"%s\": %s\n",
		    pathname, strerror(errno));
	if (read(fd, buf, len) != len)
		die("unrecoverable error: error reading \"%s\": %s\n",
		    pathname, strerror(errno));

	if (~0ULL - buf[0] < buf[1])
		die("unrecoverable error: mem property overflow\n");
	base = buf[0];
	end = base + buf[1];

	for (range = 0; range < usablemem_rgns.size; range++) {
		loc_base = usablemem_rgns.ranges[range].start;
		loc_end = usablemem_rgns.ranges[range].end;
		if (loc_base >= base && loc_end <= end) {
			ranges[rlen++] = loc_base;
			ranges[rlen++] = loc_end - loc_base;
		} else if (base < loc_end && end > loc_base) {
			if (loc_base < base)
				loc_base = base;
			if (loc_end > end)
				loc_end = end;
			ranges[rlen++] = loc_base;
			ranges[rlen++] = loc_end - loc_base;
		}
	}

	if (!rlen) {
		/*
		 * User did not pass any ranges for thsi region. Hence, write
		 * (0,0) duple in linux,usable-memory property such that
		 * this region will be ignored.
		 */
		ranges[rlen++] = 0;
		ranges[rlen++] = 0;
	}

	rlen = rlen * sizeof(uint64_t);
	/*
	 * No add linux,usable-memory property.
	 */
	*dt++ = 3;
	*dt++ = rlen;
	*dt++ = propnum("linux,usable-memory");
	if ((rlen >= 8) && ((unsigned long)dt & 0x4))
		dt++;
	memcpy(dt,&ranges,rlen);
	dt += (rlen + 3)/4;
}

/* put all properties (files) in the property structure */
static void putprops(char *fn, struct dirent **nlist, int numlist)
{
	struct dirent *dp;
	int i = 0, fd, len;
	struct stat statbuf;

	for (i = 0; i < numlist; i++) {
		dp = nlist[i];
		strcpy(fn, dp->d_name);

		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
                        continue;

		if (lstat(pathname, &statbuf))
			die("unrecoverable error: could not stat \"%s\": %s\n",
			    pathname, strerror(errno));

		if (!crash_param && !strcmp(fn,"linux,crashkernel-base"))
			continue;

		if (!crash_param && !strcmp(fn,"linux,crashkernel-size"))
			continue;

		/*
		 * This property will be created for each node during kexec
		 * boot. So, ignore it.
		 */
		if (!strcmp(dp->d_name, "linux,pci-domain") ||
			!strcmp(dp->d_name, "linux,htab-base") ||
			!strcmp(dp->d_name, "linux,htab-size") ||
			!strcmp(dp->d_name, "linux,kernel-end"))
				continue;

		/* This property will be created/modified later in putnode()
		 * So ignore it, unless we are reusing the initrd.
		 */
		if ((!strcmp(dp->d_name, "linux,initrd-start") ||
		     !strcmp(dp->d_name, "linux,initrd-end")) &&
		    !reuse_initrd)
				continue;

		/* This property will be created later in putnode() So
		 * ignore it now.
		 */
		if (!strcmp(dp->d_name, "bootargs"))
			continue;

		if (! S_ISREG(statbuf.st_mode))
			continue;

		len = statbuf.st_size;

		*dt++ = 3;
		*dt++ = len;
		*dt++ = propnum(fn);

		if ((len >= 8) && ((unsigned long)dt & 0x4))
			dt++;

		fd = open(pathname, O_RDONLY);
		if (fd == -1)
			die("unrecoverable error: could not open \"%s\": %s\n",
			    pathname, strerror(errno));

		if (read(fd, dt, len) != len)
			die("unrecoverable error: could not read \"%s\": %s\n",
			    pathname, strerror(errno));

		checkprop(fn, dt, len);

		dt += (len + 3)/4;
		if (!strcmp(dp->d_name, "reg") && usablemem_rgns.size)
			add_usable_mem_property(fd, len);
		if (!strcmp(dp->d_name, "ibm,dynamic-memory") &&
					usablemem_rgns.size)
			add_dyn_reconf_usable_mem_property(fd);

		close(fd);
	}

	fn[0] = '\0';
	checkprop(pathname, NULL, 0);
}

/*
 * Compare function used to sort the device-tree directories
 * This function will be passed to scandir.
 */
static int comparefunc(const void *dentry1, const void *dentry2)
{
	char *str1 = (*(struct dirent **)dentry1)->d_name;
	char *str2 = (*(struct dirent **)dentry2)->d_name;

	/*
	 * strcmp scans from left to right and fails to idetify for some
	 * strings such as memory@10000000 and memory@f000000.
	 * Therefore, we get the wrong sorted order like memory@10000000 and
	 * memory@f000000.
	 */
	if (strchr(str1, '@') && strchr(str2, '@') &&
		(strlen(str1) > strlen(str2)))
		return 1;

	return strcmp(str1, str2);
}

/*
 * put a node (directory) in the property structure.  first properties
 * then children.
 */
static void putnode(void)
{
	char *dn;
	struct dirent *dp;
	char *basename;
	struct dirent **namelist;
	int numlist, i;
	struct stat statbuf;

	*dt++ = 1;
	strcpy((void *)dt, *pathstart ? pathstart : "/");
	while(*dt)
		dt++;
	if (dt[-1] & 0xff)
		dt++;

	numlist = scandir(pathname, &namelist, 0, comparefunc);
	if (numlist < 0)
		die("unrecoverable error: could not scan \"%s\": %s\n",
		    pathname, strerror(errno));
	if (numlist == 0)
		die("unrecoverable error: no directory entries in \"%s\"",
		    pathname);

	basename = strrchr(pathname,'/');

	strcat(pathname, "/");
	dn = pathname + strlen(pathname);

	putprops(dn, namelist, numlist);

	/* Add initrd entries to the second kernel */
	if (initrd_base && !strcmp(basename,"/chosen/")) {
		int len = 8;
		unsigned long long initrd_end;
		*dt++ = 3;
		*dt++ = len;
		*dt++ = propnum("linux,initrd-start");

		if ((len >= 8) && ((unsigned long)dt & 0x4))
			dt++;

		memcpy(dt,&initrd_base,len);
		dt += (len + 3)/4;

		len = 8;
		*dt++ = 3;
		*dt++ = len;
		*dt++ = propnum("linux,initrd-end");

		initrd_end = initrd_base + initrd_size;
		if ((len >= 8) && ((unsigned long)dt & 0x4))
			dt++;

		memcpy(dt,&initrd_end,len);
		dt += (len + 3)/4;

		reserve(initrd_base, initrd_size);
	}

	/* Add cmdline to the second kernel.  Check to see if the new
	 * cmdline has a root=.  If not, use the old root= cmdline.  */
	if (!strcmp(basename,"/chosen/")) {
		size_t cmd_len = 0;
		char *param = NULL;

		cmd_len = strlen(local_cmdline);
		if (cmd_len != 0) {
			param = strstr(local_cmdline, "crashkernel=");
			if (param)
				crash_param = 1;
			/* does the new cmdline have a root= ? ... */
			param = strstr(local_cmdline, "root=");
		}

		/* ... if not, grab root= from the old command line */
		if (!param) {
			char filename[MAXPATH];
			FILE *fp;
			char *last_cmdline = NULL;
			char *old_param;

			strcpy(filename, pathname);
			strcat(filename, "bootargs");
			fp = fopen(filename, "r");
			if (fp) {
				if (getline(&last_cmdline, &cmd_len, fp) == -1)
					die("unable to read %s\n", filename);

				param = strstr(last_cmdline, "root=");
				if (param) {
					old_param = strtok(param, " ");
					if (cmd_len != 0)
						strcat(local_cmdline, " ");
					strcat(local_cmdline, old_param);
				}
			}
			if (last_cmdline)
				free(last_cmdline);
		}
		strcat(local_cmdline, " ");
		cmd_len = strlen(local_cmdline);
		cmd_len = cmd_len + 1;

		/* add new bootargs */
		*dt++ = 3;
		*dt++ = cmd_len;
		*dt++ = propnum("bootargs");
		if ((cmd_len >= 8) && ((unsigned long)dt & 0x4))
			dt++;
		memcpy(dt, local_cmdline,cmd_len);
		dt += (cmd_len + 3)/4;

		fprintf(stderr, "Modified cmdline:%s\n", local_cmdline);
	}

	for (i=0; i < numlist; i++) {
		dp = namelist[i];
		strcpy(dn, dp->d_name);
		free(namelist[i]);

		if (!strcmp(dn, ".") || !strcmp(dn, ".."))
			continue;

		if (lstat(pathname, &statbuf))
			die("unrecoverable error: could not stat \"%s\": %s\n",
			    pathname, strerror(errno));

		if (S_ISDIR(statbuf.st_mode))
			putnode();
	}

	*dt++ = 2;
	dn[-1] = '\0';
	free(namelist);
}

int create_flatten_tree(char **bufp, off_t *sizep, char *cmdline)
{
	unsigned long len;
	unsigned long tlen;
	char *buf;
	unsigned long me;

	me = 0;

	strcpy(pathname, "/proc/device-tree/");

	pathstart = pathname + strlen(pathname);
	dt = dtstruct;

	if (cmdline)
		strcpy(local_cmdline, cmdline);

	putnode();
	*dt++ = 9;

	len = sizeof(bb[0]);
	len += 7; len &= ~7;

	bb->off_mem_rsvmap = len;

	for (len = 1; mem_rsrv[len]; len += 2)
		;
	len+= 3;
	len *= sizeof(mem_rsrv[0]);

	bb->off_dt_struct = bb->off_mem_rsvmap + len;

	len = dt - dtstruct;
	len *= sizeof(unsigned);
	bb->off_dt_strings = bb->off_dt_struct + len;

	len = propnum("");
	len +=  3; len &= ~3;
	bb->totalsize = bb->off_dt_strings + len;

	bb->magic = 0xd00dfeed;
	bb->version = 2;
	bb->last_comp_version = 2;

	reserve(me, bb->totalsize); /* patched later in kexec_load */

	buf = malloc(bb->totalsize);
	*bufp = buf;
	memcpy(buf, bb, bb->off_mem_rsvmap);
	tlen = bb->off_mem_rsvmap;
	memcpy(buf+tlen, mem_rsrv, bb->off_dt_struct - bb->off_mem_rsvmap);
	tlen = tlen + (bb->off_dt_struct - bb->off_mem_rsvmap);
	memcpy(buf+tlen, dtstruct,  bb->off_dt_strings - bb->off_dt_struct);
	tlen = tlen +  (bb->off_dt_strings - bb->off_dt_struct);
	memcpy(buf+tlen, propnames,  bb->totalsize - bb->off_dt_strings);
	tlen = tlen + bb->totalsize - bb->off_dt_strings;
	*sizep = tlen;
	return 0;
}
