/* Copyright (C) 2014 Stony Brook University
   This file is part of Graphene Library OS.

   Graphene Library OS is free software: you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   Graphene Library OS 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 Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/*
 * db_main.c
 *
 * This file contains the main function of the PAL loader, which loads and
 * processes environment, arguments and manifest.
 */

#include "pal_defs.h"
#include "pal_freebsd_defs.h"
#include "pal.h"
#include "pal_internal.h"
#include "pal_freebsd.h"
#include "pal_debug.h"
#include "pal_error.h"
#include "pal_security.h"
#include "api.h"

#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <elf/elf.h>
#include <sysdeps/generic/ldsodefs.h>
#include <sys/types.h>

/* At the begining of entry point, rsp starts at argc, then argvs,
   envps and auxvs. Here we store rsp to rdi, so it will not be
   messed up by function calls */
asm (".global pal_start \n"
     "  .type pal_start,@function \n"
     "pal_start: \n "
     "  movq %rsp, %rdi \n"
     "  call pal_bsd_main@PLT \n");

#define RTLD_BOOTSTRAP

/* pal_start is the entry point of libpal.so, which calls pal_main */
#define _ENTRY pal_start

struct pal_bsd_state bsd_state;
struct pal_sec pal_sec;

static int pagesz = PRESET_PAGESIZE;
static uid_t uid;
static gid_t gid;

static void pal_init_bootstrap (void * args, const char ** pal_name,
                                int * pargc,
                                const char *** pargv,
                                const char *** penvp,
                                ElfW(Addr) * baseaddr)
{
    /*
     * fetch arguments and environment variables, the previous stack
     * pointer is in rdi (arg). The stack structure starting at rdi
     * will look like:
     *            auxv[m - 1] = AT_NULL
     *            ...
     *            auxv[0]
     *            envp[n - 1] = NULL
     *            ...
     *            envp[0]
     *            argv[argc] = NULL
     *            argv[argc - 1]
     *            ...
     *            argv[0]
     *            argc
     *       ---------------------------------------
     *            user stack
     */
    const char ** all_args = (const char **) args;

    /* Workaround because sometimes BSD misaligns arguments on stack */
    if (all_args[0] == 0)
        all_args++;

    int argc = (uintptr_t) all_args[0];
    const char ** argv = &all_args[1];
    const char ** envp = argv + argc + 1;

    /* fetch environment information from aux vectors */
    void ** auxv = (void **) envp + 1;
    for (; *(auxv - 1); auxv++);
    ElfW(auxv_t) *av;
    ElfW(Addr) base = 0;
    for (av = (ElfW(auxv_t) *)auxv ; av->a_type != AT_NULL ; av++)
        switch (av->a_type) {
            case AT_PAGESZ:
                pagesz = av->a_un.a_val;
                break;
            case AT_UID:
            case AT_EUID:
                uid ^= av->a_un.a_val;
                break;
            case AT_GID:
            case AT_EGID:
                gid ^= av->a_un.a_val;
                break;
            case AT_BASE:
                base = (ElfW(Addr)) av->a_un.a_val;
                break;
        }

    *pal_name = argv[0];
    argv++;
    argc--;
    *pargc = argc;
    *pargv = argv;
    *penvp = envp;
    *baseaddr = base;
}

unsigned long _DkGetPagesize (void)
{
    return pagesz;
}

unsigned long _DkGetAllocationAlignment (void)
{
    return pagesz;
}

void _DkGetAvailableUserAddressRange (PAL_PTR * start, PAL_PTR * end,
                                      PAL_PTR * hole_start, PAL_PTR * hole_end)
{
    void * end_addr, * start_addr;

    if ((void *) TEXT_START - (void *) USER_ADDRESS_LOWEST >
        (void *) USER_ADDRESS_HIGHEST - (void *) DATA_END){
        end_addr = (void *) ALLOC_ALIGNDOWN(TEXT_START);
        start_addr = pal_sec.user_addr_base ? :
            (void *) USER_ADDRESS_LOWEST;
    } else {
        end_addr = (void *) USER_ADDRESS_HIGHEST;
        start_addr = (void *) ALLOC_ALIGNUP(DATA_END);
    }

    assert(ALLOC_ALIGNED(start_addr) && ALLOC_ALIGNED(end_addr));

    while (1) {
        if (start_addr >= end_addr)
            INIT_FAIL(PAL_ERROR_NOMEM, "no user memory available");

        void * mem = (void *) ARCH_MMAP(start_addr,
                                        pal_state.alloc_align,
                                        PROT_NONE,
                                        MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
                                        -1, 0);
        if (!IS_ERR_P(mem)) {
            INLINE_SYSCALL(munmap, 2, mem, pal_state.alloc_align);
            if (mem == start_addr)
                break;
        }

        start_addr = (void *) ((unsigned long) start_addr << 1);
    }

    *end   = (PAL_PTR) end_addr;
    *start = (PAL_PTR) start_addr;

    // Not used, so set it to an empty range.
    *hole_start = start_addr;
    *hole_end = start_addr;
}

PAL_NUM _DkGetProcessId (void)
{
    return (bsd_state.start_time & (~0xffff)) | bsd_state.pid;
}

PAL_NUM _DkGetHostId (void)
{
    return 0;
}

#include "dynamic_link.h"

void setup_pal_map (struct link_map * map);

static struct link_map pal_map;

#ifdef __x86_64__
# include "elf-x86_64.h"
#endif

void pal_bsd_main (void * args)
{
    const char * pal_name = NULL;
    PAL_HANDLE parent = NULL, exec = NULL, manifest = NULL;
    const char ** argv, ** envp;
    int argc;

    struct timeval time;
    INLINE_SYSCALL(gettimeofday, 2, &time, NULL);

    /* parse argc, argv, envp and auxv */
    pal_init_bootstrap(args, &pal_name, &argc, &argv, &envp, &pal_map.l_addr);
    pal_map.l_name = pal_name;
    elf_get_dynamic_info((void *) pal_map.l_addr + elf_machine_dynamic(),
                         pal_map.l_info, pal_map.l_addr);
    ELF_DYNAMIC_RELOCATE(&pal_map);

    init_slab_mgr(pagesz);
    setup_pal_map(&pal_map);

    bsd_state.start_time = 1000000ULL * time.tv_sec + time.tv_usec;
    bsd_state.pid = INLINE_SYSCALL(getpid, 0);
    bsd_state.uid = uid;
    bsd_state.gid = gid;

    if (!bsd_state.parent_pid)
        bsd_state.parent_pid = bsd_state.pid;

    PAL_HANDLE first_thread = malloc(HANDLE_SIZE(thread));
    SET_HANDLE_TYPE(first_thread, thread);
    first_thread->thread.tid = bsd_state.pid;

    init_child_process(&parent, &exec, &manifest);
    if (parent)
        goto done_init;

    int fd = INLINE_SYSCALL(open, 3, argv[0], O_RDONLY|O_CLOEXEC, 0);
    if (IS_ERR(fd))
        goto done_init;


    size_t len = strlen(argv[0]) + 1;
    PAL_HANDLE file = malloc(HANDLE_SIZE(file) + len);
    SET_HANDLE_TYPE(file, file);
    file->hdr.flags |= RFD(0)|WFD(0)|WRITABLE(0);
    file->file.fd = fd;

    char * path = (void *) file + HANDLE_SIZE(file);
    int ret = get_norm_path(argv[0], path, &len);
    if (ret < 0) {
        printf("Could not normalize path (%s): %s\n", argv[0], pal_strerror(ret));
        goto done_init;
    }
    file->file.realpath = path;

    if (!check_elf_object(file)) {
        exec = file;
        goto done_init;

    }
    manifest = file;

done_init:
    if (!parent && !exec && !manifest) {
        printf("USAGE: %s [executable|manifest] args ...\n", pal_name);
        _DkProcessExit(0);
        return;
    }
    signal_setup();

    /* jump to main function */
    pal_main(bsd_state.parent_pid, manifest, exec, NULL, parent, first_thread, argv, envp);
}

/* the following code is borrowed from CPUID */

static void cpuid (int cpuid_fd, unsigned int reg,
                   unsigned int words[], unsigned int ecx)
{
   asm("cpuid"
       : "=a" (words[PAL_CPUID_WORD_EAX]),
         "=b" (words[PAL_CPUID_WORD_EBX]),
         "=c" (words[PAL_CPUID_WORD_ECX]),
         "=d" (words[PAL_CPUID_WORD_EDX])
       : "a" (reg),
         "c" (ecx));
}

#define FOUR_CHARS_VALUE(s, w)      \
    (s)[0] = (w) & 0xff;            \
    (s)[1] = ((w) >>  8) & 0xff;    \
    (s)[2] = ((w) >> 16) & 0xff;    \
    (s)[3] = ((w) >> 24) & 0xff;

#define BPI  32
#define POWER2(power) (1ULL << (power))
#define RIGHTMASK(width) \
   (((unsigned long)(width) >= BPI) ? ~0ULL : POWER2(width) - 1ULL)

#define BIT_EXTRACT_LE(value, start, after) \
   (((unsigned long)(value) & RIGHTMASK(after)) >> (start))


static char * cpu_flags[]
       = { "fpu",    // "x87 FPU on chip"
           "vme",    // "virtual-8086 mode enhancement"
           "de",     // "debugging extensions"
           "pse",    // "page size extensions"
           "tsc",    // "time stamp counter"
           "msr",    // "RDMSR and WRMSR support"
           "pae",    // "physical address extensions"
           "mce",    // "machine check exception"
           "cx8",    // "CMPXCHG8B inst."
           "apic",   // "APIC on chip"
           NULL,
           "sep",    // "SYSENTER and SYSEXIT"
           "mtrr",   // "memory type range registers"
           "pge",    // "PTE global bit"
           "mca",    // "machine check architecture"
           "cmov",   // "conditional move/compare instruction"
           "pat",    // "page attribute table"
           "pse36",  // "page size extension"
           "pn",     // "processor serial number"
           "clflush",    // "CLFLUSH instruction"
           NULL,
           "dts"     // "debug store"
           "tm",     // "thermal monitor and clock ctrl"
           "mmx",    // "MMX Technology"
           "fxsr",   // "FXSAVE/FXRSTOR"
           "sse",    // "SSE extensions"
           "sse2",   // "SSE2 extensions"
           "ss",     // "self snoop"
           "ht",     // "hyper-threading / multi-core supported"
           "tm",     // "therm. monitor"
           "ia64",   // "IA64"
           "pbe",    // "pending break event"
        };

/*
 * Returns the number of online CPUs read from sysctl hw.ncpus, -errno on failure.
 */
int get_cpu_count(void) {
    int mib[2], cores;
    size_t len = sizeof(cores);
    mib[0] = CTL_HW;
    mib[1] = HW_NCPU;

    int rv = sysctl(mib, 2, &cores, &len, NULL, 0);
    if (rv < 0)
        return unix_to_pal_error(ERRNO(rv));

    return cores;
}

int _DkGetCPUInfo (PAL_CPU_INFO * ci)
{
    unsigned int words[PAL_CPUID_WORD_NUM];

    const size_t VENDOR_ID_SIZE = 13;
    char* vendor_id = malloc(VENDOR_ID_SIZE);
    cpuid(2, 0, words, 0);

    FOUR_CHARS_VALUE(&vendor_id[0], words[PAL_CPUID_WORD_EBX]);
    FOUR_CHARS_VALUE(&vendor_id[4], words[PAL_CPUID_WORD_EDX]);
    FOUR_CHARS_VALUE(&vendor_id[8], words[PAL_CPUID_WORD_ECX]);
    vendor_id[VENDOR_ID_SIZE - 1] = '\0';
    ci->cpu_vendor = vendor_id;

    const size_t BRAND_SIZE = 49;
    char* brand = malloc(BRAND_SIZE);
    cpuid(-2, 0x80000002, words, 0);
    memcpy(&brand[ 0], words, sizeof(unsigned int) * PAL_CPUID_WORD_NUM);
    cpuid(-2, 0x80000003, words, 0);
    memcpy(&brand[16], words, sizeof(unsigned int) * PAL_CPUID_WORD_NUM);
    cpuid(-2, 0x80000004, words, 0);
    memcpy(&brand[32], words, sizeof(unsigned int) * PAL_CPUID_WORD_NUM);
    brand[BRAND_SIZE - 1] = '\0';
    ci->cpu_brand = brand;

    cpuid(2, 1, words, 0);
    ci->cpu_family   = BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX],  8, 12) + 1;
    ci->cpu_model    = BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX],  4,  8);
    ci->cpu_stepping = BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EAX],  0,  4);

    /* we cannot use CPUID(0xb) because it counts even disabled-by-BIOS cores (e.g. HT cores);
     * instead we extract info on number of online CPUs by parsing sysfs pseudo-files */
    int cores = get_cpu_count();
    if (cores < 0) {
        free(brand);
        free(vendor_id);
        return cores;
    }
    ci->cpu_num = cores;

    int flen = 0, fmax = 80;
    char * flags = malloc(fmax);

    for (int i = 0 ; i < 32 ; i++) {
        if (!cpu_flags[i])
            break;

        if (BIT_EXTRACT_LE(words[PAL_CPUID_WORD_EDX], i, i + 1)) {
            int len = strlen(cpu_flags[i]);
            if (flen + len + 1 > fmax) {
                char * new_flags = malloc(fmax * 2);
                memcpy(new_flags, flags, flen);
                free(flags);
                fmax *= 2;
                flags = new_flags;
            }
            memcpy(flags + flen, cpu_flags[i], len);
            flen += len;
            flags[flen++] = ' ';
        }
    }

    flags[flen ? flen - 1 : 0] = 0;
    ci->cpu_flags = flags;
}
