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

/*
 * dmaCompletionThread.h
 *
 *  Created on: 06.04.2022
 *      Author: preitinger
 */

#ifndef FPGASERIAL_DMACOMPLETIONTHREAD_H_
#define FPGASERIAL_DMACOMPLETIONTHREAD_H_

#include <linux/kfifo.h>
#include <linux/kthread.h>
#include <linux/completion.h>
#include <linux/sched.h>
#include <uapi/linux/sched/types.h>

struct DmaCompletionEntry {
    bool canceled;
    uint8_t channel;
    enum dma_status status;
};

struct DmaCompletionStatus {
    bool overflow;
    bool unexpectedPutSize;
};

static const struct DmaCompletionStatus DMA_COMPLETION_STATUS_INITIAL = {
        .overflow = false,
        .unexpectedPutSize = false
};

static struct DmaCompletionStatus dmaCompletionStatus = DMA_COMPLETION_STATUS_INITIAL;

DEFINE_KFIFO(dmaCompletionFifo, struct DmaCompletionEntry, (1<<2));

DECLARE_WAIT_QUEUE_HEAD(dmaCompletionWaitQueue);

DECLARE_COMPLETION(dmaCompletionThreadDoneCompletion);

static struct task_struct* dmaCompletionThread = NULL;
//static bool dmaCompletionThreadCanceled = false;

typedef void (*TDmaCompletionRegisteredCallback)(unsigned channel, enum dma_status status);
static TDmaCompletionRegisteredCallback dmaCompletionRegisteredCallback = NULL;

static int dmaCompletionThreadFunction(void* data) {
    struct DmaCompletionEntry entry;
    int n;

    do {
//        pr_info("vor wait_event\n");
        wait_event(dmaCompletionWaitQueue, !kfifo_is_empty(&dmaCompletionFifo));

//        pr_info("vor kfifo_get\n");
        n = kfifo_get(&dmaCompletionFifo, &entry);
        ASSERT(n == 1);

        if (entry.canceled) {
            break;
        }

        if (dmaCompletionRegisteredCallback) {
            (*dmaCompletionRegisteredCallback)(entry.channel, entry.status);
        }

    } while (1);

    complete_all(&dmaCompletionThreadDoneCompletion);
    pr_info("dmaCompletionThreadFunction exiting\n");
    return 0;
}

void dmaCompletionFire(struct DmaCompletionEntry* e) {
    int n = kfifo_put(&dmaCompletionFifo, *e);

    if (n > 1 || n < 0) {
        dmaCompletionStatus.unexpectedPutSize = true;
        pr_err("unexpectedPutSize\n");
    } else if (n == 0) {
        dmaCompletionStatus.overflow = true;
        pr_err("overflow\n");
        ASSERT(false); // sonst haengt kernel eh
    }
    wake_up(&dmaCompletionWaitQueue);
//    pr_info("after wake_up in dmaCompletionFire\n");
}

void dmaCompletionFireCanceled(void) {
    struct DmaCompletionEntry e = {
            .canceled = true
    };

    dmaCompletionFire(&e);
}

void dmaCompletionFireCompletion(unsigned channel, enum dma_status status) {
    struct DmaCompletionEntry e = {
            .canceled = false,
            .channel = (uint8_t) channel,
            .status = status
    };

    dmaCompletionFire(&e);
}

int dmaCompletionThread_init(TDmaCompletionRegisteredCallback cb) {
    int res;

    dmaCompletionRegisteredCallback = cb;
    dmaCompletionStatus = DMA_COMPLETION_STATUS_INITIAL;
    dmaCompletionThread = kthread_create(dmaCompletionThreadFunction, NULL, "dmaCompletionThread");

    if (IS_ERR(dmaCompletionThread)) {
        return PTR_ERR(dmaCompletionThread);
    }
    {
        struct sched_param p;

#if 1 // original
        p.sched_priority = MAX_USER_RT_PRIO - 3;
#else // test
        p.sched_priority = 1;
#endif
        res = sched_setscheduler(dmaCompletionThread, SCHED_FIFO, &p);
    }

    wake_up_process(dmaCompletionThread);
    return 0;
}

void dmaCompletionThread_exit(void) {
    dmaCompletionFireCanceled();
    pr_info("vor wait_for_completion  in dmaCompletionThread_exit\n");
    wait_for_completion(&dmaCompletionThreadDoneCompletion);
    pr_info("after wait_for_completion in dmaCompletionThread_exit\n");
}

#endif /* FPGASERIAL_DMACOMPLETIONTHREAD_H_ */
