#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/errno.h>

#include <asm/ptrace.h>
#include <asm/signal.h>
#include <asm/io.h>
#include <asm/assert.h>

#include <libdev/etiContext.h>
#include <libdev/etiRegisters.h>
#include <libdev/etiInterrupts.h>
#include <libdev/etiInterruptDriver.h>

#include "context.h"

/* ----------------------------------------------------------- */

unsigned int local_bh_count      [NR_CPUS];
unsigned int local_irq_count     [NR_CPUS];

static intInterrupt_t     interrupts [NR_IRQS];
static struct irqaction*  actions    [NR_IRQS];




static void IRQ_handler( 
             ctxExceptionContext_t     ctx, 
             intInterrupt_t            interrupt, 
             Pointer                   data
       )
{
       int                   cpu = smp_processor_id();
       int                   irq = interrupt->globalIndex;
       struct irqaction*  action = actions[irq];

       kstat.irqs[cpu][irq]++;
       CHECK_STACK("Q");

       if ( (ctx->cl[0].cpse | ctx->cl[1].cpse) & 1 ) {
	  printk("DTLB problem (irq.c): cpse0= 0x%08x cpse1= 0x%08x \n",
                 ctx->cl[0].cpse, ctx->cl[1].cpse );
       }

       intEnable(interrupt, False);
            
       hardirq_enter(cpu);

       while (action) {
	   if ( (action->flags & SA_INTERRUPT) == 0 ) { __sti(); }

	   action->handler(irq , action->dev_id, (struct pt_regs*)ctx);

	   if (action->flags & SA_SAMPLE_RANDOM) { add_interrupt_randomness(irq); }
	   __cli();

           action= action->next;
       }

       hardirq_exit(cpu); 
       intEnable(interrupt, True);

       CHECK_STACK("q");

       RUN_BOTTOM; 
}


int request_irq(
		unsigned int  irq,
		void        (*handler)( int, void*, struct pt_regs* ),
		unsigned long irq_flags,
		const char*   dev_name,
		void*         dev_id
	)
{
    struct irqaction* action;
    unsigned long     flags;
    intInterrupt_t    interrupt;
    intSetup_t        setup;
    Bool              opened= False;


    if ( irq >= NR_IRQS || handler == NULL ) {
       return -EINVAL;
    }

    action = kmalloc( sizeof(struct irqaction), GFP_KERNEL );

    if ( action == NULL ) {
       return -ENOMEM;
    }

    save_and_cli(flags);
    {
	struct irqaction *old= actions[irq];

        action->handler  = handler;
        action->flags    = irq_flags;
        action->mask     = 0;
        action->name     = dev_name;
        action->dev_id   = dev_id;
        action->next     = old;

        if (old) {
	    if (!(old->flags & irq_flags & SA_SHIRQ)) {
	        /* Can't share interrupts unless both agree to      */
            } else 
	    if ((old->flags ^ irq_flags) & SA_INTERRUPT) {
	        /* Can't share interrupts unless both are same type */
            } else {
                actions[irq] = action; 
                opened       = True;
            }

        } else {
            if ( intOpen( irq, irq_flags&SA_SHIRQ ? intDisableable : 0, &interrupt ) == INT_OK) {
                setup.handler = IRQ_handler;
                setup.data    = Null;

                intSetup( interrupt, &setup );

                interrupts[irq]  = interrupt;
                actions[irq]     = action; 
                opened           = True;
            }
        }
    }
    restore_flags(flags);

    if (!opened)  {
        kfree( action );
        return -EBUSY;
    }

    if (irq_flags & SA_SAMPLE_RANDOM) {
        rand_initialize_irq(irq);
    }

    return 0;
}
 

void free_irq( unsigned int irq, void* dev_id )
{
    struct irqaction** action;
    unsigned long      flags;

    if ( irq >= NR_IRQS )
    {
       printk( KERN_ERR "Trying to free non-existent IRQ%d\n", irq );
       return;
    }

    save_and_cli(flags);
    {
        action= &actions[irq];

        while (*action) {
            struct irqaction* curaction= *action;

            if ( curaction->dev_id == dev_id ) {
               *action= curaction->next;
                kfree( curaction );
                break;
            }
        }

        if (actions[irq]==Null) {
            intClose( interrupts[irq] );
            interrupts[irq]= Null;
        }
    }
    restore_flags(flags);
}


void enable_irq(unsigned int irq)
{
    if ( interrupts[irq] != Null ) {
	intEnable( interrupts[irq], True );
    } else {
	printk( KERN_WARNING "Trying to enable non-requested IRQ%d\n", irq );
    }
}

void disable_irq(unsigned int irq)
{
    if ( interrupts[irq] != Null ) {
	intEnable( interrupts[irq], False );
    } else {
	printk( KERN_WARNING "Trying to disable non-requested IRQ%d\n", irq );
    }
}

void acknowledge_irq(unsigned int irq)
{
    if ( interrupts[irq] != Null ) {
	intAcknowledge( interrupts[irq] );
    } else {
	printk( KERN_WARNING "Trying to acknowledge non-requested IRQ%d\n", irq );
    }
}

/* ------------------------------------------------------------- */


void init_IRQ() {}


    static void traverseDriver( intdrvInterruptDriver_t driver, char **buf)
    {
        int len, irq;

        len= sprintf( *buf, "\n%s interrupts:\n", driver->name ); 
        *buf += len;

        while (len-- > 3) {
             *buf += sprintf( *buf, "=" );
        }

        *buf += sprintf( *buf, "\n" );

        for (irq= driver->irqBase; irq < (driver->irqBase + driver->irqAmount); irq++) {
            unsigned long flags;
            save_and_cli(flags);
            {
                struct irqaction* action= actions[irq];

                if (action) {
                   /*
                    * List Linux- installed irqs:
                    */
		   *buf += sprintf(*buf, "%2d: %8d %c %s",
			    irq, kstat.irqs[0][irq],
			    (action->flags & SA_INTERRUPT) ? '+' : ' ',
			    action->name);

                    action = action->next;

                    while (action) {
		        *buf += sprintf(*buf, ",%s %s",
				    (action->flags & SA_INTERRUPT) ? " +" : "",
				    action->name);
                        action = action->next;
                    }
		   *buf += sprintf(*buf, "\n");

                } else {
                   /*
                    * List Equator device irq that has been
                    * directly installed via the libdev API:
                    */
                    intInterrupt_t interrupt;

                    intOpen( irq, intRawDescriptor, &interrupt );

                    if (interrupt->isOpen) {
                       *buf += sprintf( *buf, "%2d: %8d   %s\n", irq, kstat.irqs[0][irq], interrupt->description );  
                    }
                }
            }
            restore_flags(flags);
        }
    }


int get_irq_list(char *buf)
{
    char *buf1= buf;

    intdrvTraverse( traverseDriver, &buf1);

    buf1 += sprintf( buf1, "\n" );

    return buf1 - buf;
}




/* ----------------------------------------------------------- */

static intInterrupt_t handles[NR_IRQS];
static int probed_irq;


    static void probe_handler(ctxExceptionContext_t ctx, intInterrupt_t interrupt, Pointer data)
    {
        probed_irq= interrupt->globalIndex;
        intEnable(interrupt,False);
    }


unsigned long probe_irq_on(void)
{
    int i;

    for (i=0; i<NR_IRQS; i++) {
        intInterrupt_t handle;

        if (  intOpen(i,intRawDescriptor,&handle) == INT_OK
           && !handle->isOpen
           ) {
            intSetup_t setup;
            setup.handler= probe_handler;
            setup.data   = Null;

            intOpen  (i,0,&handles[i]);
            intSetup (handles[i],&setup);
        }
    }

    probed_irq= 0;

    return -1;
}



int probe_irq_off (unsigned long irqs)
{
    int i;

    for (i=0; i<NR_IRQS; i++) {
        if ( handles[i] ) {
            intClose(handles[i]);
            handles[i]=Null;
        }
    }

    return probed_irq;
}






int irq_cannonicalize( UInt irq )
{
   String genericName= Null;

   if (irq == IRQ_SERIAL0) { genericName= "serial0"; } else
   if (irq == IRQ_SERIAL1) { genericName= "serial1"; }

   if (genericName) {
        intInterrupt_t handle;
        if (  intOpenByName(genericName,intRawDescriptor,&handle) == INT_OK ) {
            irq= handle->globalIndex;
        } else {
            irq= 0;
        }
   }

   return irq;
}
