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

/*
 * KM_JobApi.h
 *
 *  Created on: 12.04.2022
 *      Author: preitinger
 */

#ifndef JOBAPI_KM_JOBAPI_H_
#define JOBAPI_KM_JOBAPI_H_

/*
 * Konzept:
 *
 * JobApi-Funktionen beginnen mit Oeffnen des Devices /dev/cv_jobapi und enden mit Schliessen desselben Devices.
 * Nach dem Oeffnen von /dev/cv_jobapi wird das lokale Fpga-Reset fuer die JobApi-Funktionen im FPGA eine ms lang
 * auf aktiv low gesetzt und dann auf passiv high gesetzt.
 * Nach dem Schliessen des Devices wird das lokale Fpga-Reset auf aktiv low gesetzt und bleibt so.
 * Dieses Device kann nur genau einmal geoeffnet werden, ist nur fuer die JobApi vorgesehen, welche immer nur
 * singulaer aktiv sein kann.
 * Aktionen fuer die JobApi werden durch Schreiben eines 32-bit-Kommandos gestartet und nach deren Ende mit einer
 * lesbaren Antwort bestaetigt.
 * Es ist immer nur eine Aktion gleichzeitig moeglich.
 * Nach dem Oeffnen des Devices, werden einmalig bestimmte Daten nach einem festen Protokoll ausgelesen, wie z.B.
 * gemappter DMA-faehiger Speicher fuer Antworten/Nachrichten vom FPGA direkt an die JobApi oder fest vorab
 * allokierte DMA-Kanaele bzw. deren Parametersaetze.
 */

#include "JobApi_waitForAnyDmaCompletion.h"

#include <linux/atomic.h>
#include <linux/fs.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/kfifo.h>

//#include "../KM_CvKernelAccess.h"
#include "JobApi_commands.h"
#include "../fpgaSerial/dmaPayloadKM.h"


#define CV_JOBAPI_CLASS_NAME "CV_JOBAPI"
#define CV_JOBAPI_DEV_NAME "cv_jobapi"

/**
 * Liste fuer von der JobApi allokierte DMA-Kanaele
 */
typedef struct SCvChanList {
    struct list_head list;
    struct dma_chan* chan;
    int ch;
    bool preallocated;
    atomic_t completionCount;
} CvChanList;

typedef enum ECvJobApiState {
    CVJ_CLOSED,
    CVJ_OPENING,
    CVJ_OPEN,
    CVJ_CLOSING
} CvJobApiState;

/**
 * Liste fuer von der JobApi allokierte DMA-Parametersaetze
 */
typedef struct SCvPrmsList {
    struct list_head list;
    int num;
} CvPrmsList;

typedef struct SCvJobApiEnv {
    void* mappedCpu;
    dma_addr_t mappedDma;
    uint32_t mappedSize;
    struct mutex m;
    DECLARE_KFIFO(fifo, uint32_t, 16);
    CvJobApiState st;
    struct file_operations ops;
    struct class* jobapi_class;
    MyDevice jobapi_dev;
    struct list_head channelList;
    struct list_head paramsetList;
    struct semaphore cmdRespSema;
    int respCount;
    struct completion complForDma;
    struct completion closingCompl;
    int useCount;
} CvJobApiEnv;


//static struct class* cv_jobapi_class = NULL;
//static MyDevice cv_jobapi_dev;
//static DEFINE_MUTEX(cv_jobapi_mutex);
//static DEFINE_KFIFO(cv_jobapi_fifo, uint32_t, 16);

// koennte man sauberer als pointer machen und in init dynamisch allokieren, aber aber hier ueberfluessig, da dieses KM sowieso immer geladen wird.
static CvJobApiEnv cv_jobapi_env;

static CvChanList* cv_chan_list_lookup(int ch) {
    CvChanList* listNode;

    list_for_each_entry(listNode, &cv_jobapi_env.channelList, list) {
        if (listNode->ch == ch) {
            return listNode;
        }
    }

    return NULL;
}

static CvPrmsList* cv_prms_list_lookup(int num) {
    CvPrmsList* listNode;

    list_for_each_entry(listNode, &cv_jobapi_env.paramsetList, list) {
        if (listNode->num == num) {
            return listNode;
        }
    }

    return NULL;
}

static void cv_jobApiCompletionCb(void* pArg) {
    struct completion* compl = pArg;

//    pr_info("cv_jobApiCompletionCb\n"); // Problem?
    complete(compl);
}

static void cv_jobapi_dmaCompletion(unsigned channel, enum dma_status status, void* data) {
    CvChanList* n = data;

//    pr_info("status=%d\n", (int) status);
//    pr_info("(DMA_COMPLETE=%d)\n", DMA_COMPLETE);
//    pr_info("cv_jobapi_dmaCompletion: n->ch=%d, channel=%d\n", n->ch, (int) channel);

    if (status == DMA_COMPLETE) {
        atomic_inc(&n->completionCount);
//        pr_info("cv_jobapi_env.complForDma.done=%d\n", (int) cv_jobapi_env.complForDma.done);
//        pr_info("completionCount: %d\n", atomic_read(&n->completionCount));
        complete(&cv_jobapi_env.complForDma);
    }
}

static inline void cv_jobapi_add_preallocated_chan(CvJobApiEnv* e, struct dma_chan* chan) {
    CvChanList* node;

    node = kzalloc(sizeof(CvChanList), GFP_KERNEL);
    ASSERT(!(ZERO_OR_NULL_PTR(node)));
    INIT_LIST_HEAD(&node->list);
    node->chan = chan;
    node->ch = (int) edma_channel_num(chan);
    node->preallocated = true;
    atomic_set(&node->completionCount, 0);
    list_add(&node->list, &e->channelList);
    {
        struct CvHandler h = {
                cv_jobapi_dmaCompletion,
                node
        };
        edma_setCvHandler(chan, h);
//        pr_info("nach edma_setCvHandler fuer chan->ch=%d\n", (int) node->ch);
    }
}

static int cv_jobapi_open(struct inode * n, struct file * f) {
    CvJobApiEnv* e = &cv_jobapi_env;

//    pr_info("CV_BEGIN open\n");
//    pr_info("cv_jobapi_open\n");

    if (unlikely(!try_module_get(THIS_MODULE))) {
        pr_err("try_module_get failed in cv_jobapi_open\n");
        return -EIO;
    }

    mutex_lock(&e->m);

    switch (e->st) {
        case CVJ_CLOSED: { // nach switch fortfahren
            break;
        }
        default: { // Fehler; abbrechen
            char buf[256];

            mutex_unlock(&e->m);
            pr_err("'%s' darf nur einmal und zwar von der JobApi geoeffnet werden!\n", d_path(&f->f_path, buf, 256));
            module_put(THIS_MODULE);
            return -EIO;
            break;
        }
    }

    cv_jobapi_add_preallocated_chan(e, start1Chan);
    cv_jobapi_add_preallocated_chan(e, adcChan);

    init_completion(&e->complForDma);
//    pr_info("complForDma initialized\n");
    init_completion(&e->closingCompl);
    e->useCount = 0;

    {
        DECLARE_COMPLETION_ONSTACK(openCompl);

        e->st = CVJ_OPENING;
//        pr_info("Sending openJobApiDma\n");
        fpgaSendCmdWithResp(create_openJobApiDma(), &cv_jobApiCompletionCb, &openCompl);
//        pr_info("sent openJobApiDma\n");
        sema_init(&e->cmdRespSema, 0);
        mutex_unlock(&e->m);

//        pr_info("vor wait for openCompl\n");
        wait_for_completion(&openCompl);
//        pr_info("nach wait for openCompl\n");
        mutex_lock(&e->m);
//        pr_info("resp received for openJobApiDma\n");

    }

    INIT_KFIFO(e->fifo);

    { // Antworten fuer erste reads nach open (gemaess hart-codiertem Protokoll) vorbereiten
        int res;

        ASSERT(sizeof(uint32_t) == sizeof(dma_addr_t));
        res = kfifo_put(&e->fifo, (uint32_t) e->mappedDma);
        ASSERT(1 == res);
        res = kfifo_put(&e->fifo, (uint32_t) e->mappedSize);
        ASSERT(1 == res);
        res = kfifo_put(&e->fifo, (uint32_t) edma_channel_num(start1Chan));
        ASSERT(1 == res);
        res = kfifo_put(&e->fifo, (uint32_t) edma_channel_num(adcChan));
        ASSERT(1 == res);
//        pr_info("put mappedDma, mappedSize and tcc to kfifo\n");
    }

    e->respCount = 4; // da oben 4 antworten in den kfifo gestellt
    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_JOBAPI_RESET, 1);
//    pr_info("FPGA[ADDR_OTH_JOBAPI_RESET] := 1\n");
    e->st = CVJ_OPEN;

    mutex_unlock(&e->m);

//    pr_info("CV_END open\n");
    return 0;
}

static int cv_jobapi_mmap(struct file* f, struct vm_area_struct* vm) {
    CvJobApiEnv* e = &cv_jobapi_env;
    int res, res2;

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

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

// Voraussetzung: e->m bei Aufruf gesperrt
static void startClose(void) {
    CvJobApiEnv* e = &cv_jobapi_env;

//    pr_info("CV_BEGIN startClose\n");

    if (e->st != CVJ_CLOSING) {
        ASSERT(e->st == CVJ_OPEN);
        e->st = CVJ_CLOSING;

        {
            DECLARE_COMPLETION_ONSTACK(closeFpgaCompl);

//            pr_info("vor send close job api dma\n");
            fpgaSendCmdWithResp(create_closeJobApiDma(), &cv_jobApiCompletionCb, &closeFpgaCompl);
//            pr_info("nach send close job api dma\n");
            mutex_unlock(&e->m);
//            pr_info("warte auf closeFpgaCompl\n");
            wait_for_completion(&closeFpgaCompl);
//            pr_info("nach closeFpgaCompl\n");

            mutex_lock(&e->m);
//            pr_info("vor complete_all(complForDma)\n");
            complete_all(&e->complForDma);

        }
    } else {
//        pr_info("startClose: already closing\n");
    }

//    pr_info("CV_END startClose\n");
}

static int cv_jobapi_release(struct inode * n, struct file * f) {
    CvJobApiEnv* e = &cv_jobapi_env;

//    pr_info("CV_BEGIN release\n");
    mutex_lock(&e->m);
//    pr_info("nach lock\n");


//    pr_info("startClose in cv_jobapi_release\n");
    startClose();

    while (e->useCount > 0) {
        pr_warn("release: useCount noch %d. Warte ...\n", e->useCount);
        mutex_unlock(&e->m);
        wait_for_completion(&e->closingCompl);
        mutex_lock(&e->m);
    }

//    pr_info("vor setzen der jobapi im fpga in den reset-zustand\n");
    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_JOBAPI_RESET, 0);
//    pr_info("FPGA[ADDR_OTH_JOBAPI_RESET] := 0\n");


    {
        CvChanList* listNode;

        while ((listNode = list_first_entry_or_null(
                &cv_jobapi_env.channelList, CvChanList, list))) {

            if (!listNode->preallocated) {
                dma_release_channel(listNode->chan);
                dmaengine_put();
            }

            list_del(&listNode->list);
            kfree(listNode);
        }
    }
    {
        CvPrmsList* listNode;

        while ((listNode = list_first_entry_or_null(
                &cv_jobapi_env.paramsetList, CvPrmsList, list))) {

            my_edma_free_slot(listNode->num);
            list_del(&listNode->list);
            kfree(listNode);
        }

    }

    e->st = CVJ_CLOSED;
    mutex_unlock(&e->m);

    module_put(THIS_MODULE);

//    pr_info("CV_END release\n");
    return 0;
}

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

    CvJobApiEnv* e = &cv_jobapi_env;
    uint32_t cmd;
    unsigned long notCopied;
    ssize_t res = count;

//    pr_info("CV_BEGIN write\n");

    mutex_lock(&e->m);

//    pr_info("nach lock m\n");

    switch (e->st) {
        case CVJ_OPEN:
            ++e->useCount;
//            pr_info("write: useCount=%d\n", e->useCount);
            break;

        default: {
            pr_err("write: unexpected state: %d\n", e->st);
            mutex_unlock(&e->m);
            res = -EIO;
            return res;
        }
    }

    notCopied = copy_from_user(&cmd, buf, sizeof(cmd));
    ASSERT(0 == notCopied);
//    pr_info("write: cmd: 0x%x\n", cmd);


    switch (cmd & EPC_CMD_MASK) {
        case EPC_CHAN_ALLOC: {
            int ch = EPC_ARG_BYTE_LO(cmd);
            int crossbarEvt = EPC_ARG_BYTE_HI(cmd);
            struct dma_chan* chan;
            dma_cap_mask_t mask;

//            pr_info("EPC_CHAN_ALLOC: ch=0x%x, crossbarEvt=0x%x\n", ch, crossbarEvt);
            dma_cap_zero(mask);
            dmaengine_get();

            if (ch == 0xff) {
//                pr_info("vor dma_request_chan_by_mask\n");
                chan = dma_request_chan_by_mask(&mask);
            } else {
                if (likely(ch >= 0 && ch < 64)) {
//                    pr_info("vor dma_request_channel: ch=0x%x\n", ch);
                    chan = dma_request_channel(mask, edma_filter_fn, EDMA_FILTER_PARAM(0, ch));
                } else {
//                    pr_info("ch=%x\n", ch);
                    chan = NULL;
                }
            }


            if (IS_ERR_OR_NULL(chan)) {
                pr_warn("Kein DMA-Kanal mehr verfuegbar!\n");
                dmaengine_put();
                res = -EINVAL;
                goto end;
            }

            ch = edma_channel_num(chan);

            if (likely(crossbarEvt >= 0 && crossbarEvt < 64)) {
                DmaChan_ControlModulAccess control;
                ASSERT(0 == DmaChan_ControlModulAccess_create(&control));
                DmaChan_ControlModulAccess_setCrossbarMapping(&control, ch,
                        crossbarEvt);
                DmaChan_ControlModulAccess_delete(&control);
            }

            {
                CvChanList* newListNode;
                int res1;

                newListNode = kzalloc(sizeof(CvChanList), GFP_KERNEL);
                ASSERT(!(ZERO_OR_NULL_PTR(newListNode)));
                INIT_LIST_HEAD(&newListNode->list);
                newListNode->chan = chan;
                newListNode->ch = ch;
                newListNode->preallocated = false;
                atomic_set(&newListNode->completionCount, 0);
                list_add(&newListNode->list, &e->channelList);

                {
                    struct CvHandler h = {
                            cv_jobapi_dmaCompletion,
                            newListNode
                    };
                    edma_setCvHandler(chan, h);
//                    pr_info("nach edma_setCvHandler fuer chan->ch=%d\n", (int) newListNode->ch);
                }

                mutex_unlock(&e->m);
//                pr_info("vor down cmdRespSema\n");
                down(&e->cmdRespSema); // ggf. schlafen bis parallele (konzeptionell atomare) write-read-kombination abgeschlossen
//                pr_info("nach down cmdRespSema\n");
                mutex_lock(&e->m);

                if (e->st != CVJ_OPEN) {
//                    pr_info("up cmdRespSema because -EIO\n");
                    up(&e->cmdRespSema);
                    res = -EIO;
                    goto end;
                }


                // folgendes read vorbereiten
                res1 = kfifo_put(&e->fifo, (uint32_t) newListNode->ch);
                ASSERT(1 == res1);
                ASSERT(e->respCount == 0);
                ++e->respCount;
            }

            ASSERT(res == count);
            break;
        }
        case EPC_CHAN_CROSSBAR: {
            int8_t ch = EPC_ARG_BYTE_LO(cmd);
            int crossbarEvt = EPC_ARG_BYTE_HI(cmd);
            CvChanList* found = NULL;

            if (!(ch >= 0 && ch < 64 && crossbarEvt >= 0 && crossbarEvt < 64)) {
                res = -EINVAL;
            } else {
                ASSERT(ch >= 0 && ch < 64);
                ASSERT(crossbarEvt >= 0 && crossbarEvt < 64);
                found = cv_chan_list_lookup(ch);

                if (found) {
                    DmaChan_ControlModulAccess control;
                    ASSERT(0 == DmaChan_ControlModulAccess_create(&control));
                    DmaChan_ControlModulAccess_setCrossbarMapping(&control, ch,
                                                                  crossbarEvt);
                    DmaChan_ControlModulAccess_delete(&control);
                }
            }

            break;
        }
        case EPC_CHAN_FREE: {
            int ch = cmd & 0xffff;
            CvChanList* found = NULL;

            if (unlikely(!(ch >= 0 && ch < 64))) {
                res = -EINVAL;
            } else {
                ASSERT(ch >= 0 && ch < 64);

                found = cv_chan_list_lookup(ch);

                if (found && !found->preallocated) {
                    dma_release_channel(found->chan);
                    dmaengine_put();
                    list_del(&found->list);
                    kfree(found);
                }
            }

            break;
        }
        case EPC_PRMS_ALLOC: {
            int num = cmd & 0xffff;

            if (num == 0xffff) {
                num = -1;
            }

//            pr_info("EPC_PRMS_ALLOC: num=0x%x\n", num);

            if (!(-1 >= num && num < 256)) {
                res = -EINVAL;
            } else {
                dmaengine_get();
//                pr_info("vor my_edma_alloc_slot\n");
                num = my_edma_alloc_slot(num);

//                pr_info("num nach alloc: 0x%x\n", num);

                if (likely(num >= 0)) {
                    CvPrmsList* newNode = NULL;
                    int res1;

                    ASSERT(num < 256);

                    // in liste e->paramSetList eintragen und num als Antwort in den Fifo

                    newNode = kzalloc(sizeof(CvPrmsList), GFP_KERNEL);
                    ASSERT(!(ZERO_OR_NULL_PTR(newNode)));

                    INIT_LIST_HEAD(&newNode->list);
                    newNode->num = num;
                    list_add(&newNode->list, &e->paramsetList);

                    mutex_unlock(&e->m);
//                    pr_info("vor down cmdRespSema\n");
                    down(&e->cmdRespSema); // ggf. schlafen bis parallele (konzeptionell atomare) write-read-kombination abgeschlossen
//                    pr_info("nach down cmdRespSema\n");
                    mutex_lock(&e->m);
                    // folgendes read vorbereiten
                    res1 = kfifo_put(&e->fifo, (uint32_t) num);
                    ASSERT(1 == res1);
                    ASSERT(e->respCount == 0);
                    ++e->respCount;
                } else {
                    dmaengine_put();
                    res = -EINVAL;
                }
            }
            break;
        }
        case EPC_PRMS_FREE: {
            int num = cmd & 0xffff;
            CvPrmsList* found = NULL;

            if (!(num >= 0 && num < 256)) {
                res = -EINVAL;
            } else {

                // e->paramsetList durchsuchen nach num
                found = cv_prms_list_lookup(num);

                if (found) {
                    list_del(&found->list);
                    my_edma_free_slot(num);
                    kfree(found);
                }
            }

            break;
        }
        case EPC_PIN_MUX: {
            int off;
            int mode;
            bool pullEnabled;
            bool upSelected;
            bool receiverEnabled;

            epc_unpackPinmuxArgs(cmd, &off, &mode, &pullEnabled, &upSelected, &receiverEnabled);

            if (unlikely(!(off >= 0 && off <= 0x234))) {
                pr_err("%s: Unerwarteter off wert: %d\n", __FUNCTION__, off);

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

                res = -EINVAL;
            } else {
                DmaChan_ControlModulAccess access;
                int res1;

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

                res1 = DmaChan_ControlModulAccess_create(&access);

                if (0 == res1) {
                    DmaChan_ControlModulAccess_setPinMux(&access, off, mode,
                            pullEnabled, upSelected, receiverEnabled);
                    DmaChan_ControlModulAccess_delete(&access);
                } else {
                    res = -EINVAL;
                }
            }
            break;
        }
        case EPC_WAIT_FOR_ANY_DMA_COMPLETION: {
            CvChanList* chanNode;
            CvWaitForAnyCompletionResp r;
            int res1;
            bool anyFound = false;

//            pr_info("EPC_WAIT_FOR_ANY_DMA_COMPLETION\n");

            mutex_unlock(&e->m);
//            pr_info("vor down cmdRespSema\n");
            down(&e->cmdRespSema); // ggf. schlafen bis parallele (konzeptionell atomare) write-read-kombination abgeschlossen
//            pr_info("nach down cmdRespSema\n");
            mutex_lock(&e->m);


            while (!anyFound) {

                if (e->st != CVJ_OPEN) {
//                    pr_info("up cmdRespSema because -EIO\n");
                    up(&e->cmdRespSema);
                    res = -EIO;
                    goto end;
                }

//                pr_info("vor try_wait_for loop\n");
//                while (try_wait_for_completion(&e->complForDma));
//                pr_info("nach try_wait_for loop\n");

                ASSERT(e->respCount == 0);
                CvWaitForAnyCompletionResp_init(&r);

                list_for_each_entry(chanNode, &e->channelList, list) {
                    int count = 0;
                    count = atomic_xchg(&chanNode->completionCount, count);
//                    pr_info("any dma: ch=%d, count=%d\n", chanNode->ch, count);
                    if(count > 0) {
                        anyFound = true;
//                        pr_info("any found\n");

                        if (count > 3) {
                            // nur 3 realisierbar, also rest wieder zurueck
                            atomic_add(count - 3, &chanNode->completionCount);
                            count = 3;
                        }
                        ASSERT(count > 0 && count < 4);
                        if (CvWaitForAnyCompletionResp_put(&r, chanNode->ch, count)) {

                            // full, so put to kfifo and re-init r
                            res1 = kfifo_put(&e->fifo, r.resp);
                            ASSERT(1 == res1);
                            ++e->respCount;
                            CvWaitForAnyCompletionResp_init(&r);
                        }
                    }
                }

                if (anyFound) {
                    CvWaitForAnyCompletionResp_close(&r);
                    res1 = kfifo_put(&e->fifo, r.resp);
                    ASSERT(1 == res1);
                    ++e->respCount;
                } else {
//                    pr_info("still nothing found\n");
//                    pr_info("up cmdRespSema vor wait_for_completion_interruptible\n");
                    up(&e->cmdRespSema); // cmdRespSema darf nicht gesperrt sein waehrend wait_for_completion_interruptible, sonst sind
                    // keine anderen parallelen writes moeglich!
                    mutex_unlock(&e->m);
//                    pr_info("vor wait_for_completion_interruptible\n");
                    res1 = wait_for_completion_interruptible(&e->complForDma);
//                    pr_info("nach wait_for_completion_interruptible: res1=%d\n", res1);

                    if (res1) {
                        pr_err("wait_for_completion_interruptible interrupted\n");
                        res = res1;
//                        pr_info("up cmdRespSema because %d\n", res);
                        mutex_lock(&e->m);
                        goto end;
                    }

//                    pr_info("vor down cmdRespSema\n");
                    down(&e->cmdRespSema); // ggf. schlafen bis parallele (konzeptionell atomare) write-read-kombination abgeschlossen
//                    pr_info("nach down cmdRespSema\n");

                    mutex_lock(&e->m);
                }
            }


//            pr_info("any dma done\n");

            break;
        }

        case EPC_CLOSE: {
//            pr_info("startClose wegen cmd EPC_CLOSE\n");
            startClose();
            break;
        }

    } // switch

    end:

    --e->useCount;
//    pr_info("write: nach ende useCount=%d\n", e->useCount);

    if (e->useCount == 0 && e->st == CVJ_CLOSING) {
        complete(&e->closingCompl);
    }

    mutex_unlock(&e->m);


//    pr_info("CV_END write\n");
    return res;
}

static ssize_t cv_jobapi_read(struct file* f, char __user* buf, size_t count, loff_t *ppos) {
    CvJobApiEnv* e = &cv_jobapi_env;
    unsigned int copied;
    int res;

//    pr_info("CV_BEGIN read\n");
//    pr_info("cv_jobapi_read count=%d\n", (int) count);

    mutex_lock(&e->m);

    switch (e->st) {
        case CVJ_OPEN:
            ++e->useCount;
//            pr_info("read: useCount=%d\n", e->useCount);
            break;

        default: {
//            pr_info("read: unexpected state: %d\n", e->st);
            mutex_unlock(&e->m);
            res = -EIO;
            return res;
        }
    }

    res = kfifo_to_user(&e->fifo, buf, count, &copied);
//    pr_info("res of kfifo_to_user: %d\n", (int) res);
    if (res) {
        ASSERT(res < 0);
        pr_err("read: kfifo_to_user failed\n");
        goto end;
    }

    ASSERT(copied == count);

    ASSERT(e->respCount > 0);
//    pr_info("respCount=%d\n", e->respCount);

    if (--e->respCount == 0) {
//        pr_info("read: up cmdRespSema\n");
        up(&e->cmdRespSema);
    }

    end:

    --e->useCount;

    if (e->useCount == 0 && e->st == CVJ_CLOSING) {
        complete(&e->closingCompl);
    }

    mutex_unlock(&e->m);

//    pr_info("CV_END read\n");
    return count;
}

static int jobapi_init(void* mappedCpu, dma_addr_t mappedDma, uint32_t mappedSize) {
    int res = 0;
    CvJobApiEnv* e = &cv_jobapi_env;
    struct file_operations jobapi_ops =
    {
            .open = cv_jobapi_open,
            .release = cv_jobapi_release,
            .write = cv_jobapi_write,
            .read = cv_jobapi_read,
            .mmap = cv_jobapi_mmap,
    };

    e->mappedCpu = mappedCpu;
    e->mappedDma = mappedDma;
    e->mappedSize = mappedSize;
    mutex_init(&e->m);

    mutex_lock(&e->m);
    e->st = CVJ_CLOSED;

    e->jobapi_class = class_create(THIS_MODULE, CV_JOBAPI_CLASS_NAME);

    if (IS_ERR(e->jobapi_class)) {
        pr_err("could not create class CV_JOBAPI_CLASS\n");
        res = PTR_ERR(e->jobapi_class);
        goto err_class;
    }

    e->ops = jobapi_ops;
    if (MyDevice_create(&e->jobapi_dev, e->jobapi_class, &e->ops, "%s", CV_JOBAPI_DEV_NAME)) {
        pr_err("could not create device jobapi\n");
        // nicht 0, also fehler
        res = -EIO;
        goto err_my_device;
    }

    ////////////
    INIT_LIST_HEAD(&e->channelList);
    INIT_LIST_HEAD(&e->paramsetList);
    mutex_unlock(&e->m);
    pr_info("jobapi_init successful\n");
    return 0;
    ////////////

    err_my_device:

    class_destroy(e->jobapi_class);

    err_class:
    mutex_unlock(&e->m);

    return res;

}

void jobapi_exit(void) {
    CvJobApiEnv* e = &cv_jobapi_env;

    pr_info("jobapi_exit begin\n");
    MyDevice_delete(&e->jobapi_dev);
    class_destroy(e->jobapi_class);
    pr_info("jobapi_exit end\n");
}


#endif /* JOBAPI_KM_JOBAPI_H_ */
