/******************************************************************************
 * @file            cm32test.c
 *
 * Released to the public domain.
 *
 * Anyone and anything may copy, edit, publish, use, compile, sell and
 * distribute this work and all its parts in any form for any purpose,
 * commercial and non-commercial, without any restrictions, without
 * complying with any conditions and by any means.
 *****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct gdescriptor {
    unsigned char limit[2];
    unsigned char base[3];
    unsigned char access_byte;
    unsigned char limit_flags;
    unsigned char base2;
};

int get_cs (void);

void disable_interrupts (void);
void enable_interrupts (void);

void save_gdt (void *gdtr);
void load_gdt (void *gdt, int size);

void call_cm32 (int cm32_cs, void (*test32)(void));
void test32 (void);

int main (int argc, char **argv)
{
    unsigned char gdtr[10];
    size_t original_gdt_size;
    void *original_gdt;
    size_t gdt_size;
    struct gdescriptor *gdt;
    int cs;
    int cm32_cs;
    
    printf ("start\n");
    save_gdt (gdtr);
    original_gdt_size = (*(unsigned short *)gdtr) + 1;
    original_gdt = *(void **)(gdtr + 2);
    cs = get_cs ();
    printf ("gdt size: %u ptr: %p cs: %i\n", original_gdt_size, original_gdt, cs);

    gdt_size = original_gdt_size + sizeof (*gdt);
    gdt = malloc (gdt_size);
    cm32_cs = original_gdt_size;
    memcpy (gdt, original_gdt, original_gdt_size);
    gdt[cm32_cs / sizeof (*gdt)] = gdt[cs / sizeof (*gdt)];
    /* Converts the duplicated code segment descriptor to 32 bit code
     * by disabling long mode flag and setting size flag.
     */
    gdt[cm32_cs / sizeof (*gdt)].limit_flags &= ~0x20;
    gdt[cm32_cs / sizeof (*gdt)].limit_flags |= 0x40;
    disable_interrupts ();
    load_gdt (gdt, gdt_size);
    enable_interrupts ();

    printf ("running with new gdt\n");

    printf ("trying cm32 (cm32_cs: %i)\n", cm32_cs);
    call_cm32 (cm32_cs, &test32);
    printf ("success\n");

    printf ("restoring old gdt\n");
    disable_interrupts ();
    load_gdt (original_gdt, original_gdt_size);
    enable_interrupts ();

    free (gdt);
    
    printf ("end, press enter\n");
    getc (stdin);

    return 0;
}
