/* 915 resolution by steve tomljenovic
 *
 * This was tested only on Sony VGN-FS550.  Use at your own risk
 *
 * This code is based on the techniques used in :
 *
 *   - 855patch.  Many thanks to Christian Zietz (czietz gmx net)
 *     for demonstrating how to shadow the VBIOS into system RAM
 *     and then modify it.
 *
 *   - 1280patch by Andrew Tipton (andrewtipton null li).
 *
 *   - 855resolution by Alain Poirier
 *
 * This source code is into the public domain.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define __USE_GNU
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/io.h>
#include <unistd.h>
#include <assert.h>



#define NEW(a) ((a *)(calloc(1, sizeof(a))))
#define FREE(a) (free(a))

#define VBIOS_START         0xc0000
#define VBIOS_SIZE          0x10000

#define VBIOS_FILE    "/dev/mem"
#define VBIOS_OFFSET_IN_FILE VBIOS_START

#define CFG_SIGNATURE       "BIOS_DATA_BLOCK "

#define FALSE 0
#define TRUE 1

typedef unsigned char * address;
typedef unsigned char boolean;

typedef struct {
    unsigned char mode;
    unsigned char bits_per_pixel;
    unsigned short resolution;
    unsigned char unknown;
} __attribute__((packed)) vbios_mode;

typedef struct {
    unsigned char unknow1[2];
    unsigned char x1;
    unsigned char unknow2;
    unsigned char x2;
    unsigned char y1;
    unsigned char unknow3;
    unsigned char y2;
} __attribute__((packed)) vbios_resolution;


typedef struct {
    int bios_fd;
    address bios_ptr;

    address cnfg_ptr;

    vbios_mode * mode_table;
    int mode_table_size;

    boolean unlocked;
} vbios_map;


void initialize_system();
char * get_chipset(void);

vbios_map * open_vbios();
void close_vbios(vbios_map * map);

void unlock_vbios(vbios_map * map);
void relock_vbios(vbios_map * map);



void initialize_system(void) {
    if (iopl(3) < 0) {
        perror("Unable to obtain the proper IO permissions");
        exit(2);
    }
}

static unsigned int get_chipset_id(void) {
    outl(0x80000000, 0xcf8);
    return inl(0xcfc);
}

static char chipset_buffer[256];

char * get_chipset(void) {
    unsigned int id;
    char * name;
    
    id = get_chipset_id();

    switch (id) {
    case 0x25608086:
        name = "845G";
        break;
        
    case 0x35808086:
        name = "855GM";
        break;
        
    case 0x25708086:
        name = "865G";
        break;

    case 0x25908086:
        name = "915GM";
        break;

    default:
        sprintf(chipset_buffer, "Unknown (0x%08x)", id);
        name = chipset_buffer;
        break;
    }

    return name;
}


vbios_map * open_vbios() {
    vbios_map * map = NEW(vbios_map);

    /*
     *  Map the video bios to memory
     */
    
    map->bios_fd = open(VBIOS_FILE, O_RDWR);
    if(map->bios_fd < 0) {
        perror("Unable to open the BIOS file");
        exit(2);
    }

    map->bios_ptr = mmap((void *)VBIOS_START, VBIOS_SIZE,
                         PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
                         map->bios_fd, VBIOS_OFFSET_IN_FILE);

    if (map->bios_ptr == NULL) {
        fprintf(stderr, "Cannot mmap() the video BIOS\n");
        close(map->bios_fd);
        exit(2);
    }

    /*
     * figure out where the configuration information is
     */
    
    map->cnfg_ptr = memmem(map->bios_ptr, VBIOS_SIZE, CFG_SIGNATURE, strlen(CFG_SIGNATURE));

    if (map->cnfg_ptr == NULL) {
        fprintf(stderr, "Couldn't find the configuration area in the VBIOS!\n");
        close_vbios(map);
        exit(2);
    }

    /*
     * Figure out where the mode table is and which type of bios we have
     */
    
    {
        address p = map->bios_ptr;
        address limit = map->bios_ptr + VBIOS_SIZE - (3 * sizeof(vbios_mode));
        
        while (p < limit && map->mode_table == 0) {
            vbios_mode * mode_ptr = (vbios_mode *) p;
            
            if (((mode_ptr[0].mode & 0xf0) == 0x30) && ((mode_ptr[1].mode & 0xf0) == 0x30) &&
                ((mode_ptr[2].mode & 0xf0) == 0x30) && ((mode_ptr[3].mode & 0xf0) == 0x30)) {

                map->mode_table = mode_ptr;
            }
            
            p++;
        }

        if (map->mode_table == 0) {
            fprintf(stderr, "Unable to locate mode table!\n");
            close_vbios(map);
            exit(2);
        }
    }

    /*
     * Determine size of mode table
     */
    
    {
        vbios_mode * mode_ptr = map->mode_table;
        
        while (mode_ptr->mode != 0xff) {
            map->mode_table_size++;
            mode_ptr++;
        }
    }
    
    
    return map;
}

void close_vbios(vbios_map * map) {
    assert(!map->unlocked);

    if(map->bios_ptr == NULL) {
        fprintf(stderr, "BIOS should be open already!\n");
        exit(2);
    }

    munmap(map->bios_ptr, VBIOS_SIZE);
    close(map->bios_fd);

    FREE(map);
}

void unlock_vbios(vbios_map * map) {
    assert(!map->unlocked);

    map->unlocked = TRUE;

    outl(0x80000090, 0xcf8);

#if DEBUG
    {
        unsigned int t = inl(0xcfc);
        printf("unlock PAM: (0x%08x)\n", t);
    }
#endif
    
    outb(0x33, 0xcfd);
    outb(0x33, 0xcfe);

#if DEBUG
    {
        unsigned int t = inl(0xcfc);
        printf("unlock PAM: (0x%08x)\n", t);
    }
#endif

}

void relock_vbios(vbios_map * map) {
    assert(map->unlocked);

    map->unlocked = FALSE;
    
    outl(0x80000090, 0xcf8);

#if DEBUG
    {
        unsigned int t = inl(0xcfc);
        printf("relock PAM: (0x%08x)\n", t);
    }
#endif

    outb(0x11, 0xcfd);
    outb(0x11, 0xcfe);

#if DEBUG
    {
        unsigned int t = inl(0xcfc);
        printf("relock PAM: (0x%08x)\n", t);
    }
#endif
}


vbios_resolution * map_resolution(vbios_map * map, unsigned short res) {

    vbios_resolution * ptr = ((vbios_resolution*)(map->bios_ptr + res)); 

    return ptr;
}


void list_modes(vbios_map *map) {
    unsigned int i, x, y;

    for (i=0; i < map->mode_table_size; i++) {
        vbios_resolution * res = map_resolution(map, map->mode_table[i].resolution);

        x = ((((unsigned int) res->x2) & 0xf0) << 4) | res->x1;
        y = ((((unsigned int) res->y2) & 0xf0) << 4) | res->y1;

        if (x != 0 && y != 0) {
            printf("Mode %02x : %dx%d, %d bits/pixel\n", map->mode_table[i].mode, x, y, map->mode_table[i].bits_per_pixel);
        }
    }
}


void set_mode(vbios_map * map, unsigned int mode, unsigned int x, unsigned int y) {
    unsigned int i;


    for (i=0; i < map->mode_table_size; i++) {
        if (map->mode_table[i].mode == mode) {
            vbios_resolution * res = map_resolution(map, map->mode_table[i].resolution);

            res->x2 = (res->x2 & 0x0f) | ((x >> 4) & 0xf0);
            res->x1 = (x & 0xff);
            
            res->y2 = (res->y2 & 0x0f) | ((y >> 4) & 0xf0);
            res->y1 = (y & 0xff);
        }
    }

    
}   

int parse_args(int argc, char *argv[], int *list, int *mode, int *x, int *y) {
    int index = 1;

    *list = *mode = *x = *y = 0;

    if ((argc > index) && !strcmp(argv[index], "-l")) {
        *list = 1;
        index++;

        if(argc<=index) {
            return 0;
        }
    }
    
    if(argc-index != 3) {
        return -1;
    }

    *mode = (int) strtol(argv[index], NULL, 16);
    *x = atoi(argv[index+1]);
    *y = atoi(argv[index+2]);

    return 0;
}

void usage(char *name) {
    printf("Usage: %s [-l] [mode X Y]\n", name);
    printf("  Set the resolution to XxY for mode\n");
    printf("  Options:\n");
    printf("    -l display the modes found into the vbios\n");
}

int main (int argc, char *argv[]) {
    vbios_map * map;
    char * chipset;
    int list, mode, x, y;

    printf("Intel 915GM VBIOS Hack : version 0.1\n\n");

    if (parse_args(argc, argv, &list, &mode, &x, &y) == -1) {
        usage(argv[0]);
        return 2;
    }

    initialize_system();
    
    chipset = get_chipset();

    printf("Chipset: %s\n", chipset);

    map = open_vbios();

    printf("\n");

    if (list) {
        list_modes(map);
    }

    if (mode!=0 && x!=0 && y!=0) {
        unlock_vbios(map);
        set_mode(map, mode, x, y);
        relock_vbios(map);
        
        printf("** Patch mode %02x to resolution %dx%d complete\n", mode, x, y);
        
        if (list) {
            list_modes(map);
        }
    }

    close_vbios(map);
    
    return 0;
}

