#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/capi.h>
#include <linux/kernelcapi.h>
#include <uapi/linux/kernelcapi.h>
#include "capilib_compat.h"

struct capilib_msgidqueue {
    struct capilib_msgidqueue *next;
    u16 msgid;
};

struct capilib_ncci {
    struct list_head list;
    u16 applid;
    u32 ncci;
    u32 winsize;
    int nmsg;

    struct capilib_msgidqueue *msgidqueue;
    struct capilib_msgidqueue *msgidlast;
    struct capilib_msgidqueue *msgidfree;

    struct capilib_msgidqueue msgidpool[CAPI_MAXDATAWINDOW];
};

static void mq_init(struct capilib_ncci *np)
{
    unsigned int i;

    np->msgidqueue = NULL;
    np->msgidlast = NULL;
    np->nmsg = 0;

    memset(np->msgidpool, 0, sizeof(np->msgidpool));

    np->msgidfree = &np->msgidpool[0];

    for (i = 1; i < np->winsize; i++) {
	np->msgidpool[i].next = np->msgidfree;
	np->msgidfree = &np->msgidpool[i];
    }
}

static int mq_enqueue(struct capilib_ncci *np, u16 msgid)
{
    struct capilib_msgidqueue *mq;

    mq = np->msgidfree;
    if (!mq)
	return 0;

    np->msgidfree = mq->next;

    mq->msgid = msgid;
    mq->next = NULL;

    if (np->msgidlast)
	np->msgidlast->next = mq;

    np->msgidlast = mq;

    if (!np->msgidqueue)
	np->msgidqueue = mq;

    np->nmsg++;

    return 1;
}

static int mq_dequeue(struct capilib_ncci *np, u16 msgid)
{
    struct capilib_msgidqueue **pp;

    for (pp = &np->msgidqueue; *pp; pp = &(*pp)->next) {
	if ((*pp)->msgid == msgid) {
	    struct capilib_msgidqueue *mq = *pp;

	    *pp = mq->next;

	    if (mq == np->msgidlast)
		np->msgidlast = NULL;

	    mq->next = np->msgidfree;
	    np->msgidfree = mq;

	    np->nmsg--;

	    return 1;
	}
    }

    return 0;
}

void capilib_new_ncci(struct list_head *head, u16 applid,
	      u32 ncci, u32 winsize)
{
    struct capilib_ncci *np;

    np = kmalloc(sizeof(*np), GFP_ATOMIC);
    if (!np) {
	pr_warn("fcusb2: capilib_new_ncci: no memory\n");
	return;
    }

    if (winsize > CAPI_MAXDATAWINDOW) {
	pr_err("fcusb2: NCCI window %u too large, limiting to %u\n",
	       winsize, CAPI_MAXDATAWINDOW);
	winsize = CAPI_MAXDATAWINDOW;
    }

    /*
     * A zero-sized window would leave msgidfree pointing at an
     * unusable pool entry. Treat it as a minimum window of one.
     */
    if (winsize == 0)
	winsize = 1;

    np->applid = applid;
    np->ncci = ncci;
    np->winsize = winsize;

    mq_init(np);

    list_add_tail(&np->list, head);
}

void capilib_free_ncci(struct list_head *head, u16 applid, u32 ncci)
{
    struct capilib_ncci *np;
    struct capilib_ncci *tmp;

    list_for_each_entry_safe(np, tmp, head, list) {
	if (np->applid != applid)
	    continue;

	if (np->ncci != ncci)
	    continue;

	list_del(&np->list);
	kfree(np);
	return;
    }

    pr_err("fcusb2: capilib_free_ncci: ncci 0x%x not found\n", ncci);
}

void capilib_release_appl(struct list_head *head, u16 applid)
{
    struct capilib_ncci *np;
    struct capilib_ncci *tmp;

    list_for_each_entry_safe(np, tmp, head, list) {
	if (np->applid != applid)
	    continue;

	list_del(&np->list);
	kfree(np);
    }
}

void capilib_release(struct list_head *head)
{
    struct capilib_ncci *np;
    struct capilib_ncci *tmp;

    list_for_each_entry_safe(np, tmp, head, list) {
	list_del(&np->list);
	kfree(np);
    }
}

u16 capilib_data_b3_req(struct list_head *head, u16 applid,
	    u32 ncci, u16 msgid)
{
    struct capilib_ncci *np;

    list_for_each_entry(np, head, list) {
	if (np->applid != applid)
	    continue;

	if (np->ncci != ncci)
	    continue;

	if (!mq_enqueue(np, msgid))
	    return CAPI_SENDQUEUEFULL;

	return CAPI_NOERROR;
    }

    pr_err("fcusb2: capilib_data_b3_req: ncci 0x%x not found\n", ncci);

    return CAPI_NOERROR;
}

void capilib_data_b3_conf(struct list_head *head, u16 applid,
	      u32 ncci, u16 msgid)
{
    struct capilib_ncci *np;

    list_for_each_entry(np, head, list) {
	if (np->applid != applid)
	    continue;

	if (np->ncci != ncci)
	    continue;

	if (!mq_dequeue(np, msgid))
	    pr_err("fcusb2: msgid %hu ncci 0x%x not on queue\n",
	           msgid, ncci);

	return;
    }

    pr_err("fcusb2: capilib_data_b3_conf: ncci 0x%x not found\n", ncci);
}
