/***************************************************************************/
/*                             Druckersoftware                             */
/*                    Copyright (C) Carl Valentin GmbH                     */
/***************************************************************************/

// falls dies (vor allen includes) definiert ist UND das aktuelle log level (siehe /proc/sys/kernel/printk) > 7 ist, sind auch pr_debug und dev_dbg ausgaben sichtbar!
//#define DEBUG

/*
 * KM_CvKernelAccess.c
 *
 *  Created on: Jul 28, 2016
 *      Author: preitinger
 */

#include "KM_testConfigs.h" // erster Include!
#include "fpgaSerial/throttling.h"
#include "cv_char_dev_simple.h"

/*. IMPORT ================================================================*/
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/kobject.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include <linux/platform_data/edma.h>
#include "KM_CvKernelAccess.h"
#include "KM_DmaChan_ControlModulAccess.h"
#include "KM_DmaChan_ControlModulAccess.c"
#include "KM_DmaChan_MappedIo.c"
#include "KM_FpgaAddresses.h"
#include "Edma_neu.h"
#include "fpgaSerial/KM_CvFpgaSerial.h"
#include "dmaFramework/dmaFramework.h"
#include <linux/dmaengine.h>
#include <linux/edma.h>

// TODO erst mal testen
#include "Timer.h"
#include "Timer.c"
#include "JobApi.h"
#include "jobapi/KM_JobApi.h"
//#include "temp_test.h"
#include "test/test_neg_sema.h"

/*. ENDIMPORT =============================================================*/

/*. LOCAL =================================================================*/

/*-------------------------------- Makros ---------------------------------*/

#define MODULE_NAME "KM_CvKernelAccess"
#define MAIN_CLASS_NAME     "KM_CvKernelAccess_Main"
#define DMA_CHAN_CLASS_NAME "KM_Chan"
#define MAIN_DEV_NAME "cv_kernelaccess"
#define DMA_CHAN_DEV_NAME "cv_dma_channels"
//#define DMA_BUF_SIZE			(PAGE_SIZE * 3) // TODO richtige Groesse ermitteln, aber page aligned (*4096)
#define DMA_BUF_SIZE                    (PAGE_SIZE * 1024) // TODO richtige Groesse ermitteln, aber page aligned (*4096)

/*--------------------------- Typdeklarationen ----------------------------*/

//typedef enum EDmaChanState {
//    DCS_UNCONNECTED,
//    DCS_CONNECTED,
//    DCS_SHUTDOWN,
//    DCS_DELETED
//} DmaChanState;
//
//typedef struct SDmaChan
//{
////    MyDevice myDevice;
//    int ch; // dma channel 0-63
////    int crossbarEvt; // 0 bedeutet Standard-Event
////    bool evtEnabled;
//    struct completion comp;
//    DmaChanState state;
//} DmaChan;

/*------------------------------ Prototypen -------------------------------*/

//static int
//DmaChan_create(DmaChan* p, int chanNum);
//
//static void
//DmaChan_delete(DmaChan* p);
//
//static void
//dmaCompletionCb(unsigned channel, u16 ch_status, void* data);


static int
generic_open(struct inode * n, struct file * f);

static int
generic_release(struct inode * n, struct file * f);

static ssize_t
Main_write(struct file *file, const char __user *buf, size_t count,
           loff_t *ppos);

static ssize_t
Main_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);

static int
Main_mmap(struct file* f, struct vm_area_struct* vm);


//static int
//DmaChan_open(struct inode * n, struct file * f);
//
//static int
//DmaChan_release(struct inode * n, struct file * f);
//
//static ssize_t
//DmaChan_write(struct file *file, const char __user *buf, size_t count,
//              loff_t *ppos);
//
//static ssize_t
//DmaChan_read(struct file* f, char __user* buf, size_t count, loff_t *ppos);


//static int
//DmaParamSet_open(struct inode * n, struct file * f);
//
//static int
//DmaParamSet_release(struct inode * n, struct file * f);
//
//static ssize_t
//DmaParamSet_write(struct file *file, const char __user *buf, size_t count,
//              loff_t *ppos);
//
//static ssize_t
//DmaParamSet_read(struct file* f, char __user* buf, size_t count, loff_t *ppos);


/*------------------------ Konstantendeklarationen ------------------------*/

/*------------------------- modulglobale Variable -------------------------*/

static MyDevice mainDev;
//static MyDevice dmaChanDev;
//static DmaChan* dmaChannels[64];
//static DmaChan testChan, testChan2;
static struct class* mainClass = NULL;
//static struct class* dmaChanClass = NULL;

static struct file_operations mainOps =
{

.open = generic_open,

.release = generic_release,

.write = Main_write,

.read = Main_read,

.mmap = Main_mmap,

.owner = THIS_MODULE

};

//static struct file_operations dmaChanOps =
//{
//
//.open = DmaChan_open,
//
////.release = generic_release,
//.release = DmaChan_release,
//
//.write = DmaChan_write,
//
//.read = DmaChan_read,
//
//};

static DEFINE_KFIFO(mainAnswerFifo, uint32_t, 4)
;

static void* mappedCpu = NULL;
static dma_addr_t mappedDma = 0;
static uint32_t mappedSize = 0;

//// TODO nur mal schnell timer testen:
//static Timer t;

/*. ENDLOCAL ==============================================================*/

bool myDmaFilter(struct dma_chan* chan, void* pArg) {
    int* num = (int*) pArg;
    pr_info("myDmaFilter fuer id=%d, num=%d\n", chan->chan_id, *num);

    if (chan->chan_id == *num) {
        pr_info("myDmaFilter: gefunden: %d\n", *num);
        return true;
    }

    return false;
}

static int myPlatformDeviceProbe(struct platform_device* pdev) {
    int res;

    pr_err("kein fehler: myPlatformDeviceProbe aufgerufen\n");

    {
        pr_info("vor dma_alloc_coherent mit dev argument\n");
        mappedCpu = dma_alloc_coherent(&pdev->dev, DMA_BUF_SIZE, &mappedDma, GFP_DMA);

        if (unlikely(!mappedCpu))
        {
            pr_err(
                    "nach versuchsweise...: DMA-Speicherbereich (%d Byte) konnte nicht allokiert werden\n",
                    (int) DMA_BUF_SIZE);
        } else {
            pr_info("dma_alloc_coherent mit dev argument anscheinend erfolgreich\n");
            ASSERT(mappedCpu);
            ASSERT(mappedDma);
            pr_info("mappedCpu: 0x%08x, mappedDma: 0x%08x\n", (int) mappedCpu, mappedDma);
            mappedSize = DMA_BUF_SIZE;
        }

    }

    pr_info("vor DmaAddressesEnv_init\n");
    DmaAddressesEnv_init(mappedCpu, mappedDma, mappedSize);
    res = dmaFramework_init(&pdev->dev);

    if (unlikely(res != 0)) {
        dma_free_coherent(&pdev->dev, DMA_BUF_SIZE, mappedCpu, mappedDma);
        mappedCpu = NULL;
        mappedDma = 0;

        return res;
    }
    // TODO ... ?

    return 0;
}

static int myPlatformDeviceRemove(struct platform_device* pdev) {

    dmaFramework_release();

    DmaAddressesEnv_exit();


    dma_free_coherent(&pdev->dev, mappedSize, mappedCpu, mappedDma);
    pr_info("dma coherent block wieder freigegeben.\n");
    mappedCpu = NULL;
    mappedDma = 0;
    // TODO ?
    return 0;
}

static const struct of_device_id of_myPlatformDeviceMatch[] = {
        // Der Wert in .compatible bezieht sich auf den Eintrag
        // "compatible" im Device-Tree-Knoten nor@3,0, welcher
        // sich auf unser FPGA bezieht.
        { .compatible = "cv_kernel_access" },
        {}
};

static struct platform_driver myPlatformDriver = {
        .probe = myPlatformDeviceProbe,
        .remove = myPlatformDeviceRemove,
        .driver = {
                .name = "cv_kernel_access",
                .of_match_table = of_myPlatformDeviceMatch,
                .owner = THIS_MODULE
        }
};

//static void myPlatformDriverInit(void) {
//    int res;
//
//    res = platform_driver_register(&myPlatformDriver);
//    pr_info("res of platform_driver_register: %d\n", res);
//}

//static void myPlatformDriverExit(void) {
//	pr_info("myPlatformDriverExit\n");
//    platform_driver_unregister(&myPlatformDriver);
//
//    pr_err("myPlatformDriverExit muss noch dma_free aufrufen!\n");
//}

static int
myinit1(void)
{
    int res = 0;
    pr_info("Lade KM_CvKernelAccess...\n");

#ifdef TEST_UART_OFF_7
    WARN(true, "TEST_UART_OFF_7 definiert, wodurch Uart-DMAs teilweise deaktiviert sind!\n");
#endif

//    // TODO remove me after testing
//    if ((res = temp_test_init())) {
//        goto undo_none;
//    }

#ifdef TEST_NEG_SEMA
    if ((res = test_neg_sema_init())) {
        goto undo_temp_test_init;
    }
#endif

    pr_info("vor DmaChan_MappedIo_create fuer edmaMappedIo\n");
    res = DmaChan_MappedIo_create(&edmaMappedIo1, 0x49000000, (0x6000 + (PAGE_SIZE - 1)) / PAGE_SIZE * PAGE_SIZE); // passt zwar, aber so sind wir max. kompatibel, falls mal irgendwann 0x6000 nicht mehr durch PAGE_SIZE teilbar sein sollte - gott bewahre ;-)
    pr_info("nach DmaChan_MappedIo_create fuer edmaMappedIo\n");


    if (res) {
        goto undo_test_neg_sema;
    }


    res = platform_driver_register(&myPlatformDriver);
    pr_info("res of platform_driver_register: %d\n", res);

    if (res) {
        goto undo_mappedio;
    }

    // DMA-Block wird jetzt im probe des Platform Drivers angelegt mit richtigem device argument.
//
//    mappedCpu = dma_alloc_coherent(NULL, DMA_BUF_SIZE, &mappedDma, GFP_DMA);
//
//    if (unlikely(!mappedCpu))
//    {
//        pr_err(
//                "DMA-Speicherbereich (%d Byte) konnte nicht allokiert werden\n",
//                (int) DMA_BUF_SIZE);
//        res = -EIO;
//        goto undo_mappedio;
//    }
//
//    ASSERT(mappedCpu);
//    ASSERT(mappedDma);
//    mappedSize = DMA_BUF_SIZE;

    mainClass = class_create(THIS_MODULE, MAIN_CLASS_NAME);

    if (IS_ERR(mainClass))
    {
        res = -EIO;
        goto undo_platform_driver_register;
    }

//    dmaChanClass = class_create(THIS_MODULE, DMA_CHAN_CLASS_NAME);
//    pr_info("dmaChanClass=%p\n", dmaChanClass);
//
//    if (IS_ERR(dmaChanClass))
//    {
//        res = -EIO;
//        goto undo_class_create1;
//    }

    if (MyDevice_create(&mainDev, mainClass, &mainOps, "%s", MAIN_DEV_NAME))
    {
        res = -EIO;
        goto undo_class_create2;
    }

    pr_debug("MyDevice_create done\n");

//    if (MyDevice_create(&dmaChanDev, dmaChanClass, &dmaChanOps, "%s", DMA_CHAN_DEV_NAME)) {
//        res = -EIO;
//        goto undo_create_mainDev;
//    }

//	// TODO begin test
//	if (DmaChan_create(&testChan, EDMA_CHANNEL_ANY)) {
//		pr_err("DmaChan_create(&testChan...) failed!\n");
//		res = -EIO;
//		goto undo_create_mainDev;
//	}
//
//	if (DmaChan_create(&testChan2, EDMA_CHANNEL_ANY)) {
//		pr_err("DmaChan_create(&testChan2..) failed\n");
//		res = -EIO;
//		goto undo_create_testChan;
//	}
//	// TODO end test

//    memset(dmaChannels, 0, sizeof(dmaChannels));

    res = Edma_neu_init();
    if (res != 0) {
        goto undo_create_mainDev;
    }

//    pr_info("vor return 0\n");
////    testTimerLoop();
//    {
//        int res;
//        res = Timer_create(&t);
//        pr_info("Timer_create returned %d\n", res);
//        Timer_lowLoadStart(&t, 0xffffc17);
//        // Timer_delete-aufruf in myexit1
////        Timer_delete(&t);
//        pr_info("nach Timer_delete\n");
//    }
    pr_info("KM_CvKernelAccess geladen.\n");

    res = KM_CvFpgaSerial_init(mappedCpu, mappedDma, mappedSize);
    if (res != 0) {
        goto undo_Edma_neu_init;
    }

    res = jobapi_init(mappedCpu, mappedDma, mappedSize);
    if (res != 0) {
        goto undo_serial_init;
    }

    // TODO begin remove this test
#ifdef DMA_TTY_WRITE_TEST
    pr_info("in ifdef DMA_TTY_WRITE_TEST\n");
    dma_tty_write_runTest(mappedCpu, mappedDma);
#endif
    // TODO end remove this test

#ifdef TEST_DMA_PAYLOAD_KM
    dmaPayloadKM_runTest();
#endif

    return 0;


//    undo_create_testChanDev:
//
//	DmaChan_delete(&testChan);

//    undo_create_dmaChanDev:
//
//    MyDevice_delete(&dmaChanDev);

    undo_serial_init:
    KM_CvFpgaSerial_exit();

    undo_Edma_neu_init:
    Edma_neu_exit();

    undo_create_mainDev:

    MyDevice_delete(&mainDev);

    undo_class_create2:

//    class_destroy(dmaChanClass);
//    dmaChanClass = NULL;
//
//    undo_class_create:
    class_destroy(mainClass);
    mainClass = NULL;

//    undo_dma_alloc:
//
//    dma_free_coherent(NULL, mappedSize, mappedCpu, mappedDma);
//    mappedCpu = NULL;
//    mappedDma = 0;

    undo_platform_driver_register:
    platform_driver_unregister(&myPlatformDriver);


    undo_mappedio:
    DmaChan_MappedIo_delete(&edmaMappedIo1);

    undo_test_neg_sema:
#ifdef TEST_NEG_SEMA
    test_neg_sema_exit();

    undo_temp_test_init:
#endif
//    temp_test_exit();

//    undo_none:

    return res;
}

static void
myexit1(void)
{
//    Timer_delete(&t);
//    pr_info("nach Timer_delete in myexit1\n");

    // zunaechst evtl. vergessene dma kanaele wieder freigeben
//    int ch;

    jobapi_exit();
    KM_CvFpgaSerial_exit();
    Edma_neu_exit();

//    for (ch = 0; ch < 64; ++ch)
//    {
//        if (dmaChannels[ch])
//        {
//            pr_warn("Achtung, DMA Kanal vergessen: %d\nWird jetzt freigegeben...\n", ch);
//            DmaChan_delete(dmaChannels[ch]);
//            kfree(dmaChannels[ch]);
//            dmaChannels[ch] = NULL;
//        }
//    }
//
//    MyDevice_delete(&dmaChanDev);
//    pr_info("dmaChanDev deleted.\n");

//	// TODO begin test
//	DmaChan_delete(&testChan2);
//	DmaChan_delete(&testChan);
//	// TODO end test

    MyDevice_delete(&mainDev);

//	undo_class_create2:
//    class_destroy(dmaChanClass);
//    dmaChanClass = NULL;

//	undo_class_create1:
    class_destroy(mainClass);
    mainClass = NULL;

//	undo_dma_alloc:
//
//    dma_free_coherent(NULL, mappedSize, mappedCpu, mappedDma);
//    mappedCpu = NULL;
//    mappedDma = 0;

    platform_driver_unregister(&myPlatformDriver);

    DmaChan_MappedIo_delete(&edmaMappedIo1);

#ifdef TEST_NEG_SEMA
    test_neg_sema_exit();
#endif

//    temp_test_exit(); // TODO remove me after testing

    pr_info("Modul %s entladen.\n", MODULE_NAME);
}

//static void testDmaengine(struct device* d) {
//    struct dma_chan* chan;
//    pr_info("d->of_node=%p\n", d->of_node);
//    chan = dma_request_chan(d, "bla");
//    if (IS_ERR(chan)) {
//        pr_info("dma_request_chan: error\n");
//    }
//}

static int
MyDevice_create(MyDevice* d, struct class* class,
                const struct file_operations* fops, const char* devNameFmt,
                ...)
{

    int res = -2;
    struct device *dev;

    pr_debug("class->name: '%s'\n", class->name);
    if (alloc_chrdev_region(&d->devNum, 0, 1, class->name))
    {
        pr_err("alloc_chrdev_region failed\n");
        return -1;
    }

    // TODO nicht cdev_alloc verwenden, sondern cdev_init direkt auf d->dev aufrufen!

//	d->dev = cdev_alloc(); // NEIN!
    cdev_init(&d->dev, fops);
    pr_err("vor d->dev.owner = THIS_MODULE: THIS_MODULE=%p, d->dev.owner=%p\n", THIS_MODULE, d->dev.owner);
    d->dev.owner = THIS_MODULE;

    if (cdev_add(&d->dev, d->devNum, 1))
    {
        pr_err("cdev_add failed\n");
        res = -1;
        goto undo_cdev_init;
    }

    d->class = class;

    {
        va_list vargs;
        va_start(vargs, devNameFmt);
        pr_debug("vor device_create_vargs\n");
        dev = device_create_vargs(class, NULL, d->devNum, NULL, devNameFmt,
                vargs);
        va_end(vargs);
    }

    if (IS_ERR(dev))
    {
        pr_err("%s", "device_create_vargs failed\n");
        res = -1;
        goto undo_cdev_add;
    }

//    dev_dbg(dev, "dev_dbg nach device_create_vargs\n");
//    dev_info(dev, "dev_info nach device_create_vargs\n");
//    dev_notice(dev, "dev_notice nach device_create_vargs\n");
//    dev_warn(dev, "dev_warn nach device_create_vargs\n");
//    dev_err(dev, "dev_err nach device_create_vargs\n");

    // accessCount auf -1 gesetzt, somit gibt naechstes atomic_inc_and_test(&d->accessCount)
    // true zurueck, da accessCount dann 0 wird mit ueberlauf
    atomic_set(&d->accessCount, -1);
//    pr_debug("%s successful, devNum=%d\n", __FUNCTION__, d->devNum);
//    pr_debug("major: %d\n", MAJOR((d->devNum)));

    return 0;

    undo_cdev_add: cdev_del(&d->dev);
    goto undo_alloc_chrdev_region;

    undo_cdev_init: kobject_put(&d->dev.kobj);

    undo_alloc_chrdev_region: unregister_chrdev_region(d->devNum, 1);

    return res;
}

static void
MyDevice_delete(MyDevice* d)
{
    device_destroy(d->class, d->devNum);

//	undo_cdev_add:
    cdev_del(&d->dev);

//	undo_alloc_chrdev_region:
    unregister_chrdev_region(d->devNum, 1);
}

//static int
//DmaChan_create(DmaChan* p, int chanNum)
//{
//    int res;
//
//    if (!(chanNum == EDMA_CHANNEL_ANY || (chanNum >= 0 && chanNum < 64)))
//    {
//        pr_err("%s: illegal chanNum=%d\n", __FUNCTION__, chanNum);
//        return -1;
//    }
//
//    pr_info("chanNum was %d\n", chanNum);
//    // alt:
////    p->ch = chanNum = edma_alloc_channel(chanNum, dmaCompletionCb, p,
////            EVENTQ_DEFAULT);
//    // experimentell EVENTQ_0
//    p->ch = chanNum = edma_alloc_channel(chanNum, dmaCompletionCb, p,
//            EVENTQ_0);
//    pr_info("Voruebergehend EVENTQ_0 verwendet...\n");
//
//    if (p->ch < 0)
//    {
//        pr_err("edma_alloc_channel failed\n");
//        return -1;
//    }
//
//    res = 0; // nun immer
//
////    pr_info("vor create\n");
////    res = MyDevice_create(&p->myDevice, dmaChanClass, &dmaChanOps,
////            "cv_dma_%d", chanNum);
////    pr_info("nach create: res=%d, p->ch=%d\n", res, p->ch);
//
////    if (res == 0)
////    {
////        ASSERT(container_of(&p->myDevice, DmaChan, myDevice) == p); // so kann ausgehend von einem MyDevice* die umgebende DmaChan Instanz erreicht  werden
//
//        init_completion(&p->comp);
//        pr_info("completion for ch %d initialized, p=%p\n", chanNum, p);
////    }
////    else
////    {
////        // TODO notwendig?
////        edma_stop(chanNum);
////
////        // rueckgaengig
////        edma_free_channel(chanNum);
////    }
//
//    p->state = DCS_UNCONNECTED;
//    return res;
//}
//
//static void
//DmaChan_delete(DmaChan* p)
//{
//    edma_stop(p->ch);
////    complete(&p->comp);
////    complete(&p->comp);
////    complete(&p->comp);
////    msleep(1);
//    edma_free_channel(p->ch);
////    MyDevice_delete(&p->myDevice);
//}
//
//static void
//dmaCompletionCb(unsigned channel, u16 ch_status, void* data)
//{
////	char dummy = 1;
//    DmaChan* p = (DmaChan*)data;
//    pr_info(
//            "\n***\n*** dmaCompletionCb: channel=%u, status=%u, chanNum in DmaChan=%d, p=%p\n***\n\n",
//            channel, ch_status, p->ch, p);
//
//    if (p) {
//        complete(&p->comp);
//    }
//
////    // folgendes geht nicht da anscheinend hier doch im interrupt context!
////    spin_lock(&dmaChannelsLock);
////
////    if (likely(dmaChannels[channel] != 0)) {
////
////        if (likely(dmaChannels[channel] == p)) {
////            complete(&p->comp);
////        } else {
////            pr_err("FEHLER: dmaChannels[channel] == %p != %p == p in dmaCompletionCb\n", dmaChannels[channel], p);
////        }
////
////    }
////
////    spin_unlock(&dmaChannelsLock);
////    pr_info("leave dmaCompletionCb for channel %u\n", channel);
//}

static int
generic_open(struct inode * n, struct file * f)
{
    MyDevice* p = NULL;

    JobApi_up();

    pr_debug("================> generic_open file %s\n",
            f->f_path.dentry->d_iname);;

    p = container_of(n->i_cdev, MyDevice, dev);
    ASSERT(&p->dev == n->i_cdev);

    if (!try_module_get(THIS_MODULE))
    {
        return -1;
    }

    if (!atomic_inc_and_test(&p->accessCount))
    {
        // schon offen
        atomic_dec(&p->accessCount);
        pr_err("Kann nur einmal geoeffnet werden (accessCount war %d)!\n",
                atomic_read(&p->accessCount));
        module_put(THIS_MODULE);
        return -EBUSY;
    }

    return 0;
}

static int
generic_release(struct inode * n, struct file * f)
{
    MyDevice* p = NULL;

    pr_debug("=====================> generic_release file %s\n",
            f->f_path.dentry->d_iname);;

    p = container_of(n->i_cdev, MyDevice, dev);
    ASSERT(-1 == atomic_dec_return(&p->accessCount));
    module_put(THIS_MODULE);
    pr_debug("end of generic_release\n");
    return 0;
}

#if 0
static bool startsWith(const char* x, int xLen, const char* start)
{
    const char* xEnd = x + xLen;

    while (x != xEnd)
    {
        if (*start == 0)
        {
            // alles in start verglichen, also fing x mit start an
            return true;
        }
        if (*x != *start)
        {

            // Unterschied gefunden, negativ abbrechen
            pr_info("Unterschied bei *start=%c, *x=%c\n", *start, *x);
            return false;
        }
        // bis hier gleich, weiter
        ++x;
        ++start;
    }

    pr_info("Ende von x");
    return *start == 0; // sonst Ende von x erreicht, aber noch nicht Ende von start
}
#endif

//static int
//doDmaChanAlloc(uint32_t cmd)
//{
//    int res = 4;
//    int ch;
//
//    pr_info("dma alloc\n");
//    ch = cmd & ARG_MASK;
//    if (ch == ARG_MASK)
//    {
//        ch = EDMA_CHANNEL_ANY;
//    }
//    if (ch != EDMA_CHANNEL_ANY && (ch < 0 || ch >= 64))
//    {
//        pr_err("Ungueltiger Dma Kanal: %d\n", ch);
//        if (unlikely(kfifo_put(&mainAnswerFifo, -1) == 0))
//        {
//            res = 0; // hier 0 zurueckgeben um zu zeigen, dass keine Antwort generiert werden konnte
//            goto end;
//        }
//    }
//    else
//    {
//
////                              if (unlikely(!try_module_get(THIS_MODULE))) {
////                                      pr_err("%s: try_module_get fehlgeschlagen\n", __FUNCTION__);
////
////                                      if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1))) {
////                                              return 0; // hier 0 zurueckgeben um zu zeigen, dass keine Antwort generiert werden konnte
////                                      }
////                              } else {
////                                      // module_get hat funktioniert wie erwartet
//
//        DmaChan* p = kzalloc(sizeof(DmaChan), GFP_KERNEL);
//        int res = DmaChan_create(p, ch);
//        if (res)
//        {
//            // fehler
////                                              module_put(THIS_MODULE);
//
//            if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
//            {
//                res = 0; // hier 0 zurueckgeben um zu zeigen, dass keine Antwort generiert werden konnte
//                goto end;
//            }
//        }
//        else
//        {
//            // dma channel erfolgreich allokiert
//
//            if (unlikely(0 == kfifo_put(&mainAnswerFifo, p->ch)))
//            {
//                // Antwort dieses Kommandos konnte nicht in den fifo gestellt werden, also allokation rueckgaengig:
//                DmaChan_delete(p);
//                res = 0;
//                goto end;
//            }
//            else
//            {
//                spin_lock(&dmaChannelsLock);
//                dmaChannels[p->ch] = p; // keine Synchronisierung noetig, da die edma api dies uebernimmt und channels nicht mehrmals allokiert werden koennen
//                spin_unlock(&dmaChannelsLock);
//                pr_info("dma alloc komplett erfolgreich\n");
//                // allokation erfolgreich und generierung der antwort ebenso
//            }
//        }
////                              }
//    }
//
//    end: return res;
//
//}
//
//static int
//doDmaChanFree(uint32_t cmd)
//{
//
////    int ch;
//
//    pr_err("DEPRECATED: dma free\n");
//    return 0;
////    ch = cmd & ARG_MASK;
////    pr_info("dma free: ch=%d\n", ch);
////
////    if (unlikely(ch < 0 || ch >= 64))
////    {
////        pr_err("ungueltiger wert fuer ch: %d\n", ch);
////
////        if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
////        {
////            pr_err("dma free: kfifo_put fehlgeschlagen\n");
////            // fehlgeschlagen
////            return 0;
////        }
////    }
////    else if (unlikely(!dmaChannels[ch]))
////    {
////        pr_err("ungueltiger zustand von dmaChannels[%d]: NULL\n", ch);
////    }
////    else
////    {
////        DmaChan* toDelete = 0;
////
////        ASSERT(dmaChannels[ch]);
////        if (unlikely(0 == kfifo_put(&mainAnswerFifo, 0)))
////        {
////            pr_err(
////                    "MAIN_DMA_FREE: konnte nicht erfolgreiche antwort schreiben!\n");
////            return 0; // hier 0 zurueckgeben um zu zeigen, dass keine Antwort generiert werden konnte
////        }
////
////        pr_info("Going to free channel %d\n", ch);
////        spin_lock(&dmaChannelsLock);
////        toDelete = dmaChannels[ch];
////        dmaChannels[ch] = NULL;
////        spin_unlock(&dmaChannelsLock);
////        DmaChan_delete(toDelete);
////    }
////
////    return 4;
//
//}

static int
doMappedDma(uint32_t cmd)
{
    pr_info("doMappedDma\n");
    ASSERT(sizeof(uint32_t) == sizeof(dma_addr_t));
    if (unlikely(0 == kfifo_put(&mainAnswerFifo, (uint32_t) mappedDma)))
    {
        pr_err("MAIN_MAPPED_DMA: konnte nicht mappedDma schreiben!\n");
        return 0; // siehe oben
    }
    if (unlikely(0 == kfifo_put(&mainAnswerFifo, (uint32_t) mappedSize)))
    {
        pr_err("MAIN_MAPPED_DMA: konnte nicht mappedSize schreiben!\n");
        return -1; // -1 als Zeichen dafuer dass nicht alle 12 Antwort Bytes bereitgestellt werden konnten
    }
    if (unlikely(0 == kfifo_put(&mainAnswerFifo, (uint32_t) edma_channel_num(start1Chan)))) {
        pr_err("MAIN_MAPPED_DMA: konnte nicht tcc fuer payload schreiben!\n");
        return -1; // -1 als Zeichen dafuer dass nicht alle 12 Antwort Bytes bereitgestellt werden konnten
    }

    return 4; // dies ist der Rueckgabewert fuer das write (nicht fuer die anschliessend erst gelesene Antwort!)
}

//static int
//doDmaChanCrossbarMap(uint32_t cmd)
//{
//    int ch, evt;
//
//    ch = ((cmd & 0x0000ff00) >> 8);
//    evt = (cmd & 0x000000ff);
//
//    if (unlikely(ch < 0 || ch >= 64))
//    {
//        pr_err("%s: DMA_CROSSBAR_MAP: ungueltiger channel %d\n", __FUNCTION__,
//                ch);
//        if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
//        {
//            return 0; // konnte antwort nicht schreiben
//        }
//    }
//    else if (unlikely(evt < 0 || evt >= 64))
//    {
//        pr_err("%s: DMA_CROSSBAR_MAP: ungueltiges event %d\n", __FUNCTION__,
//                evt);
//        if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
//        {
//            return 0; // konnte antwort nicht schreiben
//        }
//    }
//    else
//    {
//        bool chValid = false;
//        ASSERT(ch >= 0 && ch < 64 && evt >= 0 && evt < 64);
//
//        // pruefen ob ch hier allokiert und in benutzung ist
//        spin_lock(&dmaChannelsLock);
//        // TODO check if dmaChannels[...]
//        chValid = (dmaChannels[ch] && dmaChannels[ch]->state == DCS_CONNECTED);
//
//        if (likely(chValid))
//        {
//            // ch und evt gueltig
//            DmaChan_ControlModulAccess control;
//            int res = -1;
//            ASSERT(0 == DmaChan_ControlModulAccess_create(&control));
//            res = DmaChan_ControlModulAccess_setCrossbarMapping(&control, ch,
//                    evt);
//            DmaChan_ControlModulAccess_delete(&control);
//
//            if (res == 0)
//            {
//                // crossbar mapping erfolgreich gesetzt
//            }
//            else
//            {
//                // hier nur ein moeglicher Fehlerwert, naemlich -1
//                res = -1;
//            }
//
//            if (unlikely(0 == kfifo_put(&mainAnswerFifo, res)))
//            {
//                // konnte antwort nicht schreiben
//                spin_unlock(&dmaChannelsLock);
//                return 0; // d.h. schreibvorgang nicht durchgefuehrt
//            }
//        }
//        else
//        {
//            pr_err("%s: DMA_CROSSBAR_MAP: channel %d nicht allokiert\n", __FUNCTION__,
//                    ch);
//            if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
//            {
//                spin_unlock(&dmaChannelsLock);
//                return 0; // konnte antwort nicht schreiben
//            }
//
//        }
//        spin_unlock(&dmaChannelsLock);
//
//    }
//
//    return 4;
//}
//
//static int
//doDmaParamSetAlloc(uint32_t cmd)
//{
//    uint32_t slot;
//    int iSlot;
//
//    slot = cmd & ARG_MASK;
//    if (slot == 0xffff)
//    {
//        // any slot
//        iSlot = EDMA_SLOT_ANY;
//    }
//    else
//    {
//        if (unlikely(slot >= 256))
//        {
//            pr_err("%s: ungueltiger Slot: %d\n", __FUNCTION__, slot);
//            return 0;
//        }
//
//        iSlot = (int)slot;
//        ASSERT(0 <= iSlot && iSlot < 256);
//    }
//
//    ASSERT(iSlot == EDMA_SLOT_ANY || (iSlot >= 0 && iSlot < 256));
//    iSlot = edma_alloc_slot(0, (unsigned)iSlot);
//
//    if (likely(iSlot >= 0))
//    {
//        ASSERT(iSlot < 256);
//        // slot erfolgreich allokiert
//        if (unlikely(0 == kfifo_put(&mainAnswerFifo, (uint32_t ) iSlot)))
//        {
//            // Antwort konnte nicht erstellt werden, also Allokation rueckgaengig
//            edma_free_slot(iSlot);
//            return 0;
//        }
//    }
//    else
//    {
//        // slot nicht erfolgreich allokiert, -1 zurueck, wenn moeglich; sonst 0 als rueckgabewert dieses schreibvorgangs als fehlerzeichen
//        if (unlikely(0 == kfifo_put(&mainAnswerFifo, (uint32_t ) -1)))
//        {
//            return 0;
//        }
//
//    }
//
//    return 4;
//}
//
//static int
//doDmaParamSetFree(uint32_t cmd)
//{
//    uint32_t slot;
//
//    slot = cmd & ARG_MASK;
//
//    if (unlikely(slot >= 256))
//    {
//        pr_err("%s: Ungueltiger slot: %d\n", __FUNCTION__, slot);
//
//        if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
//        {
//            return 0; // da Fehlerantwort -1 nicht geschrieben werden konnte, diesen Schreibvorgang nicht bestaetigen, also 0 zurueck
//        }
//    }
//    else
//    {
//        // TODO koennte man hier noch sicherer machen (aber dann leider auch etwas uneffizienter) indem man prueft ob der paramset ueber dieses device allokiert wurde
//
//        // anschliessendes edma_free_slot hat keinen rueckgabewert, also erst versuchen, Antwort zu erstellen und nur dann edma_free_slot, wenn dies geklappt hat
//        if (unlikely(0 == kfifo_put(&mainAnswerFifo, 0)))
//        {
//            return 0;
//        }
//
//        edma_free_slot((unsigned int)slot); // kfifo_put hat geklappt
//    }
//
//    return 4;
//}

// VORSICHT! Muss immer passen zum Rest des Gesamtsystems, also
// theoretisch zum ganzen Linux-Kernel samt aller Kernelmodule etc.!
static int doPinmux(uint32_t cmd) {
    int off, mode;
    bool pullEnabled, upSelected, receiverEnabled;
    int res;

    unpackPinmuxArgs(cmd, &off, &mode, &pullEnabled, &upSelected,
            &receiverEnabled);
    pr_info("%s: off=%d, mode=%d, pullEnabled=%d, upSelected=%d, receiverEnabled=%d\n", __FUNCTION__, off, mode, pullEnabled, upSelected, receiverEnabled);
    if (unlikely(!(off >= 0 && off <= 0x234)))
    {
        pr_err("%s: Unerwarteter off wert: %d\n", __FUNCTION__, off);

        if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
        {
            return 0;
        }
    }
    else if (unlikely(!(mode >= 0 && mode < 8)))
    {
        pr_err("%s: Unerwarteter mode wert: %d\n", __FUNCTION__, mode);

        if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
        {
            return 0;
        }
    }
    else
    {
        DmaChan_ControlModulAccess access;

        ASSERT(off >= 0 && off <= 0x234);
        ASSERT(mode >= 0 && mode < 8);

        res = DmaChan_ControlModulAccess_create(&access);

        if (0 == res)
        {
            DmaChan_ControlModulAccess_setPinMux(&access, off, mode,
                    pullEnabled, upSelected, receiverEnabled);
            DmaChan_ControlModulAccess_delete(&access);

            // alles erfolgreich, nun hoffentlich noch auch kfifo_put ;-)
            if (unlikely(0 == kfifo_put(&mainAnswerFifo, 0)))
            {
                // Oje, pinmux bereits gesetzt, aber Antwort, dass erfolgreich kann nicht uebermittelt werden
                // shit happens ;-) (kommt praktisch hoffentlich nie vor...)
                return 0;
            } // else pinmux erfolgreich gesetzt und auch die antwort des erfolgs erfolgreich im fifo gelandet

        }
        else
        {
            if (unlikely(0 == kfifo_put(&mainAnswerFifo, -1)))
            {
                return 0; // keine antwort
            }
        }
    }

    return 4;
}

static ssize_t
Main_write(struct file *file, const char __user *buf, size_t count,
           loff_t *ppos)
{
    int result = count;
    MyDevice* p = NULL;
    uint32_t cmd = 0;
    unsigned long notCopied;

    p = container_of(file->f_inode->i_cdev, MyDevice, dev);
    ASSERT(&p->dev == file->f_inode->i_cdev);

    switch (count)
    {

        case 4:
        {

            notCopied = copy_from_user(&cmd, buf, count);
            if (notCopied)
            {
                pr_err(
                        "%s: Nur %d von %d Bytes konnten aus dem Userspace kopiert werden!\n",
                        __FUNCTION__, (int )(count - notCopied), (int )count);
            }

            switch (cmd & CMD_MASK)
            {
//                case DMA_CHAN_ALLOC:
//                    result = doDmaChanAlloc(cmd);
//                    break;
//                case DMA_CHAN_FREE:
//                    result = doDmaChanFree(cmd);
//                    break;
                case CV_MAIN_MAPPED_DMA:
                    result = doMappedDma(cmd);
                    break;
//                case DMA_CHAN_CROSSBAR_MAP:
//                    result = doDmaChanCrossbarMap(cmd);
//                    break;
//                case DMA_PARAMSET_ALLOC:
//                    result = doDmaParamSetAlloc(cmd);
//                    break;
//                case DMA_PARAMSET_FREE:
//                    result = doDmaParamSetFree(cmd);
//                    break;

                case CV_MAIN_PINMUX:
                    result = doPinmux(cmd);
                    break;

                case CV_MAIN_SHUT_DOWN:
                    pr_info("CV_MAIN_SHUT_DOWN\n");
                    JobApi_down();

                    if (unlikely(0 == kfifo_put(&mainAnswerFifo, 0))) {
                        pr_err("Could not put answer for CV_MAIN_SHUT_DOWN to mainAnswerFifo\n");
                        result = 0;
                    } else {
                        result = 4;
                    }
                    break;

                default:
                    pr_err("Unbekanntes Kommando: 0x%x\n", cmd);
                    return 0;
            }
            break;
        }

    }

    if (ppos && result > 0)
    {
        *ppos += result;
    }

    return result;
}

static ssize_t
Main_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{

#if 0
    MyDevice* p = NULL;
    int len;
    char testOutput[256];
    ssize_t res;

//	pr_info("Main_read\n");

    p = container_of(file->f_inode->i_cdev, MyDevice, dev);
    ASSERT(&p->dev == file->f_inode->i_cdev);

    len = snprintf(testOutput, 256, "read on devNum=%d\n", p->devNum);
//	pr_info("content of testOutput: '%s'\n", testOutput);
//	pr_info("len=%d\n", len);
    res = len - copy_to_user(buf, testOutput, len);
    if (ppos)
    {
        *ppos += res;
    }
//	pr_info("res=%d\n", res);
    return res;
#else
    // einfach einen Eintrag aus dem Fifo lesen
    unsigned int copied = 0;

    if (unlikely((count & 3) != 0))
    { // count muss durch 4 teilbar sein, da Groesse der Antwortfifoelemente 4 Byte ist.
        pr_err(
                "Unerwarteter count (nicht durch 4 teilbar) bei Main_read: %u\n",
                count);
        return 0;
    }

    pr_info("Main_read: count=%u\n", count);

//    pr_info("-EIO=%d\n", -EIO);

    if (unlikely(
            kfifo_to_user(&mainAnswerFifo, buf, (unsigned int ) count,
                    &copied)))
    {
        pr_err("kfifo_to_user fehlgeschlagen\n");
        // fehler
        return -EIO;
    }

    if (unlikely(copied != count))
    {
        pr_warn("Unerwartete Laenge beim Lesen von Main Dev. Konnte nur %d von %d Bytes zurueckgeben.\n", copied, count);
    }

    return copied;

#endif
}

static int
Main_mmap(struct file* f, struct vm_area_struct* vm)
{
    int res, res2;

    res = dma_common_mmap(NULL, vm, mappedCpu, mappedDma, mappedSize);
//	pr_info("dma_common_mmap returned %d\n", res);
    res2 = dma_mapping_error(NULL, mappedDma);
    pr_info("dma_mapping_error returned %d\n", res2);

//	printk(KERN_INFO "dev_mmap returning %d\n", res);
    return res;
}


//static int
//DmaChan_open(struct inode * n, struct file * f) {
//    // Hier noch nicht bekannt welcher channel ueber diese file-instanz (entspricht einem file handle im user space) verwaltet wird.
//    // Dies wird durch das erste write definiert werden.
//    f->private_data = NULL;
//
//    if (!try_module_get(THIS_MODULE))
//    {
//        return -EIO;
//    }
//
//    return 0;
//}
//
//static int
//DmaChan_release(struct inode * n, struct file * f) {
//    DmaChan* p;
//
//    if (unlikely(!f->private_data)) {
//        module_put(THIS_MODULE);
//        return 0;
//    }
//
//    p = (DmaChan*) f->private_data;
//
//    switch (p->state) {
//        case DCS_SHUTDOWN:
//        case DCS_CONNECTED:
//            break;
//        default:
//            pr_warn("DmaChan_release: unerwarteter zustand: %d\n", p->state);
//            break;
//    }
//
//    p->state = DCS_DELETED; // eigentlich ueberfluessig da danach freigegeben, aber paranoiderweise gemacht um evtl. anschliessend fehlerhafte speicherzugriffe aufdecken...
//    f->private_data = NULL;
//    spin_lock(&dmaChannelsLock);
//    ASSERT(p->ch >= 0 && p->ch < 64);
//    ASSERT(dmaChannels[p->ch] == p);
//    dmaChannels[p->ch] = NULL;
//    spin_unlock(&dmaChannelsLock);
//
//    DmaChan_delete(p);
//    kfree(p);
//    p = NULL;
//
//    module_put(THIS_MODULE);
//    return 0;
//}
//
//// TODO:
///*
//Besser so:
//Allokation von DmaChan und DmaParamSet nicht ueber "Main" Command, sondern erst in
//DmaChan_write bzw. DmaParamSet_write
//ueberlegen wo ueberhaupt unterschied zwischen DmaChan und DmaParamSet liegt:
//wahrscheinlich nur beim aufruf der edma-api-funktionen
//Rest gleich, d.h. beim ersten write allokieren, beim release freigeben
//
//dafuer:
//-3. struct DmaParamSet
//-2. DmaParamSet_write
//-1. DmaParamSet_release
//0. dmaParamSetOps
//1. dmaParamSetClass
//2. dmaParamSetDev
//3. my_init1 erweitern
//4. my_exit1 erweitern
//5.
// */
//
//static ssize_t
//DmaChan_write(struct file *file, const char __user *buf, size_t count,
//              loff_t *ppos)
//{
//    if (file->private_data) {
//        // nicht erstes write, dient als hint fuer shutdown
//        DmaChan* p;
//
//        pr_info("DmaChan_write fuer shutdown\n");
//        p = (DmaChan*) file->private_data;
//
//        if (p->state == DCS_CONNECTED) {
//            p->state = DCS_SHUTDOWN;
//            complete(&p->comp);
//            complete(&p->comp);
//            complete(&p->comp);
//            complete(&p->comp);
//            complete(&p->comp);
//            complete(&p->comp);
//            // zur sicherheit ein paar mehr... ;-)
//            return count;
//        } else {
//            return -EIO;
//        }
//    } else {
//        // erstes write
//        char data;
//        int ch;
//
//        pr_info("DmaChan_write: erstes fuer Verbindung\n");
//
//        if (count != 1) {
//            return -EIO; // unerlaubtes write
//        }
//        ASSERT(count == 1);
//        if (copy_from_user(&data, buf, 1) != 0) {
//            return -EIO;
//        }
//
//        ch = (int) data;
//        ASSERT(ch >= 0);
//        ASSERT(ch < 64);
//        // data identifiziert einen dma channel?
//
//        spin_lock(&dmaChannelsLock);
//        if (!dmaChannels[ch]) {
//            // ch nicht allokiert
//            spin_unlock(&dmaChannelsLock);
//            pr_err("dmaChannels[%d]==NULL\n", ch);
//            return -EIO;
//        } else if(dmaChannels[ch]->state != DCS_UNCONNECTED) {
//            spin_unlock(&dmaChannelsLock);
//            pr_err("dmaChannels[%d]->state == %d != DCS_UNCONNECTED\n", ch, dmaChannels[ch]->state);
//            return -EIO;
//        } else {
//            dmaChannels[ch]->state = DCS_CONNECTED;
//            pr_info("channel %d nun verbunden\n", ch); // TODO nicht waehrend spin_lock!
//            file->private_data = dmaChannels[ch];
//        }
//        spin_unlock(&dmaChannelsLock);
//    }
//
//    return (ssize_t) count;
//}
//
//static ssize_t
//DmaChan_read(struct file* f, char __user* buf, size_t count, loff_t *ppos)
//{
//    ssize_t res;
//    DmaChan* p = NULL;
//    uint32_t val = 0;
//
////    p = container_of(container_of(f->f_inode->i_cdev, MyDevice, dev), DmaChan,
////            myDevice);
////    ASSERT(&p->myDevice.dev == f->f_inode->i_cdev);
//
//    if (!f->private_data) {
//        return -EPROTO; // zu frueh gelesen
//    }
//
//    p = (DmaChan*) f->private_data;
//
//    ASSERT(p);
//
//    switch (p->state) {
//    case DCS_SHUTDOWN:
//        return -ENODATA;
//
//    case DCS_CONNECTED: {
//        int waitRes = 0;
//
//        if (count != sizeof(uint32_t))
//        {
//            pr_err("%s: unerwarteter count=%d\n", __FUNCTION__, count);
//            return -EPROTO; //
//        }
//
//        // 1x immer wirklich blockierend
//        pr_debug("vor wait_for_completion: p=%p, p->ch=%d\n", p, p->ch);
//        waitRes = wait_for_completion_interruptible(&p->comp);
//
//        if (0 != waitRes)
//        {
//            if (waitRes != -ERESTARTSYS) {
//                pr_warn("DmaChan_read: wait_for_completion_interruptible returned %d (-ERESTARTSYS: %d)\n", waitRes, -ERESTARTSYS);
//            } else {
//                pr_debug("DmaChan_read: wait_for_completion_interruptible returned -ERESTARTSYS\n");
////                return -EINTR;
//                return -ERESTARTSYS;
//            }
//            // interrupted
//            return waitRes;
//        }
//        pr_info("nach wait_for_completion: p=%p\n", p);
//        switch (p->state) {
//            case DCS_CONNECTED:{
//                val = 1;
//                // weitere wenn nicht blockierend:
//                while (p->state == DCS_CONNECTED && try_wait_for_completion(&p->comp))
//                {
//                    ++val;
//                    pr_info(".val=%d\n", (int) val);
//                }
//                res = copy_to_user(buf, &val, sizeof(val));
//                if (0 != res)
//                {
//                    pr_err("Unerwartetes verhalten von copy_to_user: res=%d\n", res);
//                    return count - res;
//                }
//                return count;
//
//                break;
//            }
//            case DCS_SHUTDOWN:
//                pr_info("read im zustand shutdown beantwortet mit ENODATA\n");
//                return -ENODATA;
//            default:
//                return -EPROTO;
//        }
//    }
//
//    default:
//        return -EIO;
//    }
//}
//
//static int
//DmaParamSet_open(struct inode * n, struct file * f) {
//    f->private_data = NULL;
//
//    if (!try_module_get(THIS_MODULE))
//    {
//        return -EIO;
//    }
//
//    return 0;
//
//}

ssize_t test_read(struct file * f, char __user * p, size_t sz, loff_t * off) {
    const char* resp = "Hello, world!\n";
    int len = strlen(resp);

    int n = min((int) sz, len);
    return n - copy_to_user(p, resp, n);
}

struct file_operations testOps = {
        .read = test_read
};

static t_cv_char_dev_simple testDev __attribute__((unused));

static int __init
KM_CvKernelAccess_init(void)
{
#if 1
    return myinit1();
#else
    return cv_cds_create(&testDev, "testName", &testOps);
#endif
}

static void __exit
KM_CvKernelAccess_exit(void)
{
#if 1
    myexit1();
#else
    cv_cds_delete(&testDev);
#endif
}

MODULE_LICENSE("GPL");

module_init(KM_CvKernelAccess_init);
module_exit(KM_CvKernelAccess_exit);
