/*
 * Open Hack'Ware BIOS main.
 * 
 * Copyright (c) 2004-2005 Jocelyn Mayer
 * 
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License V2
 *   as published by the Free Software Foundation
 *
 *   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
 */

/*
 * Status:
 * - boots Linux 2.4 from floppy and IDE
 * - preliminary residual data support
 * - can find PREP boot images and Apple boot blocs
 *
 * TODO:
 * 1/ Cleanify boot partitions:
 *    allow more than one boot bloc + fix PREP load again
 * 2/ add ATAPI driver
 * 3/ add a prompt to let the user choose its boot device / PREP image
 * 4/ add POST
 * 5/ add VGA driver (SVGA/VESA ?)
 * 6/ add a user accessible setup
 * 7/ add netboot
 */

#include <stdlib.h>
#include <stdio.h>
#include "bios.h"

#include "char.h"

//#define DEBUG_MEMORY 1

/* Version string */
const unsigned char *BIOS_str =
"PPC Open Hack'Ware BIOS for qemu version " BIOS_VERSION "\n";
const unsigned char *copyright = "Copyright 2003-2005 Jocelyn Mayer\n";

uint32_t isa_io_base = ISA_IO_BASE;

/* Temporary hack: boots only from floppy */
int boot_device = 'a';

/* Some other PPC helper */
/* Setup a memory mapping, using BAT0
 * BATU:
 * BEPI  : bloc virtual address
 * BL    : area size bits (128 kB is 0, 256 1, 512 3, ...
 * Vs/Vp
 * BATL:
 * BPRN  : bloc real address align on 4MB boundary
 * WIMG  : cache access mode : not used
 * PP    : protection bits
 */
static void BAT_setup (int nr, uint32_t virtual, uint32_t physical,
                       uint32_t size, int Vs, int Vp, int PP)
{
    uint32_t sz_bits, tmp_sz, align, tmp;
    
    sz_bits = 0;
    align = 131072;
    DPRINTF("Set BAT %d v=%0x p=%0x size=%0x\n", nr, virtual, physical, size);
    if (size < 131072)
        size = 131072;
    for (tmp_sz = size / 131072; tmp_sz != 1; tmp_sz = tmp_sz >> 1) {
        sz_bits = (sz_bits << 1) + 1;
        align = align << 1;
    }
    tmp = virtual & ~(align - 1);  /* Align virtual area start */
    tmp |= sz_bits << 2;           /* Fix BAT size             */
    tmp |= Vs << 1;                /* Supervisor access        */
    tmp |= Vp;                     /* User access              */
    DPRINTF("Set BATU%d to %0x\n", nr, tmp);
    switch (nr) {
    case 0:
        /* Setup IBAT0U */
        MTSPR(528, tmp);
        /* Setup DBAT0U */
        MTSPR(536, tmp);
        break;
    case 1:
        /* Setup DBAT1U */
        MTSPR(538, tmp);
        break;
    case 2:
        /* Setup DBAT2U */
        MTSPR(540, tmp);
        break;
    }
    tmp = physical & ~(align - 1); /* Align physical area start */
    tmp |= 0;                      /* Don't care about WIMG     */
    tmp |= PP;                     /* Protection                */
    DPRINTF("Set BATL%d to %0x\n", nr, tmp);
    switch (nr) {
    case 0:
        /* Setup IBAT0L */
        MTSPR(529, tmp);
        /* Setup DBAT0L */
        MTSPR(537, tmp);
        break;
    case 1:
        /* Setup DBAT1L */
        MTSPR(539, tmp);
        break;
    case 2:
        /* Setup DBAT2L */
        MTSPR(541, tmp);
        break;
    }
}

typedef struct PPC_CPU_t {
    uint32_t pvr;
    uint32_t mask;
    unsigned char *name;
} PPC_CPU_t;

static const PPC_CPU_t CPU_PPC[] = {
    /* For now, know only G3 */
    {
        0x00080000,
        0xFFFF0000,
        "PowerPC,G3",
    },
    {
        0x00000000,
        0x00000000,
        "PowerPC,generic",
    },
};

static const unsigned char *CPU_get_name (uint32_t pvr)
{
    int i;

    for (i = 0;; i++) {
        if ((pvr & CPU_PPC[i].mask) == CPU_PPC[i].pvr)
            return CPU_PPC[i].name;
    }

    return NULL;
}

#define TB_FREQ (10 * 1000 * 1000) // XXX: should calibrate
void usleep (uint32_t usec)
{
#if 0 // Buggy: makes OpenDarwin crash (!)
    uint32_t tb0[2], tb1[2], count[2];
    uint32_t tpu;
    int wrap = 0;

    tpu = TB_FREQ / (1000 * 1000);
    mul64(count, usec, tpu);
    mftb(tb0);
    add64(count, count, tb0);
    if (count[0] < tb0[0])
        wrap = 1;
    while (1) {
        mftb(tb1);
        if (wrap == 1 && tb1[0] < tb0[0])
            wrap = 0;
        if (wrap == 0 &&
            (tb1[0] > count[0] ||
             (tb1[0] == count[0] && tb1[1] >= count[1])))
            break;
        tb0[0] = tb1[0];
    }
#else
    uint32_t i, j;
    
    for (i = 0; i < (usec >> 16) * 50; i++) {
        for (j = 0; j < (usec & 0xFFFF) * 50; j++) {
            continue;
        }
    }
#endif
}

void sleep (int sec)
{
    int i;

    for (i = 0; i < sec; i++)
        usleep(1 * 1000 * 1000);
}

/* Stolen from Linux code */
#define CRCPOLY_LE 0xedb88320

uint32_t crc32 (uint32_t crc, const uint8_t *p, int len)
{
    int i;

    while (len--) {
        crc ^= *p++;
        for (i = 0; i < 8; i++)
            crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0);
    }

    return crc;
}

/* Fake write */
int write (unused int fd, unused const char *buf, int len)
{
    return len;
}

/* BIOS library functions */
/* Fake memory management (to be removed) */
void *mem_align (unused int align)
{
    return malloc(0);
}
#if 1
void free (unused void *p)
{
}
#endif

void freep (void *p)
{
    void **_p = p;

    free(*_p);
    *_p = NULL;
}

static inline int in_area (const void *buf,
                           const void *start, const void *end)
{
    return buf >= start && buf <= end;
}

#ifdef DEBUG_MEMORY
static void *load_dest, *load_end;
static int relax_check;
#endif

void set_loadinfo (unused void *load_base, unused uint32_t size)
{
#ifdef DEBUG_MEMORY
    load_dest = load_base;
    load_end = (char *)load_dest + size;
#endif
}

void set_check (unused int do_it)
{
#ifdef DEBUG_MEMORY
    relax_check = do_it == 0 ? 1 : 0;
#endif
}

void check_location (unused const void *buf,
                     unused const char *func,
                     unused const char *name)
{
#ifdef DEBUG_MEMORY
    if (relax_check != 0)
        return;
    if (!in_area(buf, &_data_start, &_data_end) &&
        !in_area(buf, &_OF_vars_start, &_OF_vars_end) &&
        !in_area(buf, &_sdata_start, &_sdata_end) &&
        !in_area(buf, &_ro_start, &_ro_end) &&
        !in_area(buf, &_RTAS_data_start, &_RTAS_data_end) &&
        !in_area(buf, &_bss_start, &_bss_end) &&
        !in_area(buf, &_ram_start, malloc_base) &&
        /* Let's say 64 kB of stack is enough */
        !in_area(buf, (void *)0x5ff0000, (void *)0x6000000) &&
        !in_area(buf, load_dest, load_end) &&
        /* IO area */
        !in_area(buf, (void *)0x80000000, (void *)0x88000000)) {
        printf("**************************************************************"
               "**************\n");
        printf("%s: %s: %p\n", func, name, buf);
        printf("    data:       %p %p\n", &_data_start, &_data_end);
        printf("    OF_vars:    %p %p\n", &_OF_vars_start, &_OF_vars_end);
        printf("    sdata:      %p %p\n", &_sdata_start, &_sdata_end);
        printf("    rodata:     %p %p\n", &_ro_start, &_ro_end);
        printf("    RTAS_data:  %p %p\n", &_RTAS_data_start, &_RTAS_data_end);
        printf("    bss:        %p %p\n", &_bss_start, &_bss_end);
        printf("    mallocated: %p %p\n", &_ram_start, malloc_base);
        printf("    stack     : %p %p\n", (void *)0x5ff0000,
               (void *)0x6000000);
        printf("    load image: %p %p\n", load_dest, load_end);
        printf("**************************************************************"
               "**************\n");
        bug();
    }
#endif
}

/* No overflow check here... */
long strtol (const unsigned char *str, unsigned char **end, int base)
{
    long ret = 0, tmp, sign = 1;

    check_location(str, __func__, "str");
    if (base < 0 || base > 36)
        return 0;
    for (; *str == ' '; str++)
        continue;
    if (*str == '-') {
        sign = -1;
        str++;
    }
    for (;; str++) {
        tmp = *str;
        if (tmp < '0')
            break;
        if (base <= 10) {
            if (tmp > '0' + base - 1)
                break;
            tmp -= '0';
        } else {
            if (tmp <= '9') {
                tmp -= '0';
            } else {
                tmp &= ~0x20;
                if (tmp < 'A' || tmp > 'A' + base - 11)
                    break;
                tmp += 10 - 'A';
            }
        }
        ret = (ret * base) + tmp;
    }
    if (sign == -1)
        ret = -ret;
    if (end != NULL)
        *end = (unsigned char *)str;

    return ret;
}

nvram_t *nvram;
int arch;
/* HACK... */
int vga_width, vga_height, vga_depth;

part_t *boot_part;

/* XXX: to fix */
void mm_init (uint32_t memsize);
int page_descrs_init (void);

int main (void)
{
    bloc_device_t *bd;
    pci_host_t *pci_main;
    void *res, *bootinfos;
    void *boot_image, *cmdline, *ramdisk;
    void *load_base, *load_entry, *last_alloc, *load_end;
    uint32_t memsize, boot_image_size, cmdline_size, ramdisk_size;
    uint32_t boot_nb;
    int boot_device, i;
    static const uint32_t isa_base_tab[3] = {
        0x80000000, /* PREP */
        0xFE000000, /* Grackle (Heathrow) */
        0xF2000000, /* UniNorth (Mac99)  */
    };

    /* Retrieve NVRAM configuration */
    for(i = 0; i < 3; i++) {
        isa_io_base = isa_base_tab[i];
    nvram = NVRAM_get_config(&memsize, &boot_device,
                             &boot_image, &boot_image_size,
                             &cmdline, &cmdline_size,
                             &ramdisk, &ramdisk_size);
        if (nvram)
            break;
        }
    if (i == 3) {
        ERROR("Unable to load configuration from NVRAM. Aborting...\n");
        return -1;
    }
#if 1
    mm_init(memsize);
#endif
#if 1
    page_descrs_init();
#endif
#ifdef USE_OPENFIRMWARE
    OF_init();
#endif
    pci_main = pci_init();
    if (pci_main == NULL)
        ERROR("Unable to configure PCI\n");
#ifdef USE_OPENFIRMWARE
    /* XXX: this mess needs a serious cleanup... */
    {
        const unsigned char *cpu_name;
        uint32_t pvr = mfpvr();

        cpu_name = CPU_get_name(pvr);
        OF_register_cpu(cpu_name, 0, pvr,
                        200 * 1000 * 1000, 200 * 1000 * 1000,
                        100 * 1000 * 1000, 100 * 1000 * 1000,
                        0x0092);
    }
    OF_register_memory(memsize, 512 * 1024 /* TOFIX */);
    /* Claim memory used by the BIOS */
    OF_claim_virt(0x05800000, 0x00080000, NULL);
    OF_register_bootargs(cmdline);
#endif
    if (isa_io_base == 0x80000000 || 1) {
        pc_serial_register(0x3F8);
#ifdef USE_OPENFIRMWARE
        OF_register_bus("isa", isa_io_base, "ISA");
        OF_register_serial("isa", "com1", 0x3F8, 4);
        OF_register_stdio("com1", "com1");
#endif
    }
#ifdef USE_OPENFIRMWARE
    RTAS_init();
#endif
    /* Get a console */
    console_open();
    printf("%s", BIOS_str);
    printf("Build " stringify(BUILD_DATE) " " stringify(BUILD_TIME) "\n");
    printf("%s\n", copyright);
    printf("Memory size: %d MB. \nBooting from device %c\n",
           memsize >> 20, boot_device);
    vga_puts(BIOS_str);
    vga_puts("Build " stringify(BUILD_DATE) " " stringify(BUILD_TIME) "\n");
    vga_puts(copyright);
    vga_puts("\n");

#if 0
    /* QEMU is quite incoherent: d is cdrom, not second drive */
    /* XXX: should probe CD-ROM position */
    if (boot_device == 'd')
        boot_device = 'e';
#endif
    /* Open boot device */
    boot_part = bd_probe(boot_device);
    if (boot_device == 'm') {
        bd = bd_get('m');
        if (bd == NULL) {
            ERROR("Unable to get memory bloc device\n");
            return -1;
        }
        printf("boot device: %p image %p size %d\n",
               bd, boot_image, boot_image_size);
        bd_ioctl(bd, MEM_SET_ADDR, boot_image);
        bd_ioctl(bd, MEM_SET_SIZE, &boot_image_size);
        boot_part = part_probe(bd, 1);
        bd_put(bd);
        printf("boot device: %p\n", bd);
    }
    if (boot_part == NULL) {
        ERROR("Found no boot partition!\n");
        return -1;
    }
    ERROR("Found boot partition : %p %p\n", boot_part, part_fs(boot_part));
    mem_align(0x00001000);
    res = malloc(0x4000);
    last_alloc = malloc(0);
    boot_nb = 0;
    DPRINTF("Load base: %p - residual data: %p %p %p %p\n",
            load_base, res, last_alloc, boot_part, part_fs(boot_part));
    /* Load the whole boot image */
    if (bootfile_load((void *)&load_base, (void *)&load_entry,
                      (void *)&load_end, boot_part, -1, NULL, 0) < 0) {
        printf("Unable to load boot file\n");
        return -1;
    }
#ifdef USE_OPENFIRMWARE
    DPRINTF("Boot parameters: res: %p load_base: %p OF: %p entry: %p\n",
            res, load_base, &OF_entry, load_entry);
#else
    DPRINTF("Boot parameters: res: %p load_base: %p OF: %p entry: %p\n",
            res, load_base, NULL, load_entry);
#endif
    DPRINTF("Boot: %0x %0x %0x %0x\n", *(uint32_t *)load_entry,
            *((uint32_t *)load_entry + 1),
            *((uint32_t *)load_entry + 2),
            *((uint32_t *)load_entry + 3));
    /* Fill residual data structure */
    residual_build(res, memsize, (uint32_t)load_base,
                   (uint32_t)boot_nb * part_blocsize(boot_part),
                   (uint32_t)last_alloc);
    /* Fill bootinfos */
    bootinfos = (void *)(((uint32_t)load_end + (1 << 20) - 1) & ~((1 << 20) - 1));
    if (boot_image != NULL && cmdline == NULL) {
        cmdline = bootinfos + 0x1000;
        *(char *)cmdline = '\0';
    }
    set_check(0);
    prepare_bootinfos(bootinfos, memsize, cmdline, ramdisk, ramdisk_size);
    set_check(1);
    if (part_flags(boot_part) & PART_PREP) {
        vga_prep_init();
    }
    /* Format NVRAM */
    NVRAM_format(nvram);
    /* Setup MMU and boot the loaded image */
    DPRINTF("\nSet up MMU context\n");
    /* Map all memory with transparent mapping */
    BAT_setup(0, 0x00000000, 0x00000000, memsize, 1, 0, 2);
    /* Open IO ports */
    BAT_setup(1, isa_io_base, isa_io_base, 0x00800000, 1, 1, 2);
#if 0
    /* Open the frame-buffer area */
    BAT_setup(2, vga_fb_phys_addr, vga_fb_phys_addr, 0x00200000, 1, 1, 2);
#else
    if (pci_main != NULL) {
        uint32_t mem_start, mem_size;
        pci_get_mem_range(pci_main, &mem_start, &mem_size);
        BAT_setup(2, mem_start, mem_start, mem_size, 1, 1, 2);
    }
#endif
    /* Enable MMU */
    MMU_on();
    usleep(500);
    dprintf("Boot: %08x %08x %08x %08x\n", *(uint32_t *)load_base,
            *(uint32_t *)(load_base + 4), *(uint32_t *)(load_base + 8),
            *(uint32_t *)(load_base + 12));
    dprintf("Bootinfos at : %p\n", bootinfos);
    printf("\nNow boot it... (%p)\n\n", malloc(0));
    usleep(500);
    {
        register uint32_t r1 __asm__ ("r1");
        printf("stack: %0x malloc_base: %p 0x05800000 0x06000000\n",
               r1, malloc(0));
    }
    
    if (part_flags(boot_part) & PART_PREP) {
        printf("PREP boot... %p %p\n", load_entry, load_base);
        /* Hack for Linux to boot without OpenFirmware */
        put_be32(load_base, 0xdeadc0de);
    }
    transfer_handler(res                      /* residual data          */,
                     load_base                /* load address           */,
#ifdef USE_OPENFIRMWARE
                     &OF_entry                /* OF entry point         */,
#else
                     NULL,
#endif
                     bootinfos                /* bootinfos for Linux    */,
                     cmdline                  /* command line for Linux */,
                     NULL                     /* unused for now         */,
                     load_entry               /* start address     */,
#if 0
                     mem_align(0x00100000)    /* Stack base             */
#else
                     (void *)0x05800000
#endif
                     );
    /* Should never come here */

    return 0;
}