/* $Id: prtconf.c,v 1.3 1998/03/03 08:41:42 jj Exp $
 * prtconf.c: OpenPROM dump utility.
 *
 * Copyright (C) 1998 Jakub Jelinek (jj@ultra.linux.cz)
 *
 *  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; either version 2 of the License, or (at your
 *  option) any later version.
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/utsname.h>

#include <asm/openpromio.h>

const char * program_name = "prtconf";

int prom = 0;
int verbose = 0;
int fbname = 0;
int version = 0;
char *promdev = "/dev/openprom";
int promfd;
char sun4x = ' ';
int prom_root_node, prom_current_node;
#define MAX_PROP	128
#define MAX_VAL		(4096-128-4)
char buf[4096], buf2[4096];
#define DECL_OP(size) struct openpromio *op = (struct openpromio *)buf; op->oprom_size = (size)

static int prom_getsibling(int node)
{
	DECL_OP(sizeof(int));
	
	if (node == -1) return 0;
	*(int *)op->oprom_array = node;
	if (ioctl (promfd, OPROMNEXT, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

static int prom_getchild(int node)
{
	DECL_OP(sizeof(int));
	
	if (!node || node == -1) return 0;
	*(int *)op->oprom_array = node;
	if (ioctl (promfd, OPROMCHILD, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

static char *prom_getproperty(char *prop, int *lenp)
{
	DECL_OP(MAX_VAL);
	
	strcpy (op->oprom_array, prop);
	if (ioctl (promfd, OPROMGETPROP, op) < 0)
		return 0;
	if (lenp) *lenp = op->oprom_size;
	return op->oprom_array;
}

static int prom_searchsiblings(char *name)
{
	char *prop;
	int len;
	
	for (;;) {
		if (!(prop = prom_getproperty("name", &len)))
			return 0;
		prop[len] = 0;
		if (!strcmp(prop, name))
			return prom_current_node;
		if (!prom_getsibling(prom_current_node))
			return 0;
	}
}

static inline void print_version(void)
{
	FILE *f;
	char *p;
	
	if (sun4x == 'u') {
		prom_getchild(prom_getsibling(0));
		if (prom_searchsiblings("openprom")) {
			int len;
			char *prop = prom_getproperty("version", &len);
			
			if (prop && len > 0) {
				printf("%s\n", prop);
				return;
			}
		}
	}
	f = fopen("/proc/cpuinfo","r");
	if (!f) {
		fprintf(stderr, "Cannot open /proc/cpuinfo\n");
		exit(1);
	}
	while (fgets(buf2, 4096, f)) {
		if (!strncmp (buf2, "promlib", 7) && (p = strchr(buf2, ':'))) {
			printf("%s", p + 2);
			fclose(f);
			return;
		}
	}
	printf("Unknown OpenPROM version\n");
	fclose(f);
}

static unsigned long long proc_memory_size(void)
{
	FILE *f;
	char *p, *q;
	unsigned long size;
	
	f = fopen("/proc/meminfo","r");
	if (!f) {
		fprintf(stderr, "Cannot open /proc/meminfo\n");
		exit(1);
	}
	while (fgets(buf2, 4096, f)) {
		if (!strncmp (buf2, "Mem:", 4)) {
			p = buf2 + 4;
			while (*p == ' ' || *p == '\t') p++;
			size = strtoul(p, &q, 0);
			if (p != q) {
				fclose(f);
				return size;
			}
		} else if (!strncmp (buf2, "MemTotal:", 9)) {
			p = buf2 + 9;
			while (*p == ' ' || *p == '\t') p++;
			size = strtoul(p, &q, 0);
			if (p != q) {
				fclose(f);
				if (!strncmp (q, " kB", 3))
					return (long long)size * 1024;
				else if (!strncmp (q, " MB", 3))
					return (long long)size * 1024 * 1024;
				else
					return size;
			}
		}
	}
	fclose(f);
	return 0LL;
}

static inline void print_fbname(void)
{
	prom_getchild(prom_getsibling(0));
	if (prom_searchsiblings("aliases")) {
		int len;
		char *prop = prom_getproperty("screen", &len);
			
		if (prop && len > 0) {
			printf("%s\n", prop);
			return;
		}
	}
	exit(1);
}

static unsigned long long prom_memory_size(void)
{
	unsigned long long memory_size = 0LL;
	int len, i;
	unsigned int *prop;

	prom_getchild(prom_getsibling(0));
	if (!prom_searchsiblings("memory")) {
		return 0LL;
	}
	
	prop = (unsigned int *)prom_getproperty("reg", &len);
		
	if (!prop || (len % sizeof(int))) {
		return 0LL;
	}
	len /= sizeof(int);
	if (sun4x == 'u') {
		if (len % 4) {
			return 0LL;
		} else {
			for (i = 0; i < len; i+=4) {
				memory_size += ((unsigned long long)prop[i + 2] << 32);
				memory_size += prop[i + 3];
			}
		}
	} else {
		if (len % 3) {
			return 0LL;
		} else {
			for (i = 0; i < len; i+=3)
				memory_size += prop[i + 2];
		}
	}
	return memory_size;
}

static inline void indent(int level)
{
	while (level--)
		printf("    ");
}

static void prom_walk(int node, int level)
{
	int nextnode;
	
	indent(level);
	
	if (prom && verbose) {
		struct openpromio *op = (struct openpromio *)buf2; 
		int len, string;
		char *prop;
		
		printf("Node 0x%08x\n", node);
		
		*(int *)op->oprom_array = 0;
		
		for (;;) {
		
			op->oprom_size = MAX_PROP;
			if (ioctl(promfd, OPROMNXTPROP, op) < 0) {
				fprintf(stderr, "OPROMNXTPROP failed\n");
				exit(2);
			}
			if (!op->oprom_size) break;
			
			indent(level+1);
			
			prop = prom_getproperty(op->oprom_array, &len);
			if (!prop)
				printf("%s: data not available.\n", op->oprom_array);
			else {
				string = (len && prop[0]);
				if (string) {
					int i;
					
					for (i = 0; i < len - 1; i++)
						if (prop[i] < ' ' || prop[i] >= 127) {
							string = 0;
							break;
						}
					if ((prop[len - 1] && prop[len - 1] < ' ') ||
					    prop[len - 1] >= 127)
						string = 0;
				}
				if (string)
					printf("%s: '%s'\n", op->oprom_array, prop);
				else {
					printf("%s:  ");
					while (len > sizeof(int)) {
						printf("%08x.", *(int *)prop);
						prop += sizeof(int);
						len -= sizeof(int);
					}
					while (len) {
						printf("%02x", *(unsigned char *)prop);
						prop++;
						len--;
					}
					printf("\n");
				}
			}
		}
		printf("\n");
		
	} else if (prom) {
		int len;
		char *prop = prom_getproperty("name", &len);
		
		if (prop && len > 0)
			printf("Node '%s'\n", prop);
		else
			printf("Node\n"); /* Should not happen */
	} else {
		int len;
		char *prop = prom_getproperty("name", &len);
		
		if (prop && len > 0) { /* There is no easy way to imitate solaris behaviour */
			if (level)
				printf("%s (driver probably installed)\n", prop);
			else
				printf("%s\n", prop);
		} else
			printf("Unnamed node\n"); /* Should not happen */
	}
	
	nextnode = prom_getchild(node);
	if (nextnode)
		prom_walk(nextnode, level+1);
	nextnode = prom_getsibling(node);
	if (nextnode)
		prom_walk(nextnode, level);
}

static inline void print_tree(void)
{
	unsigned long long memory_size;
	
	memory_size = prom_memory_size();
	
	if (!memory_size) memory_size = proc_memory_size();
	
	printf("System Configuration:  Sun Microsystems  sun4");
	if (sun4x != ' ')
		printf("%c\n", sun4x);
	else
		printf("\n");
	printf("Memory size: %lld Megabytes\n", (memory_size + 0xfffff) / 0x100000);
	if (prom)
		printf("System Peripherals (PROM Nodes):\n\n");
	else
		printf("System Peripherals (Software Nodes):\n\n");
	prom_walk(prom_getsibling(0), 0);
}

static inline char get_sun4x(void)
{
	struct utsname uts_info;
	char *prop;
	int len;
	
	prom_getsibling(0);
	uname(&uts_info);
	if (!strcmp(uts_info.machine, "sparc64"))
		return 'u';
	if (strcmp(uts_info.machine, "sparc")) {
		fprintf(stderr, "This utility can be run on Sparc/UltraLinux only\n");
		exit(3);
	}
	prop = prom_getproperty("idprom", &len);
	if (prop && len > 2) {
		switch(prop[1] & 0xf0) {
		case 0x20: return ' ';
		case 0x50: return 'c';
		case 0x70: return 'm';
		}
	}
	
	prop = prom_getproperty("compatability", 0);
		
	if (prop && !strncmp(prop, "sun4", 4)) {
		switch (prop[4]) {
		case 'c':
		case 'd':
		case 'm':
		case 'e':
			return prop[4];
		}
	}
	prop = prom_getproperty("compatible", 0);
	
	if (prop && !strncmp(prop, "sun4", 4)) {
		switch (prop[4]) {
		case 'c':
		case 'd':
		case 'm':
		case 'e':
			return prop[4];
		}
	}
	return ' ';
}

static inline void do_prtconf(void)
{
	promfd = open(promdev, O_RDONLY);
	if (promfd == -1) {
		fprintf(stderr, "Cannot open %s\n", promdev);
		exit(1);
	}
	prom_root_node = prom_getsibling(0);
	if (!prom_root_node) {
		fprintf(stderr, "Cannot find root node\n");
		exit(1);
	}
	sun4x = get_sun4x();

	if (version)
		print_version();
	else if (fbname)
		print_fbname();
	else
		print_tree();
	close(promfd);
}

static void usage(void) __attribute__((noreturn));

static void usage(void)
{
	fprintf (stderr, "Usage: %s [-pvFVh] [-f device] [-PD]\n", program_name);
	exit (1);
}

int main(int argc, char **argv)
{
	char c;
	int i;
	static struct option long_options[] = {
	  { "prom", 0, 0, 'p' },
	  { "verbose", 0, 0, 'v' },
	  { "fbname", 0, 0, 'F' },
	  { "version", 0, 0, 'V' },
	  { "promdev", 1, 0, 'f' },
	  { "pseudo", 0, 0, 'P' },
	  { "drvname", 0, 0, 'D' },
	  { "help", 0, 0, 'h' },
	  { NULL, 0, 0, '\0' }
	};

	if (argc && *argv)
		program_name = *argv;
	while ((c = getopt_long (argc, argv, "pvFVf:PDh", long_options, 0)) != EOF)
		switch (c) {
		case 'p':
			prom = 1;
			break;
		case 'v':
			verbose = 1;
			break;
		case 'F':
			fbname = 1;
			break;
		case 'V':
			version = 1;
			break;
		case 'f':
			promdev = optarg;
			break;
		case 'P':
		case 'D':
			/* Solaris cruft */
			break;
		case 'h':
		default:
			usage ();
		}

	if (optind < argc)
		usage ();
	do_prtconf();
	exit(0);
}
