summaryrefslogblamecommitdiffstats
path: root/c/src/lib/libbsp/sparc/shared/irq/genirq.c
blob: 57654b38bf0ae337cfa7135414a34698d8e16902 (plain) (tree)
































































































































































                                                                                                





                                 














































































                                                                           
/*
 * Generic interrupt helpers mainly for GRLIB PCI peripherals
 * 
 * COPYRIGHT (c) 2008.
 * Cobham Gaisler AB.
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.com/license/LICENSE.
 */

#include <rtems.h>
#include <stdlib.h>
#include <string.h>
#include <genirq.h>

struct genirq_handler_entry {
	struct genirq_handler_entry	*next;		/* Next ISR entry for this IRQ number */
	genirq_handler			isr;		/* ISR function called upon IRQ */
	void				*arg;		/* custom argument to ISR */
	int				enabled;	/* Inidicates if IRQ is enabled */
};

struct genirq_irq_entry {
	struct genirq_handler_entry	*head;
	struct genirq_stats		stats;
};

struct genirq_priv {
	/* Maximum number of interrupt */
	int				genirq_max;
	/* IRQ Table index N reflect IRQ number N */
	struct genirq_irq_entry		genirq_table[1]; /* Length depends on */
};

genirq_t genirq_init(int number_of_irqs)
{
	int size;
	struct genirq_priv *priv;

	size = sizeof(int) +
	       number_of_irqs * sizeof(struct genirq_irq_entry);

	priv = (struct genirq_priv *)malloc(size);
	if ( !priv )
		return NULL;
	memset(priv, 0, size);
	priv->genirq_max = number_of_irqs - 1;
	return priv;
}

void genirq_destroy(genirq_t d)
{
	struct genirq_priv *priv = d;
	struct genirq_irq_entry *irqentry;
	struct genirq_handler_entry *isrentry, *tmp;
	int i;

	/* Free all registered interrupts */
	for ( i=0; i<priv->genirq_max; i++) {
		irqentry = &priv->genirq_table[i];
		isrentry = irqentry->head;
		while ( isrentry ) {
			tmp = isrentry;
			isrentry = isrentry->next;
			free(tmp);
		}
	}

	free(priv);
}

int genirq_check(genirq_t d, int irq)
{
	struct genirq_priv *priv = d;

	if ( (irq <= 0) || (irq > priv->genirq_max) )
		return -1;
	else
		return 0;
}

int genirq_register(genirq_t d, int irq, genirq_handler isr, void *arg)
{
	struct genirq_priv *priv = d;
	struct genirq_irq_entry *irqentry;
	struct genirq_handler_entry *isrentry, *newentry;
	rtems_interrupt_level level;
	
	if ( genirq_check(d, irq) )
		return -1;

	newentry = malloc(sizeof(*newentry));
	if ( !newentry )
		return -1;

	/* Initialize ISR entry */
	newentry->isr     = isr;
	newentry->arg     = arg;
	newentry->enabled = 0;

	rtems_interrupt_disable(level);

	/* Insert new ISR entry first into table */
	irqentry = &priv->genirq_table[irq];
	isrentry = irqentry->head;
	irqentry->head = newentry;
	newentry->next = isrentry;

	rtems_interrupt_enable(level);

	if ( isrentry )
		return 1; /* This is the first handler on this IRQ */
	return 0;
}

int genirq_unregister(genirq_t d, int irq, genirq_handler isr, void *arg)
{
	struct genirq_priv *priv = d;
	struct genirq_irq_entry *irqentry;
	struct genirq_handler_entry *isrentry, **prev;
	rtems_interrupt_level level;
	int ret;

	if ( genirq_check(d, irq) )
		return -1;

	/* Remove isr[arg] from ISR list */
	irqentry = &priv->genirq_table[irq];
	ret = -1;

	rtems_interrupt_disable(level);

	prev = &irqentry->head;
	isrentry = irqentry->head;
	while ( isrentry ) {
		if ( (isrentry->arg == arg) && (isrentry->isr == isr) ) {
			/* Found ISR, remove it from list */
			if ( isrentry->enabled ) {
				/* Can not remove enabled ISRs, disable first */
				ret = 1;
				break;
			}
			*prev = isrentry->next;
			ret = 0;
			break;
		}
		prev = &isrentry->next;
		isrentry = isrentry->next;
	}

	rtems_interrupt_enable(level);

	return ret;
}

/* Enables or Disables ISR handler. Internal function to reduce footprint
 * of enable/disable functions.
 *
 * \param action 1=enable, 0=disable ISR
 */
static int genirq_set_active(
	struct genirq_priv *priv,
	int irq,
	genirq_handler isr,
	void *arg,
	int action)
{
	struct genirq_irq_entry *irqentry;
	struct genirq_handler_entry *isrentry, *e = NULL;
	int enabled;

	if ( genirq_check(priv, irq) )
		return -1;

	/* Find isr[arg] in ISR list */
	irqentry = &priv->genirq_table[irq];
	enabled = 0;

	isrentry = irqentry->head;
	while ( isrentry ) {
		if ( (isrentry->arg == arg) && (isrentry->isr == isr) ) {
			/* Found ISR */
			if ( isrentry->enabled == action ) {
				/* The ISR is already enabled or disabled 
				 * depending on request, neccessary actions
				 * were taken last time the same action was
				 * requested.
				 */
				return 1;
			}
			e = isrentry;
		} else {
			enabled += isrentry->enabled;
		}
		isrentry = isrentry->next;
	}

	if ( !e )
		return -1;

	e->enabled = action;

	return enabled;
}

int genirq_enable(genirq_t d, int irq, genirq_handler isr, void *arg)
{
	struct genirq_priv *priv = d;
	return genirq_set_active(priv, irq, isr, arg, 1);
}

int genirq_disable(genirq_t d, int irq, genirq_handler isr, void *arg)
{
	struct genirq_priv *priv = d;
	return genirq_set_active(priv, irq, isr, arg, 0);
}

void genirq_doirq(genirq_t d, int irq)
{
	struct genirq_priv *priv = d;
	struct genirq_irq_entry *irqentry;
	struct genirq_handler_entry *isrentry;
	int enabled;

	irqentry = &priv->genirq_table[irq];
	irqentry->stats.irq_cnt++;
	
	enabled = 0;

	isrentry = irqentry->head;
	while ( isrentry ) {
		if ( isrentry->enabled ) {
			enabled = 1;
			/* Call the ISR */
			isrentry->isr(isrentry->arg);
		}
		isrentry = isrentry->next;
	}

	/* Was the IRQ an IRQ without source? */
	if ( enabled == 0 ) {
		/* This should not happen */
		printk("Spurious IRQ happened on IRQ %d\n", irq);
	}
}