/* _NVRM_COPYRIGHT_BEGIN_
 *
 * Copyright 2001-2011 by NVIDIA Corporation.  All rights reserved.  All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 *
 * _NVRM_COPYRIGHT_END_
 */

#include "nv-misc.h"
#include "os-interface.h"
#include "nv.h"
#include "nv-freebsd.h"

MALLOC_DEFINE(M_NVIDIA, "nvidia", "NVIDIA memory allocations");
TASKQUEUE_DEFINE_THREAD(nvidia);

RM_STATUS NV_API_CALL os_alloc_mem(
    void **address,
    NvU64 size
)
{
    unsigned long alloc_size;

    /*
     * malloc takes an input of unsigned long (8 bytes in x64, 4 bytes in x86).
     * To avoid truncation and wrong allocation, below check is required.
     */
    alloc_size = size;

    if (alloc_size != size)
        return RM_ERR_INVALID_LIMIT;

    /* XXX Fix me? (malloc flags) */
    *address = malloc(alloc_size, M_NVIDIA, M_NOWAIT | M_ZERO);
    return *address ? RM_OK : RM_ERR_NO_FREE_MEM;
}

void NV_API_CALL os_free_mem(void *address)
{
    free(address, M_NVIDIA);
}

#define NV_MSECS_PER_TICK       (1000 / hz)
#define NV_MSECS_TO_TICKS(ms)   ((ms) * hz / 1000)
#define NV_USECS_PER_TICK       (1000000 / hz)
#define NV_USECS_TO_TICKS(us)   ((us) * hz / 1000000)

RM_STATUS NV_API_CALL os_delay(NvU32 MilliSeconds)
{
    unsigned long MicroSeconds;
    unsigned long ticks;
    struct timeval tv_end, tv_aux;

    getmicrotime(&tv_aux);

    if (__NV_ITHREAD() && (MilliSeconds > NV_MAX_ISR_DELAY_MS))
        return RM_ERROR;

    if (__NV_ITHREAD()) {
        DELAY(MilliSeconds * 1000);
        return RM_OK;
    }

    MicroSeconds = MilliSeconds * 1000;
    tv_end.tv_usec = MicroSeconds;
    tv_end.tv_sec = 0;
    /* tv_end = tv_aux + tv_end */
    NV_TIMERADD(&tv_aux, &tv_end, &tv_end);

    ticks = NV_USECS_TO_TICKS(MicroSeconds);

    if (ticks > 0) {
        do {
            tsleep((void *)os_delay, PUSER | PCATCH, "delay", ticks);
            getmicrotime(&tv_aux);
            if (NV_TIMERCMP(&tv_aux, &tv_end, <)) {
                /* tv_aux = tv_end - tv_aux */
                NV_TIMERSUB(&tv_end, &tv_aux, &tv_aux);
                MicroSeconds = tv_aux.tv_usec + (tv_aux.tv_sec * 1000000);
            } else
                MicroSeconds = 0;
        } while ((ticks = NV_USECS_TO_TICKS(MicroSeconds)) > 0);
    }

    if (MicroSeconds > 0)
        DELAY(MicroSeconds);

    return RM_OK;
}

RM_STATUS NV_API_CALL os_delay_us(NvU32 MicroSeconds)
{
    if (__NV_ITHREAD() && (MicroSeconds > NV_MAX_ISR_DELAY_US))
        return RM_ERROR;
    DELAY(MicroSeconds);
    return RM_OK;
}

NvU64 NV_API_CALL os_get_cpu_frequency(void)
{
    /* round up by 4999 before division by 1000000 in osGetCpuFrequency() */
    return (tsc_freq + 4999);
}

NvU32 NV_API_CALL os_get_current_process(void)
{
    return curproc->p_pid;
}

RM_STATUS NV_API_CALL os_get_current_thread(NvU64 *threadId)
{
    if (__NV_ITHREAD())
        *threadId = 0;
    else
        *threadId = (NvU64) CURTHREAD->td_tid;

    return RM_OK;
}

RM_STATUS NV_API_CALL os_get_current_time(
    NvU32 *sec,
    NvU32 *usec
)
{
    struct timeval tv;

    getmicrotime(&tv);

    *sec  = tv.tv_sec;
    *usec = tv.tv_usec;

    return RM_OK;
}

BOOL NV_API_CALL os_is_administrator(PHWINFO pDev)
{
    return suser(CURTHREAD) ? FALSE : TRUE;
}

NvU8 NV_API_CALL os_io_read_byte(
    NvU32 address
)
{
    /* XXX Fix me? (bus_space access) */
    return inb(address);
}

void NV_API_CALL os_io_write_byte(
    NvU32 address,
    NvU8  value
)
{
    /* XXX Fix me? (bus_space access) */
    outb(address, value);
}

NvU16 NV_API_CALL os_io_read_word(
    NvU32 address
)
{
    /* XXX Fix me? (bus_space access) */
    return inw(address);
}

void NV_API_CALL os_io_write_word(
    NvU32 address,
    NvU16 value
)
{
    /* XXX Fix me? (bus_space access) */
    return outw(address, value);
}

NvU32 NV_API_CALL os_io_read_dword(
    NvU32 address
)
{
    /* XXX Fix me? (bus_space access) */
    return inl(address);
}

void NV_API_CALL os_io_write_dword(
    NvU32 address,
    NvU32 value
)
{
    /* XXX Fix me? (bus_space access) */
    outl(address, value);
}

void* NV_API_CALL os_map_kernel_space(
    NvU64 start,
    NvU64 size,
    NvU32 mode
)
{
    int map_mode;

#if defined(NVCPU_X86) && !defined(PAE)
    if (start > 0xffffffffULL)
        return NULL;
#endif

    switch (mode) {
        case NV_MEMORY_CACHED:
            map_mode = PAT_WRITE_BACK;
            break;
        case NV_MEMORY_WRITECOMBINED:
            map_mode = PAT_WRITE_COMBINING;
            break;
        case NV_MEMORY_UNCACHED:
        case NV_MEMORY_DEFAULT:
            map_mode = PAT_UNCACHEABLE;
            break;
        default:
            nv_printf(NV_DBG_ERRORS,
                      "NVRM: unknown mode in os_map_kernel_space()\n");
            return NULL;
    }

    return pmap_mapdev_attr(start, size, map_mode);
}

void NV_API_CALL os_unmap_kernel_space(
    void *address,
    NvU64 size
)
{
    pmap_unmapdev((vm_offset_t)address, size);
}

void* NV_API_CALL os_map_user_space(
    NvU64   start,
    NvU64   size_bytes,
    NvU32   mode,
    NvU32   protect,
    void  **priv_data
)
{
    return (void *)(NvUPtr)start;
}

void NV_API_CALL os_unmap_user_space(
    void  *address,
    NvU64  size,
    void  *priv_data
)
{
}

RM_STATUS NV_API_CALL os_set_mem_range(
    NvU64 start,
    NvU64 size,
    NvU32 mode,
    NvS32 *memory_token
)
{
    int arg;
    struct mem_range_desc mrd;

    mrd.mr_base  = start;
    mrd.mr_len   = size;
    mrd.mr_flags = MDF_WRITECOMBINE;

    strcpy(mrd.mr_owner, "NVIDIA");
    arg = MEMRANGE_SET_UPDATE;

    if (mem_range_attr_set(&mrd, &arg))
        return RM_ERROR;

    return RM_OK;
}

RM_STATUS NV_API_CALL os_unset_mem_range(
    NvU64 start,
    NvU64 size,
    NvS32 memory_token
)
{
    int arg;
    struct mem_range_desc mrd;

    mrd.mr_base = start;
    mrd.mr_len  = size;
    arg = MEMRANGE_SET_REMOVE;

    if (mem_range_attr_set(&mrd, &arg))
        return RM_ERROR;

    return RM_OK;
}


/*
 * The current debug level is used to determine if certain debug messages
 * are printed to the system console/log files or not. It defaults to the
 * highest debug level, i.e. the lowest debug output.
 */

NvU32 cur_debuglevel = 0xffffffff;

void NV_API_CALL os_dbg_init(void)
{
    NvU32 new_debuglevel;
    nv_stack_t *sp;

    NV_UMA_ZONE_ALLOC_STACK(sp);
    if (sp == NULL)
        return;

    if (rm_read_registry_dword(sp, NULL, "NVreg", "ResmanDebugLevel",
            &new_debuglevel) == RM_OK) {
        if (new_debuglevel != 0xffffffff)
            cur_debuglevel = new_debuglevel;
    }

    NV_UMA_ZONE_FREE_STACK(sp);
}

RM_STATUS NV_API_CALL os_schedule(void)
{
    return RM_ERR_NOT_SUPPORTED;
}

static void os_execute_work_item(void *context, int pending)
{
    nvidia_work_t *work = (nvidia_work_t *)context;
    nv_stack_t *sp = NULL;

    NV_UMA_ZONE_ALLOC_STACK(sp);
    if (sp == NULL) {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate stack!\n");
        return;
    }

    rm_execute_work_item(sp, work->data);
    os_free_mem((void *)work);

    NV_UMA_ZONE_FREE_STACK(sp);
}

RM_STATUS NV_API_CALL os_queue_work_item(void *data)
{
    RM_STATUS status;
    nvidia_work_t *work;

    status = os_alloc_mem((void **)&work, sizeof(nvidia_work_t));
    if (status != RM_OK)
        return status;

    work->data = data;

    TASK_INIT(&work->task, 0, os_execute_work_item, (void *)work);
    taskqueue_enqueue(taskqueue_nvidia, &work->task);

    return RM_OK;
}

RM_STATUS NV_API_CALL os_flush_work_queue(void)
{
    taskqueue_run(taskqueue_nvidia);
    return RM_OK;
}

void NV_API_CALL os_dbg_set_level(NvU32 new_debuglevel)
{
    cur_debuglevel = new_debuglevel;
}

void NV_API_CALL os_dbg_breakpoint(void)
{
#ifdef DEBUG
    NV_KDB_ENTER("breakpoint", "DEBUG breakpoint");
#endif
}

void NV_API_CALL out_string(const char *message)
{
    printf("%s", message);
}

#define MAX_ERROR_STRING 512
static char nv_error_string[MAX_ERROR_STRING];

int NV_API_CALL nv_printf(
    NvU32 debuglevel,
    const char *format,
    ...
)
{
    char *message = nv_error_string;
    va_list arglist;
    int chars_written = 0;

    if (debuglevel >= ((cur_debuglevel >> 4) & 3)) {
        va_start(arglist, format);
        chars_written = vsnprintf(message, sizeof(nv_error_string), format, arglist);
        va_end(arglist);
        printf("%s", message);
    }

    return chars_written;
}

NvS32 NV_API_CALL os_snprintf(
    char *buf,
    NvU32 size,
    const char *fmt,
    ...
)
{
    va_list arglist;
    int chars_written;

    va_start(arglist, fmt);
    chars_written = vsnprintf(buf, size, fmt, arglist);
    va_end(arglist);

    return chars_written;
}

void NV_API_CALL os_log_error(
    NvU32 log_level,
    NvU32 error_number,
    const char *fmt,
    va_list ap
)
{
    int l;
    sprintf(nv_error_string, "NVRM: ");
    l = strlen(nv_error_string);
    vsnprintf(nv_error_string + l, MAX_ERROR_STRING - l, fmt, ap);
    printf("%s", nv_error_string);
}

NvS32 NV_API_CALL os_mem_cmp(
    const NvU8 *buf0,
    const NvU8 *buf1,
    NvU32 length
)
{
    return memcmp(buf0, buf1, length);
}

NvU8* NV_API_CALL os_mem_copy(
    NvU8 *dst,
    const NvU8 *src,
    NvU32 length
)
{
#if defined(NVCPU_X86_64)
    uint32_t i;
    for (i = 0; i < length; i++) dst[i] = src[i];
    return dst;
#else
    return memcpy(dst, src, length);
#endif
}

RM_STATUS NV_API_CALL os_memcpy_from_user(
    void *dst,
    const void *src,
    NvU32 length
)
{
    return copyin(src, dst, length)  ? RM_ERR_INVALID_POINTER : RM_OK;
}

RM_STATUS NV_API_CALL os_memcpy_to_user(
    void *dst,
    const void *src,
    NvU32 length
)
{
    return copyout(src, dst, length) ? RM_ERR_INVALID_POINTER : RM_OK;
}

void* NV_API_CALL os_mem_set(
    void *b,
    NvU8  c,
    NvU32 length
)
{
    return memset(b, c, length);
}

char* NV_API_CALL os_string_copy(
    char *dst,
    const char *src
)
{
    return strcpy(dst, src);
}

NvU32 NV_API_CALL os_string_length(const char* s)
{
    return strlen(s);
}

RM_STATUS NV_API_CALL os_strncpy_from_user(
    char *dst,
    const char *src,
    NvU32 n
)
{
    return copyinstr(src, dst, n, NULL) ? RM_ERR_INVALID_POINTER : RM_OK;
}

NvU32 NV_API_CALL os_get_page_size(void)
{
    return PAGE_SIZE;
}

NvU64 NV_API_CALL os_get_page_mask(void)
{
    return ~PAGE_MASK;
}

NvU64 NV_API_CALL os_get_system_memory_size(void)
{
    return ((NvU64)physmem * PAGE_SIZE) / RM_PAGE_SIZE;
}

NvU32 NV_API_CALL os_get_cpu_count(void)
{
    return mp_ncpus;
}

#ifdef NV_USE_WBINVD
static void wbinvd_action_func(void *arg)
{
    __asm__ __volatile__("wbinvd": : :"memory");
}

RM_STATUS NV_API_CALL os_flush_cpu_cache(void)
{
    smp_rendezvous(NULL, wbinvd_action_func, NULL, NULL);
    wbinvd_action_func(NULL);
    return RM_OK;
}
#else
RM_STATUS NV_API_CALL os_flush_cpu_cache(void)
{
    pmap_invalidate_cache();
    return RM_OK;
}
#endif

RM_STATUS NV_API_CALL os_flush_user_cache(
    NvU64 start,
    NvU64 end,
    NvU64 physStart,
    NvU64 physEnd, 
    NvU32 flags
)
{
    return RM_ERR_NOT_SUPPORTED;
}

static void sfence_action_func(void *arg)
{
    __asm__ __volatile__("sfence": : :"memory");
}

void NV_API_CALL os_flush_cpu_write_combine_buffer(void)
{
    smp_rendezvous(NULL, sfence_action_func, NULL, NULL);
    sfence_action_func(NULL);
}

RM_STATUS NV_API_CALL os_raise_smp_barrier(void)
{
    return RM_ERR_NOT_SUPPORTED;
}

RM_STATUS NV_API_CALL os_clear_smp_barrier(void)
{
    return RM_ERR_NOT_SUPPORTED;
}

void NV_API_CALL os_disable_preemption(void)
{
}

void NV_API_CALL os_enable_preemption(void)
{
}

struct os_mutex {
    nv_stack_t *sp;
    struct mtx mutex_mtx;
    struct cv mutex_cv;
    int refcnt;
};

RM_STATUS NV_API_CALL os_alloc_sema(void **semaphore)
{
    nv_stack_t *sp;
    struct os_mutex *mtx;
    RM_STATUS status;

    NV_UMA_ZONE_ALLOC_STACK(sp);
    if (sp == NULL)
        return RM_ERR_NO_FREE_MEM;

    status = os_alloc_mem((void **)&mtx, sizeof(struct os_mutex));
    if (status != RM_OK) {
        NV_UMA_ZONE_FREE_STACK(sp);
        return status;
    }

    mtx_init(&mtx->mutex_mtx, "os.mutex_mtx", NULL, MTX_DEF | MTX_RECURSE);
    cv_init(&mtx->mutex_cv, "os.mutex_cv");

    mtx->sp = sp;
    mtx->refcnt = 1;

    *semaphore = (void *)mtx;

    return RM_OK;
}

void NV_API_CALL os_free_sema(void *semaphore)
{
    struct os_mutex *mtx = semaphore;
    nv_stack_t *sp;

    sp = mtx->sp;
    NV_UMA_ZONE_FREE_STACK(sp);

    mtx_destroy(&mtx->mutex_mtx);
    cv_destroy(&mtx->mutex_cv);

    os_free_mem(semaphore);
}

RM_STATUS NV_API_CALL os_acquire_sema(void *semaphore)
{
    struct os_mutex *mtx = semaphore;

    mtx_lock(&mtx->mutex_mtx);
    if (mtx->refcnt > 0)
        rm_disable_interrupts(mtx->sp);
    mtx->refcnt--;
    if (mtx->refcnt < 0)
        cv_wait(&mtx->mutex_cv, &mtx->mutex_mtx);
    mtx_unlock(&mtx->mutex_mtx);

    return RM_OK;
}

RM_STATUS NV_API_CALL os_cond_acquire_sema(void *semaphore)
{
    struct os_mutex *mtx = semaphore;

    mtx_lock(&mtx->mutex_mtx);
    if (mtx->refcnt < 1) {
        mtx_unlock(&mtx->mutex_mtx);
        return RM_ERROR;
    } else {
        rm_disable_interrupts(mtx->sp);
        mtx->refcnt--;
        mtx_unlock(&mtx->mutex_mtx);
    }

    return RM_OK;
}

RM_STATUS NV_API_CALL os_release_sema(void *semaphore)
{
    struct os_mutex *mtx = semaphore;

    mtx_lock(&mtx->mutex_mtx);
    if (mtx->refcnt < 0)
        cv_signal(&mtx->mutex_cv);
    else
        rm_enable_interrupts(mtx->sp);
    mtx->refcnt++;
    mtx_unlock(&mtx->mutex_mtx);

    return RM_OK;
}

BOOL NV_API_CALL os_is_acquired_sema(void *semaphore)
{
    struct os_mutex *mtx = semaphore;
    return (mtx->refcnt < 1);
}


RM_STATUS NV_API_CALL os_alloc_mutex(void **mutex)
{
    RM_STATUS status;
    struct sx *sx;

    status = os_alloc_mem((void **)&sx, sizeof(struct sx));
    if (status != RM_OK)
        return status;

    sx_init(sx, "os.lock_sx");
    *mutex = (void *)sx;

    return RM_OK;
}

void NV_API_CALL os_free_mutex(void *mutex)
{
    struct sx *sx = mutex;

    if (sx != NULL) {
        sx_destroy(sx);
        os_free_mem(sx);
    }
}

RM_STATUS NV_API_CALL os_acquire_mutex(void *mutex)
{
    struct sx *sx = mutex;

    if (__NV_ITHREAD())
        return RM_ERR_INVALID_REQUEST;

    sx_xlock(sx);

    return RM_OK;
}

RM_STATUS NV_API_CALL os_cond_acquire_mutex(void *mutex)
{
    struct sx *sx = mutex;

    if (__NV_ITHREAD())
        return RM_ERR_INVALID_REQUEST;

    if (sx_try_xlock(sx) == 0)
        return RM_ERR_TIMEOUT_RETRY;

    return RM_OK;
}

void NV_API_CALL os_release_mutex(void *mutex)
{
    struct sx *sx = mutex;

    sx_xunlock(sx);
}

struct os_semaphore {
    struct mtx mutex_mtx;
    struct cv mutex_cv;
    NvS32  count;
    NvS32  limit;
};

void* NV_API_CALL os_alloc_semaphore(NvU32 initialValue, NvU32 limit)
{
    RM_STATUS status;
    struct os_semaphore *os_sema;

    status = os_alloc_mem((void **)&os_sema, sizeof(struct os_semaphore));
    if (status != RM_OK) {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate semaphore!\n");
        return NULL;
    }

    mtx_init(&os_sema->mutex_mtx, "os.sema_mtx", NULL, MTX_DEF);
    cv_init(&os_sema->mutex_cv, "os.sema_cv");

    os_sema->count = initialValue;
    os_sema->limit = limit;

    return (void *)os_sema;
}

void NV_API_CALL os_free_semaphore(void *semaphore)
{
    struct os_semaphore *os_sema = (struct os_semaphore *)semaphore;

    mtx_destroy(&os_sema->mutex_mtx);
    cv_destroy(&os_sema->mutex_cv);

    os_free_mem(os_sema);
}

RM_STATUS NV_API_CALL os_acquire_semaphore(void *semaphore)
{
    struct os_semaphore *os_sema = (struct os_semaphore *)semaphore;

    mtx_lock(&os_sema->mutex_mtx);
    os_sema->count--;
    if (os_sema->count < 0)
        cv_wait_unlock(&os_sema->mutex_cv, &os_sema->mutex_mtx);
    else
        mtx_unlock(&os_sema->mutex_mtx);

    return RM_OK;
}

RM_STATUS NV_API_CALL os_release_semaphore(void *semaphore)
{
    struct os_semaphore *os_sema = (struct os_semaphore *)semaphore;

    mtx_lock(&os_sema->mutex_mtx);
    if (os_sema->count < 0) {
        cv_signal(&os_sema->mutex_cv);
    }
    os_sema->count++;
    mtx_unlock(&os_sema->mutex_mtx);

    return RM_OK;
}

BOOL NV_API_CALL os_semaphore_may_sleep(void)
{
    return (!__NV_ITHREAD());
}

BOOL NV_API_CALL os_is_isr(void)
{
    return (__NV_ITHREAD());
}

BOOL NV_API_CALL os_pat_supported(void)
{
    return TRUE;
}

BOOL NV_API_CALL os_is_efi_enabled(void)
{
    return FALSE;
}

void NV_API_CALL os_register_compatible_ioctl(
    NvU32 cmd,
    NvU32 size
)
{
}

void NV_API_CALL os_unregister_compatible_ioctl(
    NvU32 cmd,
    NvU32 size
)
{
}

RM_STATUS NV_API_CALL os_disable_console_access(void)
{
    return RM_OK;
}

RM_STATUS NV_API_CALL os_enable_console_access(void)
{
    return RM_OK;
}

RM_STATUS NV_API_CALL os_alloc_spinlock(void **lock)
{
    RM_STATUS status;
    struct mtx *mtx;

    status = os_alloc_mem((void **)&mtx, sizeof(struct mtx));
    if (status != RM_OK)
        return status;

    mtx_init(mtx, "os.lock_mtx", NULL, MTX_DEF);

    *lock = (void *)mtx;

    return RM_OK;
}

void NV_API_CALL os_free_spinlock(void *lock)
{
    struct mtx *mtx = lock;

    if (mtx != NULL) {
        mtx_assert(mtx, MA_OWNED);
        mtx_destroy(mtx);
        os_free_mem(mtx);
    }
}

NvU64 NV_API_CALL os_acquire_spinlock(void *lock)
{
    struct mtx *mtx = lock;

    mtx_lock(mtx);

    return 0;
}

void NV_API_CALL os_release_spinlock(void *lock, NvU64 oldIrql)
{
    struct mtx *mtx = lock;

    mtx_unlock(mtx);
}

RM_STATUS NV_API_CALL os_get_address_space_info(
    NvU64 *userStartAddress,
    NvU64 *userEndAddress,
    NvU64 *kernelStartAddress,
    NvU64 *kernelEndAddress
)
{
    return RM_ERR_NOT_SUPPORTED;
}

RM_STATUS NV_API_CALL os_alloc_smp_barrier(void)
{
    return RM_OK;
}

RM_STATUS NV_API_CALL os_free_smp_barrier(void)
{
    return RM_OK;
}

RM_STATUS NV_API_CALL os_get_version_info(os_version_info * pOsVersionInfo)
{
    return RM_ERR_NOT_SUPPORTED;
}
