// Simple command-line kernel monitor useful for
// controlling the kernel and exploring the system interactively.

#include <inc/stdio.h>
#include <inc/string.h>
#include <inc/pmap.h>
#include <inc/assert.h>
#include <inc/x86.h>

#include <kern/console.h>
#include <kern/monitor.h>
#include <kern/pmap.h>

#define CMDBUF_SIZE	80	// enough for one VGA text line

struct Command {
	const char *name;
	const char *desc;
	void (*func)(int argc, char **argv);
};

static struct Command commands[] = {
	{"help",	"Display this list of commands", mon_help},
	{"kerninfo",	"Display information about the kernel", mon_kerninfo},
        {"backtrace",   "Display current call chain", mon_backtrace},
        {"showmappings", "Show physical page mappings for virtual address", mon_showmappings},
        {"pageperm",    "Show or set page permissions", mon_pageperm},
        {"dumpmem",     "Display memory contents", mon_dumpmem},
        {"allocpage",   "Allocate physical page", mon_allocpage},
        {"freepage",    "Free physical page", mon_freepage},
        {"pagestatus",  "Display physical page status", mon_pagestatus}
};
        
#define NCOMMANDS (sizeof(commands)/sizeof(commands[0]))

/***** Implementations of basic kernel monitor commands *****/

void
mon_help(int argc, char **argv)
{
	int i;

	for (i = 0; i < NCOMMANDS; i++)
		printf("%s - %s\n", commands[i].name, commands[i].desc);
}

void
mon_kerninfo(int argc, char **argv)
{
	extern char _start[], etext[], edata[], end[];

	printf("Special kernel symbols:\n");
	printf("  _start %08x (virt)  %08x (phys)\n", _start, _start-KERNBASE);
	printf("  etext  %08x (virt)  %08x (phys)\n", etext, etext-KERNBASE);
	printf("  edata  %08x (virt)  %08x (phys)\n", edata, edata-KERNBASE);
	printf("  end    %08x (virt)  %08x (phys)\n", end, end-KERNBASE);
	printf("Kernel executable memory footprint: %dKB\n",
		(end-_start+1023)/1024);
}

struct sym_table_entry *
eip2sym(u_long eip)
{
   // Walk through the symbol table to find the greatest symbol less
   // than eip
   struct sym_table_entry *ret = NULL;
   struct sym_table_entry *ptr;
   for (ptr = symtable; ptr->addr != 0 || ptr->name != NULL; ptr++) {
      if (ret == NULL)
         ret = ptr;
      else if (ptr->addr <= eip && ptr->addr > ret->addr)
         ret = ptr;
   }
   
   return ret;   
}

      
void
mon_backtrace(int argc, char **argv)
{
   u_long *ebp;
   for (ebp = (void *) read_ebp(); ebp; ebp = (u_long *) *ebp) {
      printf("%08lx:  ", ebp);
      struct sym_table_entry *sym = eip2sym(*(ebp+1));
      printf("%s+%lx(%08lx):  ", sym->name, *(ebp+1) - sym->addr, *(ebp+1));
      printf("%x ", *(ebp+2));
      printf("%x ", *(ebp+3));
      printf("%x ", *(ebp+4));
      printf("%x ", *(ebp+5));
      printf("%x\n", *(ebp+6));
   }
}		
		
void
mon_showmappings(int argc, char **argv)
{
        if (argc != 3)
                goto considered_harmful;

        char *endptr;
        u_long start = strtol(argv[1], &endptr, 0);
        if (argv[1][0] == '\0' || *endptr != '\0')
                goto considered_harmful;
        u_long end = strtol(argv[2], &endptr, 0);
        if (argv[2][0] == '\0' || *endptr != '\0')
                goto considered_harmful;

        start &= ~0xFFF;
        int i;
        for (i = start; i <= end; i += BY2PG) {
                printf("0x%08lx -> ", i);
                Pte *pte;
                if (pgdir_walk(boot_pgdir, i, 0, &pte) != 0)
                        panic("pgdir_walk failed");
                if (pte == 0) {
                   printf("unmapped\n");
                } else {   
                   printf("0x%08lx ", *pte & ~0xFFF);
                   if (*pte & PTE_P) printf("PTE_P ");
                   if (*pte & PTE_W) printf("PTE_W ");
                   if (*pte & PTE_U) printf("PTE_U ");
                   if (*pte & PTE_PWT) printf("PTE_PWT ");
                   if (*pte & PTE_PCD) printf("PTE_PCD ");
                   if (*pte & PTE_A) printf("PTE_A ");
                   if (*pte & PTE_D) printf("PTE_D ");
                   if (*pte & PTE_PS) printf("PTE_PS ");
                   printf("\n");
                }
                
        }
        return;

  // This label should be called something sane like 'usage',
  // but the spirit of Dijkstra compelled me.      
  considered_harmful:
        printf("usage: showmappings start end\n");
        printf("start and end are virtual (= linear) addresses\n");
        
        return;
}

void
mon_pageperm(int argc, char **argv)
{
        if (argc != 4 && argc != 2)
                goto considered_harmful;

        char *endptr;
        u_long i = strtol(argv[1], &endptr, 0);
        if (argv[1][0] == '\0' || *endptr != '\0')
                goto considered_harmful;
        i &= ~0xFFF;
        
        Pte *pte;
        if (pgdir_walk(boot_pgdir, i, 0, &pte) != 0)
                panic("pgdir_walk failed");

        if (pte == 0) {
                printf("%08lx is unmapped\n", i);
                return;
        }
        
        if (argc == 4) {
                if (strcmp(argv[2], "kern") == 0)
                        *pte &= ~PTE_U;
                else if (strcmp(argv[2], "user") == 0)
                        *pte |= PTE_U;
                else
                        goto considered_harmful;

                if (strcmp(argv[3], "ro") == 0)
                        *pte &= ~PTE_W;
                else if (strcmp(argv[2], "rw") == 0)
                        *pte |= PTE_W;
                else
                        goto considered_harmful;
        }

        printf("%08lx: %s %s\n", i,
               (*pte & PTE_U) ? "user" : "kern",
               (*pte & PTE_W) ? "rw" : "ro");

        return;
        
        
 considered_harmful:
        printf("usage: pageperm <va> [(kern|user) (rw|ro)]\n");
        return;
}

void
mon_dumpmem(int argc, char **argv)
{
        if (argc != 3)
                goto considered_harmful;

        char *endptr;
        u_long start = strtol(argv[1], &endptr, 0);
        if (argv[1][0] == '\0' || *endptr != '\0')
                goto considered_harmful;
        u_long end = strtol(argv[2], &endptr, 0);
        if (argv[2][0] == '\0' || *endptr != '\0')
                goto considered_harmful;

        u_long *p;
        int c = 0;
        for (p = (u_long *) start; p < (u_long *)end; p++) {
                printf("%08lx ", *p);
                if (++c == 8) {
                        printf("\n");
                        c = 0;
                }
        }
        printf("\n");
        return;

  considered_harmful:
        printf("usage: dumpmem start end\n");
        printf("start and end are virtual (= linear) addresses\n");
        return;
}

void
mon_allocpage(int argc, char **argv)
{
        struct Page *pg;
        if (page_alloc(&pg) != 0) {
                printf("failed to allocate page!\n");
                return;
        }
        pg->pp_ref++;
        
        printf("allocated page %08lx\n", page2pa(pg));
}

void
mon_freepage(int argc, char **argv)
{
        if (argc != 2)
                goto considered_harmful;

        char *endptr;
        u_long n = strtol(argv[1], &endptr, 0);
        if (argv[1][0] == '\0' || *endptr != '\0')
                goto considered_harmful;
        n &= ~0xFFF;
        
        page_free(pa2page(n));

        return;
        
 considered_harmful:
        printf("usage: freepage <pa>\n");
}

void
mon_pagestatus(int argc, char **argv)
{
        if (argc != 2)
                goto considered_harmful;

        char *endptr;
        u_long n = strtol(argv[1], &endptr, 0);
        if (argv[1][0] == '\0' || *endptr != '\0')
                goto considered_harmful;
        n &= ~0xFFF;
        
        struct Page *p = pa2page(n);
        printf("%08lx: ", n);
        if (p->pp_ref == 0)
                printf("unmapped\n");
        else
                printf("mapped, refcount = %ld\n", p->pp_ref);

        return;
        
 considered_harmful:
        printf("usage: pagestatus <pa>\n");
}


/***** Kernel monitor command interpreter *****/

#define WHITESPACE " \t\r\n"
#define MAXARGS 16

static void
runcmd(char *buf)
{
	int argc;
	char *argv[MAXARGS];
	int i;

	// Parse the command buffer into whitespace-separated arguments
	argc = 0;
	argv[argc] = 0;
	while (1) {
		// gobble whitespace
		while (*buf && strchr(WHITESPACE, *buf))
			*buf++ = 0;
		if (*buf == 0)
			break;

		// save and scan past next arg
		if (argc == MAXARGS-1) {
			printf("Too many arguments (max %d)\n", MAXARGS);
			return;
		}
		argv[argc++] = buf;
		while (*buf && !strchr(WHITESPACE, *buf))
			buf++;
	}
	argv[argc] = 0;

	// Lookup and invoke the command
	if (argc == 0)
		return;
	for (i = 0; i < NCOMMANDS; i++) {
		if (strcmp(argv[0], commands[i].name) == 0) {
			commands[i].func(argc, argv);
			return;
		}
	}
	printf("Unknown command '%s'\n", argv[0]);
}

void
monitor(struct Trapframe *tf)
{
	char *buf;

	printf("Welcome to the JOS kernel monitor!\n");
	printf("Type 'help' for a list of commands.\n");

	while (1) {
		buf = readline("K> ");
		if (buf != NULL)
			runcmd(buf);
	}
}

