/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
#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/cdev.h>
#include <linux/fs.h>
#include <asm/semaphore.h>
#include <asm/errno.h>
#include <linux/wait.h>
#include <linux/vmalloc.h>
#include <linux/poll.h>

#define AVM_EVENT_INTERNAL
#include <linux/avm_event.h>
#include <asm/mach_avm.h>

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
/*--- MODULE_DESCRIPTION("AVM Central Event distribution"); ---*/
/*--- MODULE_LICENSE("\n(C) Copyright 2004, 2005 by AVM\n"); ---*/

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int avm_event_open(struct inode *, struct file *);
static int avm_event_close(struct inode *, struct file *);
static int avm_event_fasync(int, struct file *, int);
static ssize_t avm_event_write(struct file *, const char *, size_t , loff_t *);
static ssize_t avm_event_read(struct file *, char *, size_t , loff_t *);
void avm_event_cleanup(void);
static unsigned int avm_event_poll(struct file *filp, poll_table *wait);

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _avm_event avm_event;
struct semaphore avm_event_sema;
spinlock_t avm_event_lock = SPIN_LOCK_UNLOCKED;
unsigned long long avm_event_source_mask;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct file_operations avm_event_fops = {
    owner:   THIS_MODULE,
    open:    avm_event_open,
    release: avm_event_close,
    read:    avm_event_read,
    write:   avm_event_write,
    /*--- ioctl:   avm_event_ioctl, ---*/
    fasync:  avm_event_fasync,
    poll: avm_event_poll,
};

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
int avm_event_init(void) {
    int reason;

    DEB_INFO("[avm_event] register_chrdev_region()\n");
    avm_event.device = MKDEV(AVM_EVENT_MAJOR, 0);
    reason = register_chrdev_region(avm_event.device, 1, "avm_event");
    if(reason) {
        DEB_ERR("[avm_event] register_chrdev_region failed: reason %d!\n", reason);
        return -ERESTARTSYS;
    }

	avm_event.cdev = cdev_alloc();
	if (!avm_event.cdev) {
        unregister_chrdev_region(avm_event.device, 1);
        DEB_ERR("[avm_event] cdev_alloc failed!\n");
        return -ERESTARTSYS;
    }

	avm_event.cdev->owner = avm_event_fops.owner;
	avm_event.cdev->ops = &avm_event_fops;
	kobject_set_name(&(avm_event.cdev->kobj), "avm_event");
		
    spin_lock_init(&avm_event_lock);
    sema_init(&avm_event_sema, 1);

    avm_event_init2(100 /* max_items */, 100 /* max_datas */);

	if(cdev_add(avm_event.cdev, avm_event.device, 1)) {
        kobject_put(&avm_event.cdev->kobj);
        unregister_chrdev_region(avm_event.device, 1);
        DEB_ERR("[avm_event] cdev_add failed!\n");
        return -ERESTARTSYS;
    }

#   if defined(CONFIG_AVM_PUSH_BUTTON)
    avm_event_push_button_init();
#   endif /*--- #if defined(CONFIG_AVM_PUSH_BUTTON) ---*/

    DEB_INFO("[avm_event]: major %d (success)\n", MAJOR(avm_event.device));

    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
void avm_event_cleanup(void) {
    DEB_INFO("[avm_event]: unregister_chrdev(%u)\n", MAJOR(avm_event.device));
#   if defined(CONFIG_AVM_PUSH_BUTTON)
    avm_event_push_button_deinit();
#   endif /*--- #if defined(CONFIG_AVM_PUSH_BUTTON) ---*/
    cdev_del(avm_event.cdev); /* Delete char device */
    avm_event_deinit2();
    unregister_chrdev_region(avm_event.device, 1);
    return;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int avm_event_fasync(int fd, struct file *filp, int mode) {
    struct _avm_event_open_data *open_data = (struct _avm_event_open_data *)filp->private_data;
    DEB_INFO("[%s] avm_event_fasync:\n", "avm_event");
    return fasync_helper(fd, filp, mode, &(open_data->fasync));
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static unsigned int avm_event_poll(struct file *filp, poll_table *wait) {
    struct _avm_event_open_data *open_data = (struct _avm_event_open_data *)filp->private_data;

    DEB_INFO("[%s] avm_event_poll:\n", "avm_event");
	poll_wait (filp, &(open_data->wait_queue), wait);

    if(open_data->item)
	    return POLLIN | POLLRDNORM;

    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int avm_event_open(struct inode *inode, struct file *filp) {
    struct _avm_event_open_data *open_data;

    DEB_INFO("[%s]: avm_event_open:\n", "avm_event");
    /*-------------------------------------------------------------------------------------------*\
    \*-------------------------------------------------------------------------------------------*/
    if(filp->f_flags & O_APPEND) {
        DEB_ERR("[%s]: avm_event_open: open O_APPEND not supported\n", "avm_event");
        return -EFAULT;
    }

    /*-------------------------------------------------------------------------------------------*\
    \*-------------------------------------------------------------------------------------------*/
    if(down_interruptible(&avm_event_sema)) {
        DEB_ERR("[%s] down_interruptible() failed\n", "avm_event");
        return -ERESTARTSYS;
    }

    /*-------------------------------------------------------------------------------------------*\
    \*-------------------------------------------------------------------------------------------*/
    open_data = (struct _avm_event_open_data *)kmalloc(sizeof(struct _avm_event_open_data), GFP_KERNEL);
    if(!open_data) {
        DEB_ERR("%s: avm_event_open: open malloc failed\n", "avm_event");
        up(&avm_event_sema);
        return -EFAULT;
    }
    memset(open_data, 0, sizeof(*open_data));

    init_waitqueue_head (&(open_data->wait_queue));
    open_data->pf_owner = &(filp->f_owner);
    filp->private_data = (void *)open_data;

    up(&avm_event_sema);
    DEB_INFO("[%s]: avm_event_open: open success flags=0x%x\n", "avm_event", filp->f_flags);
    return 0;
}

/*-----------------------------------------------------------------------------------------------*\
\*-----------------------------------------------------------------------------------------------*/
static int avm_event_close(struct inode *inode, struct file *filp) {

    DEB_INFO("[%s]: avm_event_close:\n", "avm_event");

    if(down_interruptible(&avm_event_sema)) {
        DEB_ERR("%s down_interruptible() failed\n", "avm_event");
        return -ERESTARTSYS;
    }
    /*--- achtung auf ind wartende "gefreien" und warten bis alle fertig ---*/

    if(filp->private_data) {
        struct _avm_event_open_data *open_data = (struct _avm_event_open_data *)filp->private_data;
        struct _avm_event_cmd_param_release avm_event_cmd_param_release;
        if(open_data->registered) {
            DEB_INFO("[close_0]\n");
            if(open_data->event_source_handle)
                avm_event_source_release(open_data->event_source_handle);
            open_data->event_source_handle = NULL;

            DEB_INFO("[close_1]\n");
            strcpy(avm_event_cmd_param_release.Name, open_data->Name);
            DEB_INFO("[close_2]\n");
            (void)avm_event_release(open_data, &avm_event_cmd_param_release);
            DEB_INFO("[close_3]\n");
        }
        avm_event_fasync(-1, filp, 0);  /*--- remove this file from asynchonously notified filp ---*/
        DEB_INFO("[close_4]\n");
        kfree(filp->private_data);
        DEB_INFO("[close_5]\n");
        filp->private_data = NULL;
        DEB_INFO("[close_6]\n");
    }

    up(&avm_event_sema);
    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void avm_event_source_user_mode_notify(void *Context, enum _avm_event_id id) {
    struct _avm_event_open_data *O = (struct _avm_event_open_data *)Context;
    struct _avm_event_data *D;

    DEB_INFO("[avm_event_source_user_mode_notify]: avm_event_source_user_mode_notify:\n");

    if(O->event_mask_registered & ((unsigned long long)1 << avm_event_id_user_source_notify)) {
        struct _avm_event_user_mode_source_notify *N;
        N = (struct _avm_event_user_mode_source_notify *)kmalloc(sizeof(struct _avm_event_user_mode_source_notify), GFP_KERNEL);
        if(N == NULL) {
            DEB_ERR("[avm_event_source_user_mode_notify]: out of memory\n");
            return;
        }

        D = (struct _avm_event_data *)vmalloc(sizeof(struct _avm_event_data));
        if(D == NULL) {
            DEB_ERR("[avm_event_source_user_mode_notify]: out of memory\n");
            return;
        }
        N->header.id   = avm_event_id_user_source_notify;
        N->id          = id;
        D->link_count  = 0; 
        D->data        = N;
        D->data_length = sizeof(struct _avm_event_user_mode_source_notify);
        avm_event_source_trigger_one(O, D);
    }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static ssize_t avm_event_write(struct file *filp, const char *write_buffer, size_t write_length, loff_t *write_pos) {
    unsigned int status;
    unsigned char *data;
    unsigned int data_length;
    struct _avm_event_cmd Buffer;
    struct _avm_event_open_data *open_data = (struct _avm_event_open_data *)filp->private_data;

    DEB_INFO("[%s]: write_length = %u *write_pos = 0x%LX\n", "avm_event_write", write_length, *write_pos);

    if(write_length < sizeof(Buffer)) {
        DEB_ERR("%s: avm_event_write: write_lengh < %u\n", "avm_event_write", sizeof(Buffer));
        return -EINVAL;
    }
    if(copy_from_user(&Buffer, write_buffer, sizeof(Buffer))) {
        DEB_ERR("%s: avm_event_write: copy_from_user failed\n", "avm_event_write");
        return -EFAULT;
    }
    if((Buffer.cmd != avm_event_cmd_register) && (open_data->registered == 0)) {
        DEB_ERR("%s: avm_event_write: not registered\n", "avm_event_write");
        return -EFAULT;
    }

    if(down_interruptible(&avm_event_sema)) {
        DEB_ERR("%s down_interruptible() failed\n", "avm_event_write");
        return -ERESTARTSYS;
    }
    switch(Buffer.cmd) {
        case avm_event_cmd_register:
            DEB_INFO("[%s]: avm_event_cmd_register\n", "avm_event_write");
            status = avm_event_register(open_data, &Buffer.param.avm_event_cmd_param_register);
            if(status == 0)
                open_data->registered = 1;
            break;

        case avm_event_cmd_release:
            DEB_INFO("[%s]: avm_event_cmd_release\n", "avm_event_write");
            status = avm_event_release(open_data, &Buffer.param.avm_event_cmd_param_release);
            open_data->registered = 0;
            break;

        case avm_event_cmd_trigger:
            DEB_INFO("[%s]: avm_event_cmd_release\n", "avm_event_write");
            status = avm_event_trigger(open_data, &Buffer.param.avm_event_cmd_param_trigger);
            break;

        case avm_event_cmd_source_register:
            DEB_INFO("[%s]: avm_event_cmd_source_register\n", "avm_event_write");
            if(open_data->event_source_handle) {
                status = -EACCES;
                break;
            }
            open_data->event_source_handle = avm_event_source_register(Buffer.param.avm_event_cmd_param_source_register.Name, 
                                                            Buffer.param.avm_event_cmd_param_source_register.mask, 
                                                            avm_event_source_user_mode_notify, open_data);
            if(open_data->event_source_handle) {
                status = 0;
            } else {
                status = -EACCES;
            }
            break;

        case avm_event_cmd_source_release:
            DEB_INFO("[%s]: avm_event_cmd_source_release\n", "avm_event_write");
            status = 0;
            if(open_data->event_source_handle == NULL) {
                break;
            }
            avm_event_source_release(open_data->event_source_handle);
            open_data->event_source_handle = NULL;
            break;

        case avm_event_cmd_source_trigger:
            DEB_INFO("[%s]: avm_event_cmd_source_trigger\n", "avm_event_write");
            if(open_data->event_source_handle == NULL) {
                status = -EACCES;
                break;
            }
            data_length = Buffer.param.avm_event_cmd_param_source_trigger.data_length;
            data = kmalloc(data_length, GFP_KERNEL);
            if(data == NULL) {
                status = -ENOMEM;
                break;
            }
            if(copy_from_user(data, write_buffer + sizeof(struct _avm_event_cmd), data_length)) {
                DEB_ERR("%s: avm_event_write: copy_from_user failed\n", "avm_event_write");
                return -EFAULT;
            }
            avm_event_source_trigger(open_data->event_source_handle, 
                    Buffer.param.avm_event_cmd_param_source_trigger.id, data_length, data);
            status = 0;
            break;

        default:
        case avm_event_cmd_undef:
            DEB_ERR("[%s]: avm_event_cmd_undef\n", "avm_event_write");
            status = -EINVAL;
    }
    up(&avm_event_sema);
    if(status)
        return status;
    return write_length;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static ssize_t avm_event_read(struct file *filp, char *read_buffer, size_t max_read_length, loff_t *read_pos) {
    int status;
    unsigned int event_pos;
    unsigned int copy_length = 0;
    unsigned int rx_buffer_length = 0;
    unsigned char *rx_buffer;
    unsigned int commit = 0;
    struct _avm_event_open_data *open_data = (struct _avm_event_open_data *)filp->private_data;

    DEB_INFO("[%s]: avm_event_read:\n", "avm_event_read");

avm_event_read_retry:
    if(down_interruptible(&avm_event_sema)) {
        DEB_ERR("%s down_interruptible() failed\n", "avm_event_read");
        return -ERESTARTSYS;
    }
    status = avm_event_get(open_data, &rx_buffer, &rx_buffer_length, &event_pos);
    /*--------------------------------------------------------------------------------------*\
     * sind überhaupt Daten vorhanden
    \*--------------------------------------------------------------------------------------*/
    if(rx_buffer_length) {
        DEB_INFO("[%s] rx_buffer_length = %u *read_pos = %Lu\n", "avm_event_read", rx_buffer_length, *read_pos);
        copy_length = rx_buffer_length - *read_pos;
        if(copy_length <= max_read_length) {
            commit = 1;
        } else {
            copy_length = max_read_length;
        }
    /*--------------------------------------------------------------------------------------*\
     * sind wir bloekierend, nein
    \*--------------------------------------------------------------------------------------*/
    } else if(filp->f_flags & O_NONBLOCK) {
        up(&avm_event_sema);
        DEB_INFO("[%s] non block, empty\n", "avm_event_read");
        return -EAGAIN;
    /*--------------------------------------------------------------------------------------*\
     * sind wir bloekierend, ja
    \*--------------------------------------------------------------------------------------*/
    } else {
        up(&avm_event_sema);
        DEB_INFO("[%s] sleep on\n", "avm_event_read");
        if(wait_event_interruptible(open_data->wait_queue, open_data->item)) {
            DEB_INFO("[%s] handle released\n", "avm_event_read");
            return -ERESTARTSYS;
        }
        DEB_INFO("[%s] wake up\n", "avm_event_read");
        goto avm_event_read_retry;
    }

    if(copy_to_user(read_buffer, rx_buffer + *read_pos, copy_length)) {
        up(&avm_event_sema);
        DEB_ERR("%s: tffs_read: copy_to_user failed\n", "avm_event_read");
        return -EFAULT;
    }
    *read_pos += (loff_t)copy_length;
    if(commit) {
        *read_pos = (loff_t)0;
        avm_event_commit(open_data, event_pos);
    }
    up(&avm_event_sema);
    return copy_length;
}


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

/*--- EXPORT_NO_SYMBOLS; ---*/

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