#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <machine/segments.h>

extern int i386_get_ldt(int, union descriptor *, int);
extern int i386_set_ldt(int, union descriptor *, int);

int verbose = 0;
struct sigaction segv_act;

inline void set_fs(unsigned long val)
{
	__asm__ __volatile__("mov %0,%%fs"::"r" ((unsigned short) val));
}

inline unsigned char get_fs_byte(const char * addr)
{
	unsigned register char _v;

	__asm__ ("movb %%fs:%1,%0":"=q" (_v):"m" (*addr));
	return _v;
}

inline unsigned short get_cs(void)
{
	unsigned register short _v;

	__asm__ ("movw %%cs,%0"::"r" ((unsigned short) _v));
	return _v;
}

int
check_desc(unsigned int desc)
{
	desc = LSEL(desc, SEL_UPL);
	set_fs(desc);
	return(get_fs_byte((char *) 0));
}

void
gated_call()
{
	printf("Called from call gate...");
	__asm__ __volatile__("popl %ebp");
	__asm__ __volatile__(".byte 0xcb");
}

struct segment_descriptor *
make_sd(unsigned base, unsigned limit, int type, int dpl, int seg32, int inpgs)
{
	static struct segment_descriptor d;

	d.sd_lolimit = limit & 0x0000ffff;
	d.sd_lobase  = base & 0x00ffffff;
	d.sd_type    = type & 0x01f;
	d.sd_dpl     = dpl & 0x3;
	d.sd_p	     = 1;
	d.sd_hilimit = (limit & 0x00ff0000) >> 16;
	d.sd_xx	     = 0;
	d.sd_def32   = seg32?1:0;
	d.sd_gran    = inpgs?1:0;
	d.sd_hibase  = (base & 0xff000000) >> 24;

	return (&d);
}

struct gate_descriptor *
make_gd(unsigned offset, unsigned int sel, unsigned stkcpy, int type, int dpl)
{
	static struct gate_descriptor d;

	d.gd_looffset = offset & 0x0000ffff;
	d.gd_selector = sel & 0xffff;
	d.gd_stkcpy   = stkcpy & 0x1ff;
	d.gd_type     = type & 0x1ff;
	d.gd_dpl      = dpl & 0x3;
	d.gd_p	      = 1;
	d.gd_hioffset = (offset & 0xffff0000) >> 16;

	return(&d);
}

void
print_ldt(union descriptor *dp)
{
	unsigned long base_addr, limit, offset, selector, stack_copy;
	int type, dpl, i;
	unsigned long *lp = (unsigned long *)dp;
    
	/* First 32 bits of descriptor */
	selector = base_addr = (*lp >> 16) & 0x0000FFFF;
	offset = limit = *lp & 0x0000FFFF;
	lp++;
	
	/* First 32 bits of descriptor */
	base_addr |= (*lp & 0xFF000000) | ((*lp << 16) & 0x00FF0000);
	limit |= (*lp & 0x000F0000);
	type = dp->sd.sd_type;
	dpl = dp->sd.sd_dpl;
	stack_copy = dp->gd.gd_stkcpy;
	offset |= (*lp >> 16) & 0x0000FFFF;
    
	if (type == SDT_SYS386CGT || type == SDT_SYS286CGT)
		printf("LDT: Gate Off %08.8x, Sel   %05.5x, Stkcpy %d DPL %d, Type %d\n",
			offset, selector, stack_copy, dpl, type);
	else
		printf("LDT: Seg Base %08.8x, Limit %05.5x, DPL %d, Type %d\n",
			base_addr, limit, dpl, type);
	printf("	  ");
	if (*lp & 0x100)
		printf("Accessed, ");
	if (*lp & 8000)
		printf("Present, ");
	if (type != SDT_SYS386CGT && type != SDT_SYS286CGT) {
		if (*lp & 0x100000)
			printf("User, ");
		if (*lp & 0x200000)
			printf("X, ");
		if (*lp & 0x400000)
			printf("32, ");
		else
			printf("16, ");
		if (*lp & 0x800000)
			printf("page limit, ");
		else
			printf("byte limit, ");
	}
	printf("\n");
	printf("	  %08.8x %08.8x\n", *(lp), *(lp-1));
}

static void busfault(int signal, int code, struct sigcontext *sc)
{
	fprintf(stderr, "\nbus fault - investigate.\n");
	exit(1);
}

static void usage(int status)
{
	fprintf(stderr, "Usage: testldt [-v]\n");
        exit(status);
}

#define MAX_USER_LDT 1024
main(int argc, char *argv[])
{
	union descriptor ldt[MAX_USER_LDT];
	int num, n, ch;
	unsigned int cs = get_cs();
	char *data;
	struct segment_descriptor *sd;
	struct gate_descriptor *gd;
	
	segv_act.sa_handler = (sig_t) busfault;
	if (sigaction(SIGBUS, &segv_act, NULL) < 0) {
		perror("sigaction");
		exit(1);
	}

	while ((ch = getopt(argc, argv, "v")) != EOF) {
		switch (ch) {
		case 'v':
		    verbose++;
		    break;
		default:
		    usage(1);
		    break;
		}
	}

	printf("Testing i386_get_ldt...\n");
	if ((num = i386_get_ldt(0, ldt, MAX_USER_LDT)) < 0) {
		perror("get_ldt");
		exit(2);
	}
	if (num == 0) {
	    fprintf(stderr, "ERROR: i386_get_ldt() return 0 default LDT entries.\n");
	    exit(1);
	}

	if (verbose) {
	    printf("Got %d (default) LDTs\n", num);
	    for (n=0; n < num; n++) {
		printf("Entry %d: ", n);
		print_ldt(&ldt[n]);
	    }
	}
	
	/*
	 * mmap a data area and assign an LDT to it
	 */
	printf("Testing i386_set_ldt...\n");
	data = (void *) mmap( (char *)0x005f0000, 0x0fff,
			     PROT_EXEC | PROT_READ | PROT_WRITE,
			     MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
	if (data == NULL) {
		perror("mmap");
		exit(1);
	}
	if (verbose) printf("data address: %8.8x\n", data);

	*data = 0x97;

	/* Get the next free LDT and set it to the allocated data. */
	sd = make_sd((unsigned)data, 4096, SDT_MEMRW, SEL_UPL, 0, 0);
	if ((num = i386_set_ldt(6, (union descriptor *)sd, 1)) < 0) {
		perror("set_ldt");
		exit(1);
	}
	if (verbose) printf("setldt returned: %d\n", num);
	if ((n = i386_get_ldt(num, ldt, 1)) < 0) {
		perror("get_ldt");
		exit(1);
	}
	if (verbose) {
		printf("Entry %d: ", num);
		print_ldt(&ldt[0]);
	}

	if (verbose) printf("Checking desc (should be 0x97): 0x%x\n", check_desc(num));
	if (check_desc(num) != 0x97) {
		fprintf(stderr, "ERROR: descriptor check failed: (should be 0x97): 0x%x\n", check_desc(num));
		exit(1);
	}
	
	/*
	 * Test a Call Gate
	 */
	printf("Testing Call Gate...");
	gd = make_gd((unsigned)gated_call, cs, 0, SDT_SYS386CGT, SEL_UPL);
	if ((num = i386_set_ldt(5, (union descriptor *)gd, 1)) < 0) {
		perror("set_ldt: call gate");
		exit(1);
	}
	if (verbose) printf("setldt returned: %d\n", num);
	if (verbose) printf("Call gate sel = 0x%x\n", LSEL(num, SEL_UPL));
	if ((n = i386_get_ldt(num, ldt, 1)) < 0) {
		perror("get_ldt");
		exit(1);
	}
	if (verbose) printf("Entry %d: ", num);
	if (verbose) print_ldt(&ldt[0]);

#if 0
	err = setldt(5,
		     gated_call,	/* Offset */
		     0x0001,		/* This selector is for the executable segment descriptor.  It
					   is the standard linux text descriptor. */
		     0x00008c00);	/* Descriptor flags (you can't set all, the OS protects some) */
	printf("setldt returned: %d\n", err);
#endif

	__asm__ __volatile__(".byte 0x9a"); /* This is a call to a call gate. */
	__asm__ __volatile__(".byte 0x00"); /* Value is ignored in a call gate but can be used. */
	__asm__ __volatile__(".byte 0x00"); /* by the called procedure. */
	__asm__ __volatile__(".byte 0x00");
	__asm__ __volatile__(".byte 0x00");
	__asm__ __volatile__(".byte 0x2f"); /* Selector 0x002f.	 This is index = 5 (the call gate), */
	__asm__ __volatile__(".byte 0x00"); /* and a requestor priveledge level of 3. */

	printf("Gated call returned\n");
	exit (0);
}
