/*======================================================================

    Dummy device for "remote" kernel debugging with gdb

    Written by David Hinds, dhinds@allegro.stanford.edu

    kdebug.c 1.5 1995/02/27 22:18:57 (David Hinds)

======================================================================*/

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>

#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <asm/segment.h>

/*====================================================================*/

static int debug_read(struct inode *inode, struct file *file,
		      char *buf, int count);
static int debug_write(struct inode *inode, struct file *file,
		       char *buf, int count);
static int debug_ioctl(struct inode *inode, struct file *file,
		       u_int cmd, u_long arg);
static int debug_open(struct inode *inode, struct file *file);
static void debug_close(struct inode *inode, struct file *file);

static struct file_operations debug_fops = {
    NULL,		/* lseek */
    debug_read,
    debug_write,
    NULL,		/* readdir */
    NULL,		/* select */
    debug_ioctl,
    NULL,		/* mmap */
    debug_open,
    debug_close,
    NULL		/* fsync */
};
    
/* Major device # for debug pseudo-device */
static int major_dev = 0;

/* Device in use? */
static int is_open = 0;

/* Input and output buffers */
#define BUFSIZE 512
static u_char *inq, *outp, *outq;
static u_char inbuf[BUFSIZE], outbuf[BUFSIZE];

/*====================================================================*/

#define NREG 16
#define STACKSIZE 4096

enum registers {
    R_EAX, R_ECX, R_EDX, R_EBX, R_ESP, R_EBP, R_ESI, R_EDI,
    R_EIP, R_EFLAGS, R_CS, R_SS, R_DS, R_ES, R_FS, R_GS
};

static u_long reg[NREG], save[NREG];

static u_char stack[STACKSIZE];

/*====================================================================*/

static u_char *stack_base = stack;

static void update_stack(u_long esp)
{
    u_char *addr = (u_char *)esp;
    if ((addr < stack_base) || (addr > stack_base+STACKSIZE))
	stack_base = addr-STACKSIZE/2;
}

static u_char *map_stack(u_char *addr)
{
    if ((addr > stack_base) && (addr < stack_base+STACKSIZE))
	return addr + (stack - stack_base);
    else
	return addr;
}

/*====================================================================*/

static u_long call;

static void call_kernel(void)
{
    /* Extract the target address from the call dummy */
    call = *(u_long *)(reg[R_EIP]+1) + reg[R_EIP] + 5;

    asm("movl %eax, _save\n movl _reg, %eax");
    asm("movl %ecx, _save+4\n movl _reg+4, %ecx");
    asm("movl %edx, _save+8\n movl _reg+8, %edx");
    asm("movl %ebx, _save+12\n movl _reg+12, %ebx");
    asm("movl %esp, _save+16\n movl _reg+16, %esp");
    asm("movl %ebp, _save+20\n movl _reg+20, %ebp");
    asm("movl %esi, _save+24\n movl _reg+24, %esi");
    asm("movl %edi, _save+28\n movl _reg+28, %edi");
    
    asm("movl _call, %eax\n call *%eax");
    
    asm("movl %eax, _reg\n movl _save, %eax");
    asm("movl %ecx, _reg+4\n movl _save+4, %ecx");
    asm("movl %edx, _reg+8\n movl _save+8, %edx");
    asm("movl %ebx, _reg+12\n movl _save+12, %ebx");
    asm("movl %esp, _reg+16\n movl _save+16, %esp");
    asm("movl %ebp, _reg+20\n movl _save+20, %ebp");
    asm("movl %esi, _reg+24\n movl _save+24, %esi");
    asm("movl %edi, _reg+28\n movl _save+28, %edi");
    
    /* Pretend we stopped at the call dummy breakpoint */
    reg[R_EIP] += 6;
}

/*====================================================================*/

static u_char hexchars[] = "0123456789abcdef";

static int hex(int ch)
{
    if ((ch >= '0') && (ch <= '9')) return (ch-'0');
    if ((ch >= 'a') && (ch <= 'f')) return (ch-'a'+10);
    if ((ch >= 'A') && (ch <= 'F')) return (ch-'A'+10);
    return -1;
}

/*====================================================================*/

static void put_debug(char ch)
{
    *outq = ch;
    outq++;
    if (outq == outbuf + BUFSIZE)
	outq = outbuf;
    if (outp == outq)
	printk("kdebug: output buffer overflow!\n");
}

/*====================================================================*/

static void put_packet(char *buf)
{
    u_char checksum;

#ifdef DEBUG
    printk("put_packet('%s')\n", buf);
#endif
    put_debug('$');
    checksum = 0;
    while (*buf != '\0') {
	put_debug(*buf);
	checksum += *buf;
	buf++;
    }
    put_debug('#');
    put_debug(hexchars[checksum >> 4]);
    put_debug(hexchars[checksum % 16]);
}

/*====================================================================*/

static int verify_addr(u_char *addr, u_long len)
{
#ifdef HAS_VVERIFY
    if ((u_long)addr < PAGE_SIZE)
	return -1;
    else if ((u_long)addr > high_memory)
	return vverify(addr, len);
    else
	return 0;
#else
    if ((u_long)addr < PAGE_SIZE)
	return -1;
    else
	return 0;
#endif
}

/*====================================================================*/

static void handle_packet(void)
{
    char *ptr;
    u_char *inp, *start, *addr;
    u_char buf[BUFSIZE];
    u_long i, len;

    inp = inbuf;
    while (*inp != '$') inp++;

    start = ++inp;
    for (; *inp != '#'; inp++) ;
    *inp = '\0';

#ifdef DEBUG
    printk("handle_packet('%s')\n", start);
#endif
    
    put_debug('+');
    if (start[2] == ':') {
	put_debug(start[0]);
	put_debug(start[1]);
	start += 3;
    }

    buf[0] = '\0';
    switch (start[0]) {
    case '?': /* last signal */
	strcpy(buf, "S05");
	break;
    case 'd': /* toggle debug flag */
	break;
    case 'g': /* read registers */
	addr = (u_char *)reg;
	for (i = 0; i < sizeof(reg); i++) {
	    buf[i<<1] = hexchars[addr[i] >> 4];
	    buf[(i<<1)+1] = hexchars[addr[i] % 16];
	}
	buf[i<<1] = '\0';
	break;
    case 'G': /* write registers */
	addr = (u_char *)reg;
	ptr = start+1;
	for (i = 0; i < sizeof(reg); i++, ptr += 2)
	    addr[i] = (hex(ptr[0])<<4) + hex(ptr[1]);
	update_stack(reg[R_ESP]);
	strcpy(buf, "OK");
	break;
    case 'm': /* read memory */
	strcpy(buf, "E01");
	ptr = start + 1;
	addr = (caddr_t)simple_strtoul(ptr, &ptr, 16);
	addr = map_stack(addr);
	if (*ptr == ',') {
	    ptr++;
	    len = simple_strtoul(ptr, &ptr, 16);
	    if (verify_addr(addr, len))
		break;
	    for (i = 0; i < len; i++) {
		buf[i<<1] = hexchars[addr[i] >> 4];
		buf[(i<<1)+1] = hexchars[addr[i] % 16];
	    }
	    buf[len*2] = '\0';
	}
	break;
    case 'M': /* write memory */
	strcpy(buf, "E02");
	ptr = start + 1;
	addr = (caddr_t)simple_strtoul(ptr, &ptr, 16);
	addr = map_stack(addr);
	if (*ptr == ',') {
	    ptr++;
	    len = simple_strtoul(ptr, &ptr, 16);
	    if (verify_addr(addr, len))
		break;
	    if (*ptr == ':') {
		ptr++;
		for (i = 0; i < len; i++, ptr += 2)
		    addr[i] = (hex(ptr[0])<<4) + hex(ptr[1]);
		strcpy(buf, "OK");
	    }
	}
	break;
    case 'c': /* continue */
	if (stack_base != stack) {
	    strcpy(buf, "S0b");
	    break;
	}
	ptr = start+1;
	addr = (caddr_t)simple_strtoul(ptr, &ptr, 16);
	if (addr != NULL)
	    reg[R_EIP] = (u_long)addr;
	call_kernel();
	strcpy(buf, "S05");
	break;
    case 's': /* step */
	strcpy(buf, "S05");
	break;
    case 'k': /* kill */
	break;
    }
    put_packet(buf);
    
    inq = inbuf;
}

/*====================================================================*/

static int debug_read(struct inode *inode, struct file *file,
		      char *buf, int count)
{
    int i;

    for (i = 0; i < count; i++) {
	if (outp == outq) break;
	put_fs_byte(*outp, buf);
	buf++; outp++;
	if (outp == outbuf+BUFSIZE)
	    outp = outbuf;
    }
    return i;
}

/*====================================================================*/

static enum { HEAD, BODY, CSUM1, CSUM2 } state = HEAD;

static int debug_write(struct inode *inode, struct file *file,
		       char *buf, int count)
{
    int i;
    char ch;
    
    for (i = 0; i < count; i++) {
	if (inq == inbuf+BUFSIZE) break;
	*inq = ch = get_fs_byte(buf);
	inq++; buf++;
	switch (state) {
	case HEAD:
	    if (ch == '$') state = BODY;
	    break;
	case BODY:
	    if (ch == '#') state = CSUM1;
	    break;
	case CSUM1:
	    state = CSUM2;
	    break;
	case CSUM2:
	    state = HEAD;
	    handle_packet();
	    break;
	}
    }
    return i;
}

/*====================================================================*/

static int debug_ioctl(struct inode *inode, struct file *file,
		       u_int cmd, u_long arg)
{
    return 0;
}

/*====================================================================*/

static int debug_open(struct inode *inode, struct file *file)
{
    if (!suser())
	return -EPERM;
    if (is_open)
	return -EBUSY;
    inq = inbuf;
    outp = outq = outbuf;

    reg[R_ESP] = save[R_ESP] = (u_long)(stack+STACKSIZE/2);
    asm("movw %cs, _reg+40");
    asm("movw %ss, _reg+44");
    asm("movw %ds, _reg+48");
    asm("movw %es, _reg+52");
    asm("movw %fs, _reg+56");
    asm("movw %gs, _reg+60");
    
    put_packet("S05");
    MOD_INC_USE_COUNT;
    return 0;
}

/*====================================================================*/

static void debug_close(struct inode *inode, struct file *file)
{
    is_open = 0;
    MOD_DEC_USE_COUNT;
}

/*====================================================================*/

char kernel_version[] = UTS_RELEASE;

int init_module(void)
{
    major_dev = register_chrdev(0, "kdebug", &debug_fops);
    if (major_dev == 0) {
	printk("unable to grab device # for debug driver\n");
	return -1;
    }
    return 0;
}

void cleanup_module(void)
{
    if (major_dev != 0)
	unregister_chrdev(major_dev, "kdebug");
}
