/*------------------------------------------------------------------------------------------*\
 *   
 *   Copyright (C) 2006 AVM GmbH <fritzbox_info@avm.de>
 *
 *   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 
\*------------------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/fcntl.h>
#include <asm/ioctl.h>
#include <asm/errno.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/ar7wdt.h>
#include <linux/timer.h>

/*--- #include <linux/proc_fs.h> ---*/
/*--- #include <linux/sched.h> ---*/
#include <linux/unistd.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/fs.h>

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
/*--- #define AR7WDT_DEBUG ---*/

#if defined(AR7WDT_DEBUG)
#define DBG(...)  printk(KERN_INFO __VA_ARGS__)
#else /*--- #if defined(AR7WDT_DEBUG) ---*/
#define DBG(...)  
#endif /*--- #else ---*/ /*--- #if defined(AR7WDT_DEBUG) ---*/

#include "avm_sammel.h"
#include "ar7wdt.h"

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _ar7wdt_data ar7wdt_data;
struct timer_list ar7wdt_timer;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void AR7WDT_timer_handler(unsigned long);
static struct semaphore ar7wdt_sema;
extern int ar7wdt_no_reboot;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void AR7WDT_timer_handler_action(unsigned int handle);
volatile unsigned int AR7WDT_locked = 0;
volatile unsigned int AR7WDT_locked_action = 0;

#define AR7WDT_LOCK()       if(down_interruptible(&ar7wdt_sema)) { \
                                return -ERESTARTSYS; \
                            } else { \
                                AR7WDT_locked = 1; \
                            }
#define AR7WDT_LOCK_VOID()  if(down_interruptible(&ar7wdt_sema)) {  \
                                return;  \
                            } else { \
                                AR7WDT_locked = 1; \
                            }
#define AR7WDT_UNLOCK()     { \
                                unsigned int i; \
                                for(i = 0 ; AR7WDT_locked_action ; i++) { \
                                    if(i == 32) i = 0; \
                                    if(AR7WDT_locked_action & (1 << i)) \
                                        AR7WDT_timer_handler_action(i); \
                                } \
                                AR7WDT_locked = 0; \
                                up(&ar7wdt_sema); \
                            }

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int _AR7WDT_atoi(char *p) {
    int value = 0;

    while(p && *p && ((*p < '0') || (*p > '9')))
        p++;

    while(p && *p) {
        if(*p >= '0' && *p <= '9') {
            value *= 10;
            value += *p - '0';
        } else {
            break;
        }
        p++;
    }
    return value;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#define AR7WDT_DEL_TIMER    0x01
#define AR7WDT_SET_TIMER    0x02
static void _AR7WDT_ctrl_timer(int flags, int i) {
    struct timer_list *timer;
    int default_time;

    DBG(KERN_INFO "_AR7WDT_ctrl_timer(flags=0x%x, handle=%d)\n", flags, i + 1);

    if(i == -1) {
        timer = &ar7wdt_timer;
        default_time = 20;
    } else {
        timer        = &(ar7wdt_data.appl[i].Timer);
        default_time = ar7wdt_data.appl[i].default_time;
    }
    if(flags & AR7WDT_DEL_TIMER) {
        del_timer(timer);
    }
    if(flags & AR7WDT_SET_TIMER) {
        timer->expires = jiffies + HZ * default_time;
        add_timer(timer);
    }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void AR7WDT_init(void) {
    DBG(KERN_INFO "AR7WDT_init:\n");
    memset(&ar7wdt_data, 0x00, sizeof(ar7wdt_data));
    sema_init(&ar7wdt_sema, 1);
	init_timer(&ar7wdt_timer);
    ar7wdt_timer.function = AR7WDT_timer_handler;
    ar7wdt_timer.data     = -1;
    _AR7WDT_ctrl_timer(AR7WDT_SET_TIMER, -1);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void AR7WDT_deinit(void) {
    DBG(KERN_INFO "AR7WDT_deinit:\n");
    _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER, -1);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_find_handle(char *name, int len) {
    int i;
    DBG(KERN_ERR "AR7WDT_find_handle('%s', len=%u):\n", name, len);
    for( i = 0 ; i < MAX_WDT_APPLS ; i++) {
        if(ar7wdt_data.mask & (1 << i)) {
            if(!strcmp(ar7wdt_data.appl[i].Name, name)) {
                DBG(" handle=%u\n", i + 1);
                return i + 1;
            }
        }
    }
    DBG(" handle= not found\n");
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_strip_name(char **name, int len) {
    DBG(KERN_ERR "AR7WDT_strip_name('%s', len=%u): ", *name, len);
    while((*name)[len - 1] == '\n' || (*name)[len - 1] == '\r')
        (*name)[len - 1] = '\0', len = strlen(*name);
    len = min(len, MAX_WDT_NAME_LEN - 1);
    (*name)[len] = '\0';
    len = strlen(*name);
    DBG("-->('%s', len=%u)", *name, len);
    while(len && (**name == ' ' || **name == '\t' || **name == '\n' || **name == '\r'))
        (*name)++, len--;
    DBG("-->('%s', len=%u):\n", *name, len);
    return len;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void AR7WDT_timer_init_ctrl_handler(unsigned long _handle) {
    if(ar7wdt_no_reboot == 0) {
        panic("ar7wdt_hw_reboot: init sequence hangs !\n");
    }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_init_start(int handle, char *name, int len) {
    /*--- ignore handle ---*/
    unsigned int Sekunden = _AR7WDT_atoi(name);
    if(Sekunden == 0) Sekunden = 120; /*--- 2 Minuten ---*/

    init_waitqueue_head(&(ar7wdt_data.appl[MAX_WDT_APPLS].wait_queue));
    ar7wdt_data.appl[MAX_WDT_APPLS].fasync         = NULL;
    ar7wdt_data.appl[MAX_WDT_APPLS].default_time   = Sekunden;
    ar7wdt_data.appl[MAX_WDT_APPLS].Timer.function = AR7WDT_timer_init_ctrl_handler;
    ar7wdt_data.appl[MAX_WDT_APPLS].Timer.data     = MAX_WDT_APPLS + 1; /*--- handle ---*/

    init_timer(&(ar7wdt_data.appl[MAX_WDT_APPLS].Timer));

    strcpy(ar7wdt_data.appl[MAX_WDT_APPLS].Name, "init-ctrl");

    printk("AR7WDT: System Init UEberwachung %u Sekunden\n", Sekunden);
    _AR7WDT_ctrl_timer(AR7WDT_SET_TIMER, MAX_WDT_APPLS);
    return MAX_WDT_APPLS + 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_init_done(int handle, char *name, int len) {
    printk("AR7WDT: System Init UEberwachung abgeschlossen (%lu ms noch verfuegbar)\n", 10 * (ar7wdt_data.appl[MAX_WDT_APPLS].Timer.expires - jiffies));
    _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER, MAX_WDT_APPLS);
    return handle;
}

/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
void AR7WDT_ungraceful_release(int handle)
{
    DBG(KERN_INFO "AR7WDT_ungraceful_release(%u)\n", handle);
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_ungraceful_release(hdl=%u): invalid handle\n", handle);
        return;
    }
    handle--;
    if (ar7wdt_data.mask & (1 << handle)) {
        printk(KERN_EMERG "AR7WDT_ungraceful_release: handle %u (%s) still registered!\n", handle + 1, ar7wdt_data.appl[handle].Name);
    }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_register(int handle, char *name, int len) {
    int i;
    DBG(KERN_INFO "AR7WDT_register(%s): start\n", name);

    len = AR7WDT_strip_name(&name, len);
    AR7WDT_LOCK();
    for( i = 1 ; i < MAX_WDT_APPLS ; i++) {
        if(ar7wdt_data.mask & (1 << i)) {
            if(strcmp(ar7wdt_data.appl[i].Name, name))
                continue;
            DBG(KERN_INFO "AR7WDT_register(%s): already registered (accept) handle=%u\n", name, i + 1);
            AR7WDT_UNLOCK();
            return i + 1;
        }
    }
    DBG(KERN_INFO "AR7WDT_register(%s): not yet registered\n", name);

    for( i = 1 ; i < MAX_WDT_APPLS ; i++) {
        if(ar7wdt_data.mask & (1 << i)) {
            continue;
        }
        ar7wdt_data.mask |= (1 << i);
        ar7wdt_data.triggered &= ~(1 << i);
        ar7wdt_data.requested |= (1 << i);
        init_waitqueue_head(&(ar7wdt_data.appl[i].wait_queue));
        ar7wdt_data.appl[i].fasync = NULL;
        ar7wdt_data.appl[i].default_time = WDT_DEFAULT_TIME;
        ar7wdt_data.appl[i].Timer.function = AR7WDT_timer_handler;
        ar7wdt_data.appl[i].Timer.data     = i + 1; /*--- handle ---*/

	    init_timer(&(ar7wdt_data.appl[i].Timer));

        strncpy(ar7wdt_data.appl[i].Name, name, len);
        ar7wdt_data.appl[i].Name[len] = '\0';

        _AR7WDT_ctrl_timer(AR7WDT_SET_TIMER, i);

        DBG(KERN_INFO "AR7WDT_register(%s): handle=%u mask=0x%08x\n", name, i + 1, ar7wdt_data.mask);

        AR7WDT_UNLOCK();
        return i + 1;
    }
    DBG(KERN_ERR "AR7WDT_register(%s): not registered, too many appls\n", name);
    AR7WDT_UNLOCK();
    return -EUSERS; /*--- to many users ---*/
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct fasync_struct **AR7WDT_get_fasync_ptr(int handle) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_get_fasync_ptr(hdl=%u): invalid handle\n", handle);
        return NULL; /*--- io error ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_get_fasync_ptr(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return NULL; /*--- io error ---*/
    }
    return &(ar7wdt_data.appl[handle].fasync);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
wait_queue_head_t *AR7WDT_get_wait_queue(int handle) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_get_wait_queue(hdl=%u): invalid handle\n", handle);
        return NULL; /*--- io error ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_get_wait_queue(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return NULL; /*--- io error ---*/
    }
    return &(ar7wdt_data.appl[handle].wait_queue);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_poll(int handle) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_poll(hdl=%u): invalid handle\n", handle);
        return -EINVAL; /*--- inval argument ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_poll(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return -EINVAL; /*--- inval argument ---*/
    }
    return (ar7wdt_data.requested & (1 << handle)) ? 1 : 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_release(int handle, char *name, int len) {

    DBG(KERN_INFO "AR7WDT_release(hdl=%u): name='%s', len=%u\n", handle, name, len);
    len = AR7WDT_strip_name(&name, len);

    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_release(hdl=%u): invalid handle\n", handle);
        return -EINVAL; /*--- inval argument ---*/
    }
    handle--;
    if(len) {
        if(strcmp(name, ar7wdt_data.appl[handle].Name)) {
            DBG(KERN_INFO "AR7WDT_release(hdl=%u): name='%s', len=%u (name and handle to not correspond)\n", handle, name, len);
            handle = AR7WDT_find_handle(name, len);
            if(handle == 0) {
                DBG(KERN_ERR "AR7WDT_release(hdl=%u): invalid name\n", handle + 1);
                return -EINVAL; /*--- inval argument ---*/
            }
            handle--;
        }
    }
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_release(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return -EINVAL; /*--- inval argument ---*/
    }
    /*--- timer stopen ---*/

    AR7WDT_LOCK();
    _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER, handle);
    ar7wdt_data.mask      &= ~(1 << handle);
    ar7wdt_data.requested &= ~(1 << handle);
    ar7wdt_data.states    &= ~(1 << handle);
    ar7wdt_data.triggered &= ~(1 << handle);
    AR7WDT_UNLOCK();
    DBG(KERN_INFO "AR7WDT_release(hdl=%u): success\n", handle + 1);
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_set_timeout(int handle, char *time, int len) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_set_timeout(hdl=%u): invalid handle\n", handle);
        return -EINVAL; /*--- inval argument ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_set_timeout(hdl=%u): invalid handle\n", handle + 1);
        return -EINVAL; /*--- inval argument ---*/
    }
    AR7WDT_LOCK();
    ar7wdt_data.appl[handle].default_time = _AR7WDT_atoi(time) / 2;

    _AR7WDT_ctrl_timer(AR7WDT_SET_TIMER | AR7WDT_DEL_TIMER, handle);
    AR7WDT_UNLOCK();
    DBG(KERN_INFO "AR7WDT_set_timeout(hdl=%u): success (new timeout=%u)\n", handle + 1, ar7wdt_data.appl[handle].default_time);
    return handle + 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_trigger(int handle, char *time, int len) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_trigger(hdl=%u): invalid handle\n", handle);
        return -EINVAL; /*--- inval argument ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_trigger(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return -EINVAL; /*--- inval argument ---*/
    }
    AR7WDT_LOCK();
    ar7wdt_data.triggered |=  (1 << handle);
    ar7wdt_data.states    &= ~(1 << handle);
    /*--- ar7wdt_data.appl[i].default_time ---*/ 
    /*--- timer neu aufsetzen ---*/
    _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER | AR7WDT_SET_TIMER, handle);
    AR7WDT_UNLOCK();
    AR7WDT_check_all_triggered();
    DBG(KERN_ERR "AR7WDT_trigger(hdl=%u): success\n", handle + 1);
    return handle + 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_disable(int handle, char *time, int len) {
    int i;
    printk(KERN_INFO "AR7WDT_disable()\n");
    printk(KERN_INFO "registered appls:\n");
    AR7WDT_LOCK();
    for (i = 0; i < MAX_WDT_APPLS; i++)
    {
        if((ar7wdt_data.mask & (1 << i))) {
            _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER, i);
            ar7wdt_data.mask      &= ~(1 << i);
            ar7wdt_data.requested &= ~(1 << i);
            ar7wdt_data.states    &= ~(1 << i);
            ar7wdt_data.triggered &= ~(1 << i);
            printk(KERN_INFO "  hdl=%u, name=%s, disabled.\n", i + 1, ar7wdt_data.appl[i].Name);
        }
    }
    AR7WDT_UNLOCK();
    _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER, -1);
    ar7wdt_hw_deinit();
    ar7wdt_no_reboot = 3;
    return 1; /*--- sonst Endlosschleife, weil private_data == 0 ---*/
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_read(int handle, char *Buffer, int max_len) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_read(hdl=%u): invalid handle\n", handle);
        return -EINVAL; /*--- inval argument ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_read(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return -EINVAL; /*--- inval argument ---*/
    }
    if((ar7wdt_data.requested & (1 << handle)) == 0) {
        Buffer[0] = '\0';
        DBG(KERN_INFO "AR7WDT_read(hdl=%u): no request\n", handle + 1);
        return handle + 1;
    }
    AR7WDT_LOCK();
    ar7wdt_data.requested &= ~(1 << handle);
    max_len = min(max_len - 1, (int)sizeof("alive ?"));
    strncpy(Buffer, "alive ?", max_len);
    Buffer[max_len] = '\0';
    AR7WDT_UNLOCK();
    DBG(KERN_INFO "AR7WDT_read(hdl=%u): request='%s'\n", handle + 1, Buffer);
    return handle + 1;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int AR7WDT_reboot(int handle) {
    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_reboot(hdl=%u): invalid handle\n", handle);
        return -EINVAL; /*--- inval argument ---*/
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_reboot(hdl=%u): invalid handle (not set in mask)\n", handle + 1);
        return -EINVAL; /*--- inval argument ---*/
    }
    if(ar7wdt_data.states & (1 << handle)) {
        int i;
        __printk(KERN_EMERG "AR7WDT_reboot(hdl=%u, name=%s): reboot\n", handle + 1, ar7wdt_data.appl[handle].Name);
        __printk(KERN_EMERG "registered appls:\n");
        for (i = 0; i < MAX_WDT_APPLS; i++)
        {
            if((ar7wdt_data.mask & (1 << i))) {
                __printk(KERN_EMERG "  hdl=%u, name=%s\n", i + 1, ar7wdt_data.appl[i].Name);
            }
        }
#ifdef CONFIG_SCHEDSTATS
        if (ar7wdt_no_reboot == 0) {
            restore_printk();
        }
        show_sched_stats();
#endif
        show_state();
#ifdef CONFIG_MIPS_OHIO && CONFIG_SCHEDSTATS
        show_process_eip(ar7wdt_data.appl[handle].Name);
#endif
        if(ar7wdt_no_reboot == 0) {
            panic("ar7wdt_hw_reboot: watchdog expired\n");
        }
    }
    printk(KERN_ERR "AR7WDT_reboot(hdl=%u): timer not triggered\n", handle + 1);
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void AR7WDT_check_all_triggered(void) {
    int i;
    unsigned int mask    = 0;
    unsigned int trigger = 0;
    AR7WDT_LOCK_VOID();
    for( i = 0 ; i < MAX_WDT_APPLS ; i++) {
        if(ar7wdt_data.mask & (1 << i)) {
            mask |= 1 << i;
            if(ar7wdt_data.triggered & (1 << i)) {
                trigger |= 1 << i;
            }
        }
    }
    if(mask == trigger) {
        ar7wdt_data.triggered = 0;
        ar7wdt_hw_trigger();
    } else {
        DBG(KERN_INFO "AR7WDT_check_all_triggered(): not triggered mask 0x%x\n", mask ^ trigger);
    }
    AR7WDT_UNLOCK();
}


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void AR7WDT_timer_handler_action(unsigned int handle) {
    ar7wdt_data.states    |= 1 << handle;
    ar7wdt_data.requested |= 1 << handle;

    if(ar7wdt_data.appl[handle].fasync) {
        kill_fasync(&(ar7wdt_data.appl[handle].fasync), SIGIO, POLL_IN);
    } else {
        wake_up_interruptible(&(ar7wdt_data.appl[handle].wait_queue));
    }

    _AR7WDT_ctrl_timer(AR7WDT_SET_TIMER, handle);
    AR7WDT_locked_action &= ~(1 << handle);
    /*--- AR7WDT_UNLOCK(); ---*/
    DBG(KERN_INFO "AR7WDT_timer_handler(hdl=%u): timer triggered once\n", handle + 1);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void AR7WDT_timer_handler(unsigned long _handle) {
    int handle = (int)_handle;

    DBG(KERN_INFO "AR7WDT_timer_handler(hdl=%d)\n", handle);

    if(handle == -1) {
        _AR7WDT_ctrl_timer(AR7WDT_SET_TIMER, handle);
        /*--- AR7WDT_check_all_triggered(); ---*/
        ar7wdt_hw_trigger();
        return;
    }

    if(handle < 1 || handle > MAX_WDT_APPLS) {
        DBG(KERN_ERR "AR7WDT_timer_handler(hdl=%u): invalid handle\n", handle);
        return;
    }
    handle--;
    if((ar7wdt_data.mask & (1 << handle)) == 0) {
        DBG(KERN_ERR "AR7WDT_timer_handler(hdl=%u): invalid handle\n", handle + 1);
        return;
    }
    if(ar7wdt_data.states & (1 << handle)) {
        DBG(KERN_INFO "AR7WDT_timer_handler(hdl=%u): timer triggered twice (reboot)\n", handle + 1);
        AR7WDT_reboot(handle + 1);
        return;
    }
    /*--- AR7WDT_LOCK_VOID(); ---*/
    _AR7WDT_ctrl_timer(AR7WDT_DEL_TIMER, handle);
    if(AR7WDT_locked) {
        AR7WDT_locked_action |= (1 << handle);
        return;
    }

    AR7WDT_timer_handler_action(handle);
    return;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
EXPORT_SYMBOL(AR7WDT_register);
EXPORT_SYMBOL(AR7WDT_release);
EXPORT_SYMBOL(AR7WDT_set_timeout);
EXPORT_SYMBOL(AR7WDT_trigger);
EXPORT_SYMBOL(AR7WDT_read);
EXPORT_SYMBOL(AR7WDT_reboot);
EXPORT_SYMBOL(AR7WDT_poll);

EXPORT_SYMBOL(ar7wdt_no_reboot);
