/*  This file is part of Lyos.

    Lyos 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 3 of the License, or
    (at your option) any later version.

    Lyos 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 Lyos.  If not, see <http://www.gnu.org/licenses/>. */

#include <lyos/types.h>
#include <lyos/ipc.h>
#include "lyos/const.h"
#include "string.h"
#include <kernel/proc.h>
#include <kernel/global.h>
#include <kernel/proto.h>
#include <asm/proto.h>
#ifdef CONFIG_SMP
#include <asm/smp.h>
#endif
#include <lyos/clocksource.h>
#include <lyos/time.h>
#include <kernel/irq.h>
#include <kernel/clockevent.h>
#include <asm/div64.h>

clock_t idle_ticks = 0;

static DEFINE_CPULOCAL(u64, context_switch_clock);

struct tick_device {
    struct clock_event_device* evdev;
};

static DEFINE_CPULOCAL(struct tick_device, tick_cpu_device);
static DEFINE_CPULOCAL(u64, ns_per_tick);

DEF_LIST(timer_list);
static spinlock_t timers_lock;
static clock_t next_timeout = TIMER_UNSET;

void sched_clock(struct proc* p);

extern void timer_add(struct list_head* list, struct timer_list* timer);
extern void timer_expire(struct list_head* list, clock_t timestamp);

/*****************************************************************************
 *                                read_jiffies
 *****************************************************************************/
/**
 * <Ring 0> Reads jiffies.
 *
 * @param clocksource The jiffies clocksource.
 * @return The jiffies.
 *****************************************************************************/
static u64 read_jiffies(struct clocksource* cs) { return jiffies; }

#define NSEC_PER_JIFFY (NSEC_PER_SEC / DEFAULT_HZ)
#define JIFFY_SHIFT    8
static struct clocksource jiffies_clocksource = {
    .name = "jiffies",
    .rating = 1,
    .read = read_jiffies,
    .mask = 0xffffffff,
    .mul = NSEC_PER_JIFFY << JIFFY_SHIFT,
    .shift = JIFFY_SHIFT,
};

/*****************************************************************************
 *                                clock_handler
 *****************************************************************************/
/**
 * <Ring 0> This routine handles the clock interrupt generated by 8253/8254
 *          programmable interval timer.
 *
 * @param irq The IRQ nr, unused here.
 *****************************************************************************/
static void clock_handler(struct clock_event_device* evt)
{
#if CONFIG_SMP
    if (cpuid == bsp_cpu_id) {
#endif
        kclockinfo.realtime++;
        kclockinfo.uptime++;

        if (++jiffies >= MAX_TICKS) jiffies = 0;
#if CONFIG_SMP
    }
#endif

    if (get_cpulocal_var(proc_ptr) == get_cpulocal_var_ptr(idle_proc))
        idle_ticks++;
    get_cpulocal_var(proc_ptr)->user_time++;

#if CONFIG_SMP
    if (cpuid == bsp_cpu_id) {
#endif
        /* timer expired */
        if (jiffies >= next_timeout && next_timeout != TIMER_UNSET) {
            spinlock_lock(&timers_lock);
            timer_expire(&timer_list, jiffies);
            if (list_empty(&timer_list))
                next_timeout = TIMER_UNSET;
            else
                next_timeout =
                    list_entry(timer_list.next, struct timer_list, list)
                        ->expire_time;
            spinlock_unlock(&timers_lock);
        }
#if CONFIG_SMP
    }
#endif

    sched_clock(get_cpulocal_var(proc_ptr));
}

/*****************************************************************************
 *                                init_time
 *****************************************************************************/
/**
 * <Ring 0> This routine initializes time subsystem.
 *
 *****************************************************************************/
int init_time()
{
    jiffies = 0;

    arch_init_time();
    clocksource_register(&jiffies_clocksource);
    spinlock_init(&timers_lock);

    memset(&kclockinfo, 0, sizeof(kclockinfo));
    kclockinfo.hz = system_hz;

    return 0;
}

int init_local_timer(int freq)
{
    get_cpulocal_var(ns_per_tick) = NSEC_PER_SEC;
    do_div(get_cpulocal_var(ns_per_tick), (u64)freq);

    return 0;
}

/*****************************************************************************
 *                                init_bsp_timer
 *****************************************************************************/
/**
 * <Ring 0> This routine initializes timer for BSP.
 *
 * @param freq Timer frequency.
 *****************************************************************************/
int init_bsp_timer(int freq)
{
    if (init_local_timer(freq)) return -1;
    return 0;
}

/*****************************************************************************
 *                                init_ap_timer
 *****************************************************************************/
/**
 * <Ring 0> This routine initializes timer for APs.
 *
 * @param freq Timer frequency.
 *****************************************************************************/
int init_ap_timer(int freq)
{
    if (init_local_timer(freq)) return -1;
    return 0;
}

void setup_local_timer_one_shot(void)
{
    struct tick_device* td = get_cpulocal_var_ptr(tick_cpu_device);

    if (!td->evdev) return;
    clockevents_switch_state(td->evdev, CLOCK_EVT_STATE_ONESHOT);
}

void setup_local_timer_periodic(void)
{
    struct tick_device* td = get_cpulocal_var_ptr(tick_cpu_device);

    if (!td->evdev) return;
    clockevents_switch_state(td->evdev, CLOCK_EVT_STATE_PERIODIC);
}

void restart_local_timer(void)
{
    struct tick_device* td = get_cpulocal_var_ptr(tick_cpu_device);

    if (!td->evdev) return;
    clockevents_program_delta(td->evdev, get_cpulocal_var(ns_per_tick));
}

void stop_local_timer(void)
{
    struct tick_device* td = get_cpulocal_var_ptr(tick_cpu_device);

    if (!td->evdev) return;
    clockevents_shutdown(td->evdev);
}

/*****************************************************************************
 *                                stop_context
 *****************************************************************************/
/**
 * <Ring 0> This routine performs context switching.
 *
 * @param p Stop context for whom.
 *****************************************************************************/
void stop_context(struct proc* p)
{
    u64* ctx_switch_clock = get_cpulocal_var_ptr(context_switch_clock);

    if (!curr_clocksource) return;
    u64 cycle = curr_clocksource->read(curr_clocksource);
    u64 delta = cycle - *ctx_switch_clock;
    p->cycles += delta;

    u64 nsec = clocksource_cyc2ns(curr_clocksource, delta);

    if (p->endpoint >= 0) {
        if (nsec < p->counter_ns)
            p->counter_ns = p->counter_ns - nsec;
        else {
            p->counter_ns = 0;
        }
    }

    arch_stop_context(p, delta);

    *ctx_switch_clock = cycle;
}

void set_sys_timer(struct timer_list* timer)
{
    spinlock_lock(&timers_lock);

    if (timer->expire_time != TIMER_UNSET) list_del(&timer->list);

    timer_add(&timer_list, timer);
    next_timeout =
        list_entry(timer_list.next, struct timer_list, list)->expire_time;

    spinlock_unlock(&timers_lock);
}

void reset_sys_timer(struct timer_list* timer)
{
    spinlock_lock(&timers_lock);

    cancel_timer(timer);
    if (list_empty(&timer_list))
        next_timeout = TIMER_UNSET;
    else
        next_timeout =
            list_entry(timer_list.next, struct timer_list, list)->expire_time;

    spinlock_unlock(&timers_lock);
}

void set_boottime(time_t time) { kclockinfo.boottime = time; }

static void tick_setup_device(struct tick_device* td,
                              struct clock_event_device* newdev, int cpu)
{
    void (*handler)(struct clock_event_device*) = NULL;

    if (td->evdev) {
        handler = td->evdev->event_handler;
        td->evdev->event_handler = clockevents_handle_noop;
    }

    if (handler == NULL) handler = clock_handler;

    td->evdev = newdev;
    td->evdev->event_handler = handler;

    clockevents_switch_state(td->evdev, CLOCK_EVT_STATE_ONESHOT);
}

static int check_tick_device_replacement(struct clock_event_device* curdev,
                                         struct clock_event_device* newdev)
{
    if (!cpumask_test_cpu(newdev->cpumask, cpuid)) return FALSE;

    if (!cpumask_equal(newdev->cpumask, cpumask_of(cpuid))) {
        if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpuid)))
            return FALSE;
    }

    return !curdev || newdev->rating > curdev->rating ||
           !cpumask_equal(curdev->cpumask, newdev->cpumask);
}

void tick_check_new_device(struct clock_event_device* newdev)
{
    struct tick_device* td = get_cpulocal_var_ptr(tick_cpu_device);
    struct clock_event_device* curdev = td->evdev;

    if (!check_tick_device_replacement(curdev, newdev)) return;

    clockevents_exchange_device(curdev, newdev);
    tick_setup_device(td, newdev, cpuid);
}
