/*
 * \brief   Hardware-interrupt support
 * \author  Christian Helmuth <ch12@os.inf.tu-dresden.de>
 * \date    2007-02-12
 *
 *
 *
 * XXX Consider support for IRQ_HANDLED and friends (linux/irqreturn.h)
 */

/* Linux */
#include <linux/interrupt.h>
#include <linux/string.h>     /* memset() */

/* local */
#include "dde26.h"
#include "local.h"

/* dummy */
irq_cpustat_t irq_stat[CONFIG_NR_CPUS];

/**
 * IRQ handling data
 */
static struct dde_irq
{
	unsigned              irq;     /* IRQ number */
	unsigned              count;   /* usage count */
	int                   shared;  /* shared IRQ */
	struct ddekit_thread *thread;  /* DDEKit interrupt thread */
	struct irqaction     *action;  /* Linux IRQ action */

	struct dde_irq       *next;    /* next DDE IRQ */
} *used_irqs;


static void irq_thread_init(void *p) {
	l4dde26_process_add_worker(); }


extern ddekit_sem_t *dde_softirq_sem;
static void irq_handler(void *arg)
{
	struct dde_irq *irq = arg;
	struct irqaction *action;

#if 0
	DEBUG_MSG("irq 0x%x", irq->irq);
#endif
	/* interrupt occurred - call all handlers */
	for (action = irq->action; action; action = action->next) {
		irqreturn_t r = action->handler(action->irq, action->dev_id);
#if 0
		DEBUG_MSG("return: %s", r == IRQ_HANDLED ? "IRQ_HANDLED" : r == IRQ_NONE ? "IRQ_NONE" : "??");
#endif
	}

	/* upon return we check for pending soft irqs */
	if (local_softirq_pending())
		ddekit_sem_up(dde_softirq_sem);
}


/*****************************
 ** IRQ handler bookkeeping **
 *****************************/

/**
 * Claim IRQ
 *
 * \return usage counter or negative error code
 *
 * FIXME list locking
 * FIXME are there more races?
 */
static int claim_irq(struct irqaction *action)
{
	int shared = action->flags & IRQF_SHARED ? 1 : 0;
	struct dde_irq *irq;

	/* check if IRQ already used */
	for (irq = used_irqs; irq; irq = irq->next)
		if (irq->irq == action->irq) break;

	/* we have to setup IRQ handling */
	if (!irq) {
		/* allocate and initalize new descriptor */
		irq = ddekit_simple_malloc(sizeof(*irq));
		if (!irq) return -ENOMEM;
		memset(irq, 0, sizeof(*irq));

		irq->irq    = action->irq;
		irq->shared = shared;
		irq->next   = used_irqs;
		used_irqs   = irq;

		/* attach to interrupt */
		irq->thread = ddekit_interrupt_attach(irq->irq,
		                                      irq->shared,
		                                      irq_thread_init,
		                                      irq_handler,
		                                      (void *)irq);
		if (!irq->thread) {
			used_irqs = irq->next;
			ddekit_simple_free(irq);
			return -EBUSY;
		}
	}

	/* does desciptor allow our new handler? */
	if ((!irq->shared || !shared) && irq->action) return -EBUSY;

	/* add handler */
	irq->count++;
	action->next = irq->action;
	irq->action = action;

	return irq->count;
}


/**
 * Free previously claimed IRQ
 *
 * \return usage counter or negative error code
 */
static struct irqaction *release_irq(unsigned irq_num, void *dev_id)
{
	struct dde_irq *prev_irq, *irq;

	/* check if IRQ already used */
	for (prev_irq = 0, irq = used_irqs; irq;
	     prev_irq = irq, irq = irq->next)
		if (irq->irq == irq_num) break;

	if (!irq) return 0;

	struct irqaction *prev_action, *action;

	for (prev_action = 0, action = irq->action; action;
	     prev_action = action, action = action->next)
		if (action->dev_id == dev_id) break;

	if (!action) return 0;

	/* dequeue action from irq */
	if (prev_action)
		prev_action->next = action->next;
	else
		irq->action = action->next;

	/* dequeue irq from used_irqs list and free structure,
	   if no more actions available */
	if (!irq->action) {
		if (prev_irq)
			prev_irq->next = irq->next;
		else
			used_irqs = irq->next;

		/* detach from interrupt */
		ddekit_interrupt_detach(irq->irq);

		ddekit_simple_free(irq);
	}

  return action;
}


/***************
 ** Linux API **
 ***************/

/**
 * Request interrupt
 *
 * \param  irq       interrupt number
 * \param  handler   interrupt handler -> top half
 * \param  flags     interrupt handling flags (SA_SHIRQ, ...)
 * \param  dev_name  device name
 * \param  dev_id    cookie passed back to handler
 *
 * \return 0 on success; error code otherwise
 *
 * \todo FIXME consider locking!
 */
int request_irq(unsigned int irq, irq_handler_t handler,
                unsigned long flags, const char *dev_name, void *dev_id)
{
  if (!handler) return -EINVAL;

  /* facilitate Linux irqaction for this handler */
  struct irqaction *irq_action = ddekit_simple_malloc(sizeof(*irq_action));
  if (!irq_action) return -ENOMEM;
  memset(irq_action, 0, sizeof(*irq_action));

  irq_action->handler = handler;
  irq_action->flags   = flags;
  irq_action->name    = dev_name;
  irq_action->dev_id  = dev_id;
  irq_action->irq     = irq;

  /* attach to IRQ */
  int err = claim_irq(irq_action);
  if (err < 0) return err;

  return 0;
}

/** Release Interrupt
 * \ingroup mod_irq
 *
 * \param  irq     interrupt number
 * \param  dev_id  cookie passed back to handler
 *
 */
void free_irq(unsigned int irq, void *dev_id)
{
	struct irqaction *irq_action = release_irq(irq, dev_id);

	if (irq_action)
		ddekit_simple_free(irq_action);
}

void disable_irq(unsigned int irq)
{
	ddekit_interrupt_disable(irq);
}

void disable_irq_nosync(unsigned int irq)
{
	/*
	 * Note:
	 * In contrast to the _nosync semantics, DDEKit's
	 * disable definitely waits until a currently executed
	 * IRQ handler terminates.
	 */
	ddekit_interrupt_disable(irq);
}

void enable_irq(unsigned int irq)
{
	ddekit_interrupt_enable(irq);
}
