/*
 * overclock_routerstation.c
 *
 * Copyright (C) 2009 Sebastian Gottschall <gottschall@dd-wrt.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * usage:
 * overclock_routerstation mhz
 * 
 * valid values for mhz are 200, 300 ,333, 400, 600, 680, 720, 800
 */
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>

int overclock(FILE * out, char *freq, int value)
{
    fseek(out, 0xd3, SEEK_SET);
    int val = getc(out);
    if (val == value) {
        fprintf(stderr, "board already clocked to %sMhz\n", freq);
        return -1;
    }
    fseek(out, 0xd3, SEEK_SET);
    putc(value, out);
    return 0;
}

int overclock_3(FILE * out, char *freq, int value)
{
    fseek(out, 0x3f, SEEK_SET);
    int val = getc(out);
    if (val == value) {
        fprintf(stderr, "board already clocked to %sMhz\n", freq);
        return -1;
    }
    fseek(out, 0x3f, SEEK_SET);
    putc(value, out);
    return 0;
}

void start_overclock(int mhz)
{
    long len;
    long i;

    FILE *in = fopen("/dev/mtdblock0", "rb");
    fseek(in, 0, SEEK_END);
    len = ftell(in);
    rewind(in);
    char check[8];
    char check2[8];
    char values[8] = { 0x24, 0x08, 0x00, 0xaa, 0x15, 0x09, 0x00, 0x04 };
    char values2[8] = { 0x24, 0x0a, 0x00, 0x0f, 0x11, 0x2a, 0x00, 0x04 };
    fseek(in, 0xc0, SEEK_SET);
    fread(check, 1, 8, in);
    fseek(in, 0xc4, SEEK_SET);
    fread(check2, 1, 8, in);
    int ret1 = 0xff;
    int ret2 = 0xff;
    int ret3 = 0xff;
    if ((ret1 = memcmp(check, values, 8))
        && (ret2 = memcmp(check2, values, 8))
        && (ret3 = memcmp(check2, values2, 8))) {
        fprintf(stderr,
            "no compatible routerstation bootloader found\n");
        fclose(in);
        return;
    }
    if (!ret1)
        fprintf(stderr, "bootloader rev1 found\n");
    if (!ret2)
        fprintf(stderr, "bootloader rev2 found\n");
    if (!ret3)
        fprintf(stderr, "bootloader rev3 found\n");
    FILE *out = fopen("/tmp/boot", "w+b");
    rewind(in);
    for (i = 0; i < len; i++)
        putc(getc(in), out);
    fclose(in);
    int ret = 1;
    if (!ret3) {
        if (mhz == 200)
            ret = overclock_3(out, "200", 0x1);
        if (mhz == 300)
            ret = overclock_3(out, "300", 0x2);
        if (mhz == 333)
            ret = overclock_3(out, "333", 0x3);
        if (mhz == 400)
            ret = overclock_3(out, "400", 0x6);
        if (mhz == 600)
            ret = overclock_3(out, "600", 0x7);
        if (mhz == 680)
            ret = overclock_3(out, "680", 0xc);    //special ubiquiti setting with different ddram clock settings
        if (mhz == 720)
            ret = overclock_3(out, "720", 0xe);    //need to be validated
        if (mhz == 800)
            ret = overclock_3(out, "800", 0xf);
    } else {
        if (mhz == 200)
            ret = overclock(out, "200", 0x1);
        if (mhz == 300)
            ret = overclock(out, "300", 0x2);
        if (mhz == 333)
            ret = overclock(out, "333", 0x3);
        if (mhz == 400)
            ret = overclock(out, "400", 0x6);
        if (mhz == 600)
            ret = overclock(out, "600", 0x7);
        if (mhz == 680)
            ret = overclock(out, "680", 0xa);    //special ubiquiti setting with different ddram clock settings
        if (mhz == 720)
            ret = overclock(out, "720", 0x1e);    //magic atheros values
        if (mhz == 800)
            ret = overclock(out, "800", 0x1f);
    }

    fclose(out);
    if (!ret) {
        fprintf(stderr, "write new bootloader\n");

        pid_t child_pid;
        int child_status=-1;

        child_pid = fork();

        if (child_pid == -1) {
            fprintf(stderr,"Fork failed.\n");
            exit(1);
        }
        else if (child_pid > 0){
            /* This is the parent process. */
            ret = child_pid;
        }
            else {
                /* child process */
                execlp("mtd", "mtd", "-f", "write", "/tmp/boot", "RedBoot", NULL);
                /* The execl function returns only if an error occurs. */
                fprintf (stderr, "An error occurred writing new Redboot.\n");
                exit(-1);
            }

        wait(&child_status);
        if (WIFEXITED (child_status))
            fprintf(stderr, "board now clocked to %u Mhz\n", mhz);
        else
            fprintf (stderr, "An error occurred writing new Redboot.\n");
    }
}

int main (int argc, char *argv[]) 
{
    int mhz;

    if (argc == 2) {
        mhz = atoi(argv[1]);
        if (mhz != 200 && mhz != 300 && mhz != 333 && mhz != 400 && mhz != 600 && mhz != 680 && mhz != 720 && mhz != 800) {
            fprintf(stderr, "Allowed values are 200,300,333,400,600,680,720,800\n"); 
            exit(1);
        } 
        else {
            start_overclock (mhz);
        }
    }
    else {
        fprintf(stderr, "Usage: %s mhz\nAllowed values for mhz are 200,300,333,400,600,680,720,800\n", argv[0]);
        exit(1);
    }

    return 0;

}

