/*
 * Copyright (C) 2000-2003 ASANO Masahiro
 */

#include <unistd.h>
#include <string.h>
#include "crash.h"

PRIVATE addr_t pte(), pgd();
struct commandtable command_pte =
	{"pte", pte, "pte-addr", "format page-table-entry"};
struct commandtable command_pgd =
	{"pgd", pgd, "[pgd-addr]", "format page-table-entry"};

PRIVATE addr_t vtop();
struct commandtable command_vtop =
	{"vtop", vtop, "vaddress", "virtual to physical address\nuse physical memory interface such as `mcrash -p /dev/mem'"};

#define PAGE_SIZE	4096ul

#if CONFIG_X86_PAE

typedef unsigned long long	pte_t;
PRIVATE pte_t pgd_buffer[4 * PAGE_SIZE / sizeof(pte_t)];
PRIVATE pte_t pte_buffer[PAGE_SIZE / sizeof(pte_t)];
PRIVATE addr_t pgd_cache = (addr_t)-1;
PRIVATE addr_t pte_cache = (addr_t)-1;

#else /*CONFIG_X86_PAE*/

typedef unsigned long	pte_t;
PRIVATE pte_t pgd_buffer[PAGE_SIZE / sizeof(pte_t)];
PRIVATE pte_t pte_buffer[PAGE_SIZE / sizeof(pte_t)];
PRIVATE addr_t pgd_cache = (addr_t)-1;
PRIVATE addr_t pte_cache = (addr_t)-1;

#endif /*CONFIG_X86_PAE*/

PRIVATE void
format_pte(pte_t val)
{
#if CONFIG_X86_PAE
	if (val & (1<<0)) {
		mprintf("%06lx %c%c%c%c%c%c%c%c", (long)(val >> 12),
			val & (1<<8)? 'G': '-',
			val & (1<<7)? 'P': '-',
			val & (1<<6)? 'D': '-',
			val & (1<<5)? 'A': '-',
			val & (1<<4)? 'C': '-',
			val & (1<<3)? 'T': '-',
			val & (1<<2)? 'U': 'S',
			val & (1<<1)? 'W': 'R');
	} else {
		mprintf("------ --------");
	}
#else
	if (val & (1<<0)) {
		mprintf(" %05lx %c%c%c%c%c%c%c%c", val >> 12,
			val & (1<<8)? 'G': '-',
			val & (1<<7)? 'P': '-',
			val & (1<<6)? 'D': '-',
			val & (1<<5)? 'A': '-',
			val & (1<<4)? 'C': '-',
			val & (1<<3)? 'T': '-',
			val & (1<<2)? 'U': 'S',
			val & (1<<1)? 'W': 'R');
	} else {
		mprintf(" ----- --------");
	}
#endif
}

PRIVATE void
format_page_table(buf, count)
	const pte_t *buf;
	int count;
{
	int i;

	for (i = 0; ; i++) {
		if ((i & 3) == 0) {
			int lp = 0;
			while (i && memcmp(buf + i, buf + i - 4, sizeof(pte_t) * 4) == 0) {
				if (lp == 0) {
					mprintf("*\n");
					lp = 1;
				}
				i += 4;
				if (i >= count)
					break;
			}
			if (i >= count)
				break;
			if (count > 1024)
				mprintf("%03x:  ", i * 2);	/*PGDofPAE*/
			else
				mprintf("%02x:  ", i / 4);
		}
		format_pte(buf[i]);
		if ((i & 3) == 3) {
			mprintf("\n");
		} else {
			mprintf("  ");
		}
	}
}

PRIVATE addr_t
pte()
{
	int c;
	int vflag = 0;

	while ((c = getopt(argcnt, args, "v")) != EOF) {
		switch (c) {
		case 'v':
			vflag = 1;
			break;
		default:
			THROW(usage);
		}
	}

	if (vflag) {
		while (args[optind]) {
			format_pte(getvalue(args[optind++]));
			mprintf("\n");
		}
		return 0;
	}

	if (argcnt == optind + 1) {
		char buf[PAGE_SIZE];
		addr_t addr = getvalue(args[optind]);
		if (addr & 15) {
			THROW("address unaligned");
		}
		memread(addr, sizeof(buf), buf, "PTE");
		format_page_table(buf, sizeof(buf) / sizeof(pte_t));
	} else {
		THROW(usage);
	}

	return 0;
}

PRIVATE addr_t
pgd()
{
	int c;
	int vflag = 0;

	while ((c = getopt(argcnt, args, "v")) != EOF) {
		switch (c) {
		case 'v':
			vflag = 1;
			break;
		default:
			THROW(usage);
		}
	}

	if (vflag) {
		while (args[optind]) {
			format_pte(getvalue(args[optind++]));
			mprintf("\n");
		}
		return 0;
	}

	if (argcnt == optind) {
		if (pgd_cache == (addr_t)-1)
			THROW("PGD not loaded");
		mprintf("PGD " FPTR "\n", pgd_cache);
		format_page_table(pgd_buffer, sizeof(pgd_buffer) / sizeof(pte_t));
	} else if (argcnt == optind + 1) {
#if CONFIG_X86_PAE
		pte_t pgd_buf[4];
#else
		pte_t pgd_buf[PAGE_SIZE / sizeof(pte_t)];
#endif /*CONFIG_X86_PAE*/
		addr_t addr = getvalue(args[optind]);
		if (addr & 15) {
			THROW("address unaligned");
		}
		memread(addr, sizeof(pgd_buf), pgd_buf, "PGD");
		format_page_table(pgd_buf, sizeof(pgd_buf) / sizeof(pte_t));
	} else {
		THROW(usage);
	}

	return 0;
}

void
load_pgd(addr)
	addr_t addr;
{
#if CONFIG_X86_PAE
	int i;
	pte_t pdpte[4];
	ll_memread(_PA(addr), sizeof(pdpte), pdpte,
		"page directory pointer table entry");
	for (i = 0; i < 4; i++) {
		ll_memread((addr_t)(pdpte[i] & ~(PAGE_SIZE - 1)), PAGE_SIZE,
			(char *)pgd_buffer + PAGE_SIZE * i, "pgd");
	}
	pgd_cache = addr;
	pte_cache = (addr_t)-1;

#else /*CONFIG_X86_PAE*/
	ll_memread(_PA(addr), PAGE_SIZE, pgd_buffer, "pgd");
	pgd_cache = addr;
	pte_cache = (addr_t)-1;
#endif /*CONFIG_X86_PAE*/
}

PRIVATE const pte_t *
load_pte(addr)
	addr_t addr;
{
	if (pte_cache == addr)
		return pte_buffer; /*cache hit*/
	ll_memread(addr, PAGE_SIZE, pte_buffer, "pte");
	pte_cache = addr;
	return pte_buffer;
}

addr_t
translate_addr(addr, count, errmsg)
	addr_t addr;
	addr_t *count;
	const char *errmsg;
{
	pte_t e;
	addr_t p;

#if CONFIG_X86_PAE
	e = pgd_buffer[addr >> 21];
	if ((e & (1<<0)) == 0)
		THROWF2("Error: PTE page not presented for %s address %lx",
			errmsg, addr);
	p = e & ~(PAGE_SIZE - 1);
	if (e & (1<<7)) {
		/* 2MB page */
		*count = 0x200000 - (addr & 0x1fffff);
		return p | (addr & 0x1fffff);
	}

	e = (load_pte(p))[(addr >> 12) & 0x1ff];
	if ((e & (1<<0)) == 0)
		THROWF2("Error: page not presented for %s address %lx",
			errmsg, addr);
	p = e & ~(PAGE_SIZE - 1);
	*count = PAGE_SIZE - (addr & (PAGE_SIZE - 1));
	return p | (addr & (PAGE_SIZE - 1));

#else /*CONFIG_X86_PAE*/
	e = pgd_buffer[addr >> 22];
	if ((e & (1<<0)) == 0)
		THROWF2("Error: PTE page not presented for %s address %lx",
			errmsg, addr);
	p = e & ~(PAGE_SIZE - 1);
	if (e & (1<<7)) {
		/* 4MB page */
		*count = 0x400000 - (addr & 0x3fffff);
		return p | (addr & 0x3fffff);
	}

	e = (load_pte(p))[(addr >> 12) & 0x3ff];
	if ((e & (1<<0)) == 0)
		THROWF2("Error: page not presented for %s address %lx",
			errmsg, addr);
	p = e & ~(PAGE_SIZE - 1);
	*count = PAGE_SIZE - (addr & (PAGE_SIZE - 1));
	return p | (addr & (PAGE_SIZE - 1));

#endif /*CONFIG_X86_PAE*/
}

PRIVATE addr_t
vtop()
{
	int c;
	addr_t vaddr = 0, paddr = 0, dummy;

	while ((c = getopt(argcnt, args, "")) != EOF) {
		switch (c) {
		default:
			THROW(usage);
		}
	}

	if (argcnt == optind)
		THROW(usage);
	if (pgd_cache == (addr_t)-1)
		THROW("PGD not loaded");

	mprintf(SPTR "  " SPTR "\n", "VIRTUAL", "PHYSICAL");
	while (args[optind]) {
		vaddr = getvalue(args[optind++]);
		paddr = translate_addr(vaddr, &dummy, "vtop");
		mprintf(FPTR "  " FPTR "\n", vaddr, paddr);
	}
	return paddr;
}
