/*------------------------------------------------------------------------------------------*\
 *   
 *   Copyright (C) 2004 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/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/config.h>
#include <linux/slab.h>
#include <asm/fcntl.h>
#include <asm/ioctl.h>
#include <linux/devfs_fs_kernel.h>
#include <asm/semaphore.h>
#include <asm/errno.h>
#include <linux/wait.h>
#include <asm/smplock.h>
#include <linux/tffs.h>
#include <linux/zlib.h>
#if defined(CONFIG_PROC_FS)
#include <linux/proc_fs.h>
#endif /*--- #if defined(CONFIG_PROC_FS) ---*/


/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
/*--- #define TFFS_DEBUG ---*/
#include "tffs_local.h"

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int tffs_mtd[2] = { CONFIG_TFFS_MTD_DEVICE_0 - 1, CONFIG_TFFS_MTD_DEVICE_1 - 1 };
MODULE_PARM_DESC (tffs_mtd, "MTD device used for tiny flash file system with double buffering");
MODULE_DESCRIPTION("TFFS 'tiny flash file system with double buffering' driver version 2.2");
MODULE_SUPPORTED_DEVICE("MTD[0..n]");
MODULE_LICENSE("GPL");
#define MODULE_VERSION "2.0"

#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *tffs_proc;
#endif /*--- #ifdef CONFIG_PROC_FS ---*/

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static void tffs_cleanup(void);

struct file_operations tffs_fops = {
    owner:   THIS_MODULE,
    open:    tffs_open,
    release: tffs_release,
    read:    tffs_read,
    write:   tffs_write,
    ioctl:   tffs_ioctl,
};


struct _tffs tffs;
struct semaphore tffs_sema;
static struct semaphore tffs_event_sema;
static struct semaphore tffs_event_lock_sema;
static int tffs_thread_pid;
int tffs_thread_event;
static DECLARE_COMPLETION(tffs_evt_dead);

enum _tffs_thread_state tffs_thread_state;
unsigned int tffs_request_count = 0;

/*-----------------------------------------------------------------------------------------------*\ 
\*-----------------------------------------------------------------------------------------------*/
int tffs_lock(void) {
    if(down_interruptible(&tffs_sema)) {
        return -1;
    }
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\ 
\*-----------------------------------------------------------------------------------------------*/
void tffs_unlock(void) {
    up(&tffs_sema);
}

/*-----------------------------------------------------------------------------------------------*\ 
\*-----------------------------------------------------------------------------------------------*/
static int tffs_thread(void *reply_data) {
    unsigned int event;
    void *handle;
    tffs_thread_state = tffs_thread_state_init;
	lock_kernel();
	daemonize();
	unlock_kernel();
	strcpy(current->comm, "tffsd_mtd_");
    current->comm[9] = '0' + tffs_mtd[0];

    handle = TFFS_Open();
    tffs_thread_state = tffs_thread_state_idle;
    for( ;; ) {
        DBG((KERN_INFO "tffsd: wait for events\n"));
        if(down_interruptible(&tffs_event_sema)) {
            break;
        }
        tffs_thread_state = tffs_thread_state_process;

        /*--- lesen des Events ---*/
        down(&tffs_event_lock_sema);
        event = tffs_thread_event;
        tffs_thread_event = 0;
        up(&tffs_event_lock_sema);

        tffs_request_count++;
        /*--- auswerten des Events ---*/
        DBG((KERN_INFO "tffsd: process event 0x%x\n", event));

        if(event & TFFS_EVENT_CLEANUP) {
            DBG((KERN_DEBUG "%s: tffsd: cleanup\n", MODULE_NAME));
            if(down_interruptible(&tffs_sema)) {
                break;
            }
            TFFS_Cleanup(handle);
            up(&tffs_sema);
        }
    }

    TFFS_Close(handle);
    DBG((KERN_INFO "tffsd: event thread dead\n"));
    tffs_thread_state = tffs_thread_state_down;
    complete_and_exit(&tffs_evt_dead, 0);
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
void tffs_send_event(unsigned int event) {
    DBG((KERN_INFO "tffs_send_event(%x)\n", event));
    down(&tffs_event_lock_sema);
    tffs_thread_event |= event;
    up(&tffs_event_lock_sema);
    up(&tffs_event_sema);
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
int __init tffs_init(void) {
    static char tffs_mtd_string[6] = "mtd_";


    DBG((KERN_INFO "%s: register_chrdev(%s) param=mtd%u\n", MODULE_NAME, MODULE_NAME, tffs_mtd[0]));
    DBG((KERN_INFO "%s: CONFIG_TFFS_MTD_DEVICE_0=%u CONFIG_TFFS_MTD_DEVICE_1=%u\n", MODULE_NAME, CONFIG_TFFS_MTD_DEVICE_0, CONFIG_TFFS_MTD_DEVICE_1));
    tffs.major = register_chrdev(0 /*--- dynamic major ---*/, MODULE_NAME, &tffs_fops);
    if(tffs.major < 1) {
        DBG((KERN_ERR "%s: register_chrdrv failed: reason %d\n", MODULE_NAME, tffs.major));
        return -ERESTARTSYS;
    }

	tffs.devfs_dir_handle = devfs_mk_dir(NULL, "tffs", NULL);
    tffs_mtd_string[3]  = tffs_mtd[0] + '0';
    tffs.minor   = 0;
	tffs.devfs_handle = devfs_register(tffs.devfs_dir_handle, tffs_mtd_string, DEVFS_FL_DEFAULT, tffs.major, tffs.minor,
                                	   S_IFCHR | S_IRUGO | S_IWUSR, &tffs_fops, NULL);

    if(TFFS_Init(tffs_mtd[0], tffs_mtd[1])) {
        tffs_cleanup();
        return -ERESTARTSYS;
    }

    sema_init(&tffs_sema, 1);
    sema_init(&tffs_event_sema, 0);
    sema_init(&tffs_event_lock_sema, 1);

	tffs.devfs_ticfg_handle = devfs_register(NULL, "ticfg", DEVFS_FL_DEFAULT, tffs.major, FLASH_FS_ID_TICFG,
                                	   S_IFCHR | S_IRUGO | S_IWUSR, &tffs_fops, NULL);

	/*--- tffs.devfs_shutdown_handle = devfs_register(NULL, "shutdown_cmd", DEVFS_FL_DEFAULT, tffs.major, FLASH_FS_ID_SHUTDOWN, ---*/
                                	   /*--- S_IFCHR | S_IRUGO | S_IWUSR, &tffs_fops, NULL); ---*/

    DBG((KERN_INFO "%s: major %d (success)\n", MODULE_NAME, tffs.major));

#ifdef CONFIG_PROC_FS
	if ((tffs_proc = create_proc_entry( "tffs", 0, 0 )))
	  tffs_proc->read_proc = tffs_read_proc;
	  tffs_proc->write_proc = tffs_write_proc;
#endif

    tffs_thread_state = tffs_thread_state_off;
	tffs_thread_pid = kernel_thread(tffs_thread, &tffs_thread_event, CLONE_SIGHAND);

    printk("TFFS: tiny flash file system driver. GPL (c) AVM Berlin (Version %s)\n", MODULE_VERSION);
    printk("      mount on mtd%u and mtd%u (double buffering)\n", tffs_mtd[0], tffs_mtd[1]);

#ifdef CONFIG_SYSCTL
    avm_urlader_env_init();
#endif /* CONFIG_SYSCTL */
    return 0;
}


module_init(tffs_init);

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static void tffs_cleanup(void) {
#if defined(MODLE)
    DBG((KERN_INFO "%s: unregister_chrdev(%u)\n", MODULE_NAME, tffs.major));
    if(down_interruptible(&tffs_sema)) {
        DBG((KERN_ERR "%s: tffs_cleanup: down_interruptible() failed\n", MODULE_NAME));
        return;
    }
#ifdef CONFIG_SYSCTL
    avm_urlader_env_exit();
#endif /* CONFIG_SYSCTL */

    if(tffs_thread_pid) {
        int stat;
        DBG((KERN_INFO "%s: Terminating thread ", MODULE_NAME));
		stat = kill_proc(tffs_thread_pid, SIGTERM, 1);
		if(!stat) {
			DBG(("waiting..."));
			wait_for_completion(&tffs_evt_dead);
		}
		DBG(("done.\n"));
    }

    TFFS_Deinit();
    if(tffs_proc)
         remove_proc_entry( "tffs", 0);
    if(tffs.devfs_handle)
	    devfs_unregister(tffs.devfs_handle);
    if(tffs.devfs_ticfg_handle)
	    devfs_unregister(tffs.devfs_ticfg_handle);
    /*--- if(tffs.devfs_shutdown_handle) ---*/
	    /*--- devfs_unregister(tffs.devfs_shutdown_handle); ---*/
	if(tffs.devfs_dir_handle)
        unregister_chrdev(tffs.major, MODULE_NAME);
    up(&tffs_sema);
#endif /*--- #if defined(MODLE) ---*/
    return;
}

module_exit(tffs_cleanup);



EXPORT_NO_SYMBOLS;

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
