/*
 * dev_icu.c - ICU (Interrupt Controler Unit) generic device API implementation.
 *
 * Authors   Alain Greiner  (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <hal_types.h>
#include <hal_special.h>
#include <device.h>
#include <thread.h>
#include <cluster.h>
#include <printk.h>
#include <memcpy.h>
#include <spinlock.h>
#include <soclib_xcu.h>
#include <dev_icu.h>

/////////////////////////////////////////////////////////////////////////////////////////
// Extern global variables
/////////////////////////////////////////////////////////////////////////////////////////

extern devices_directory_t  devices_dir;         // allocated in kernel_init.c

extern devices_input_irq_t  devices_input_irq;   // allocated in kernel_init.c


///////////////////////////////////
void dev_icu_init( xptr_t   dev_xp,
                   uint32_t hwi_nr,
                   uint32_t wti_nr,
                   uint32_t pti_nr )
{
    // get ICU device cluster and local pointer
    cxy_t      dev_cxy = GET_CXY( dev_xp );
    device_t * dev_ptr = (device_t *)GET_PTR( dev_xp );

    if( dev_cxy != local_cxy )
    {
        printk("\n[PANIC] %s for cluster %x must be local\n",
               __FUNCTION__ , local_cxy );
        hal_core_sleep();
    }

    // set ICU device extension fields
    dev_ptr->ext.icu.hwi_nr     = hwi_nr;
    dev_ptr->ext.icu.wti_nr     = wti_nr;
    dev_ptr->ext.icu.pti_nr     = pti_nr;
    dev_ptr->ext.icu.wti_bitmap = 0;
    spinlock_init( &dev_ptr->ext.icu.wti_lock );
   
    // get implementation index from ICU device descriptor
    uint32_t impl = dev_ptr->impl;

    // set field "name" in device descriptor
    // and call the relevant driver init function
    if( impl == IMPL_ICU_XCU )
    {
        memcpy( dev_ptr->name , "ICU_XCU" , 16 );

        uint32_t     lid;
        cluster_t  * cluster = LOCAL_CLUSTER;
        for( lid = 0 ; lid < cluster->cores_nr ; lid++ )
        {
            soclib_xcu_init( dev_ptr , lid );
        }
    }
    else
    {
        printk("\n[PANIC] %s : undefined ICU implementation for cluste %x\n",
               __FUNCTION__ , local_cxy );
        hal_core_sleep();
    }
} // end dev_icu_init()

/////////////////////////////////////////////////////////////////////////////////////
// This static function check the irq_type / irq_index arguments for a remote ICU.
// It is called by the dev_icu_enable_irq() & dev_icu_disable_irq() functions.
// returns 0 if OK / returns non zero if invalid arguments
/////////////////////////////////////////////////////////////////////////////////////
static inline void dev_icu_check_irq( xptr_t     icu_xp,
                                      uint32_t   irq_type,
                                      uint32_t   irq_index )
{
    // get cluster and local pointer on remote ICU
    cxy_t      icu_cxy = GET_CXY( icu_xp );
    device_t * icu_ptr = (device_t *)GET_PTR( icu_xp );

    if( irq_type == HWI_TYPE ) 
    {
        if( irq_index >= hal_remote_lw( XPTR( icu_cxy , &icu_ptr->ext.icu.hwi_nr ) ) )
        {
            printk("\n[PANIC] in %s : illegal HWI index = %d for ICU in cluster %x\n",
                   __FUNCTION__ , irq_index , icu_cxy );
            hal_core_sleep();
        }
    }
    if( irq_type == WTI_TYPE )
    {
        if( irq_index >= hal_remote_lw( XPTR( icu_cxy , &icu_ptr->ext.icu.wti_nr ) ) )
        {
            printk("\n[PANIC] in %s : illegal WTI index = %d for ICU in cluster %x\n",
                   __FUNCTION__ , irq_index , icu_cxy );
            hal_core_sleep();
        }
    }
    if( irq_type == PTI_TYPE )
    {
        if( irq_index >= hal_remote_lw( XPTR( icu_cxy , &icu_ptr->ext.icu.pti_nr ) ) )
        {
            printk("\n[PANIC] in %s : illegal PTI index = %d for ICU in cluster %x\n",
                   __FUNCTION__ , irq_index , icu_cxy );
            hal_core_sleep();
        }
    }
}  // end dev_icu check irq()

////////////////////////////////////////////
void dev_icu_enable_irq( cxy_t      icu_cxy,
                         lid_t      lid,
                         uint32_t   irq_type,
                         uint32_t   irq_index,
                         xptr_t     src_dev_xp )
{
    // get extended pointer & local pointer on remote ICU device
    xptr_t     icu_xp  = devices_dir.icu[icu_cxy];
    device_t * icu_ptr = (device_t *)GET_PTR( icu_xp );

    // check IRQ type and index
    dev_icu_check_irq( icu_xp , irq_type , irq_index );

    // (1) call implementation specific ICU driver to enable IRQ
    if( icu_ptr->impl == IMPL_ICU_XCU ) 
    {
        soclib_xcu_enable_irq( icu_xp , 1<<irq_index , irq_type , lid );
    }
    else
    {
        printk("\n[PANIC] in %s : undefined ICU implementation" , __FUNCTION__ );
        hal_core_sleep();
    }

    // (2) get selected remote core local pointer, and register
    // source device xptr in relevant interrupt vector

    core_t * core_ptr = hal_remote_lpt( XPTR( icu_cxy , &LOCAL_CLUSTER->core_tbl[lid] ) );
    xptr_t   core_xp  = XPTR( icu_cxy , core_ptr );
	core_set_irq_vector_entry( core_xp , irq_type , irq_index , src_dev_xp );

    // (3) get extended pointer on source device, and 
    // register IRQ type and index in source device descriptor

    cxy_t      src_dev_cxy = GET_CXY( src_dev_xp );
    device_t * src_dev_ptr = (device_t *)GET_PTR( src_dev_xp );

    hal_remote_sw( XPTR( src_dev_cxy , &src_dev_ptr->irq_type ) , irq_type );
    hal_remote_sw( XPTR( src_dev_cxy , &src_dev_ptr->irq_id   ) , irq_index );

}  // end dev_icu_enable_irq()

/////////////////////////////////////////////
void dev_icu_disable_irq( cxy_t      icu_cxy,
                          lid_t      lid,
                          uint32_t   irq_type,
                          uint32_t   irq_index )
{
    // get extended pointer & local pointer on remote ICU device
    xptr_t  icu_xp = devices_dir.icu[icu_cxy];
    device_t * icu_ptr = (device_t *)GET_PTR( icu_xp );

    // check IRQ type and index
    dev_icu_check_irq( icu_xp , irq_type , irq_index );

    // (1) call the implementation specific ICU driver to disable IRQ 
    if( icu_ptr->impl == IMPL_ICU_XCU ) 
    {
        soclib_xcu_disable_irq( icu_xp , 1<<irq_index , irq_type , lid );
    }
    else
    {
        printk("\n[PANIC] in %s : undefined ICU implementation" , __FUNCTION__ );
        hal_core_sleep();
    }

    // (2) get selected remote core local pointer, and remove
    // the source device xptr from relevant interrupt vector

    core_t * core_ptr = hal_remote_lpt( XPTR( icu_cxy , &LOCAL_CLUSTER->core_tbl[lid] ) );
    xptr_t   core_xp  = XPTR( icu_cxy , core_ptr );
	core_set_irq_vector_entry( core_xp , irq_type , irq_index , XPTR_NULL );

} // end dev_icu_disable_irq()

//////////////////////////////////////////////
void dev_icu_set_period( uint32_t   pti_index,
                         uint32_t   period )
{
    // get pointer on local ICU device descriptor
    core_t   * core = CURRENT_CORE;
    device_t * icu  = core->icu;

    // check PTI index
    if( pti_index >= icu->ext.icu.pti_nr ) 
    {
        printk("\n[PANIC] in %s : illegal PTI index = %d\n", __FUNCTION__ , pti_index );
        hal_core_sleep();
    }

    // call the implementation specific driver ICU to set period 
    if( icu->impl == IMPL_ICU_XCU ) 
    {
        soclib_xcu_set_period( icu , pti_index , period );
    }
}  // end dev_icu_set_period()

////////////////////////////////////////////
void dev_icu_ack_timer( uint32_t pti_index )
{
    // get pointer on local ICU device descriptor
    core_t   * core = CURRENT_CORE;
    device_t * icu  = core->icu;

    // check PTI index
    if( pti_index >= icu->ext.icu.pti_nr ) 
    {
        printk("\n[PANIC] in %s : illegal PTI index = %d\n", __FUNCTION__ , pti_index );
        hal_core_sleep();
    }

    // call the implementation specific driver ICU to acknowledge PTI IRQ
    if( icu->impl == IMPL_ICU_XCU ) 
    {
        soclib_xcu_ack_timer( icu , pti_index );
    }
    else
    {
        printk("\n[PANIC] in %s : undefined ICU implementation" , __FUNCTION__ );
        hal_core_sleep();
    }

}  // end dev_icu_ack_timer()

////////////////////////////////////
void dev_icu_send_ipi( cxy_t    cxy,
                       lid_t    lid )
{
    // check arguments 
    cluster_t * cluster  = LOCAL_CLUSTER;
    uint32_t    y_width  = cluster->y_width;
    uint32_t    x_size   = cluster->x_size;
    uint32_t    y_size   = cluster->y_size;
    uint32_t    cores_nr = cluster->cores_nr;
    uint32_t    x = cxy >> y_width;
    uint32_t    y = cxy & ((1<<y_width)-1);

    if( (x >= x_size) || (y >= y_size) )
    {
        hal_core_sleep("%s : illegal cluster identifier = %x\n", __FUNCTION__ , cxy );
    } 
    if( lid >= cores_nr )
    {
        hal_core_sleep("%s : illegal core local index = %d\n", __FUNCTION__ , lid );
    } 

    // get extended pointer on target ICU device
    xptr_t icu_xp = devices_dir.icu[cxy];

     // get target ICU cluster and local pointer
    cxy_t      cxy_icu = GET_CXY( icu_xp );
    device_t * ptr_icu = (device_t *)GET_PTR( icu_xp );

    // get driver implementation from target ICU device
    uint32_t impl = hal_remote_lw( XPTR( cxy_icu , &ptr_icu->impl ) );   

    // call the implementation specific ICU driver to send IPI
    if( impl == IMPL_ICU_XCU ) 
    {
        soclib_xcu_send_ipi( icu_xp , lid );
    }
    else
    {
        printk("\n[PANIC] in %s : undefined ICU implementation" , __FUNCTION__ );
        hal_core_sleep();
    }
}  // end dev_icu_send_ipi()

//////////////////////////
void dev_icu_irq_handler()
{
    uint32_t   hwi_status;   // HWI index + 1  / no pending HWI if 0
    uint32_t   wti_status;   // WTI index + 1  / no pending WTI if 0
    uint32_t   pti_status;   // PTI index + 1  / no pending PTI if 0
    xptr_t     src_dev_xp;   // extended pointer on source device descriptor
    cxy_t      src_dev_cxy;  // source device cluster
    device_t * src_dev_ptr;  // source device local pointer
    uint32_t   index;        // IRQ index

    // get pointer on local ICU device descriptor
    core_t   * core = CURRENT_CORE;
    device_t * icu  = core->icu;

    // call the implementation specific ICU driver  
    // to return highest priority pending IRQ of each type
    if( icu->impl == IMPL_ICU_XCU )
    {
        soclib_xcu_get_status( icu , core->lid , &hwi_status , &wti_status , &pti_status );
    }
    else
    {
        printk("\n[PANIC] in %s : undefined ICU implementation" , __FUNCTION__ );
        hal_core_sleep();
    }

    // analyse ICU status and handle up to 3 pending IRQ (one WTI, one HWI, one PTI)

    if( wti_status )          // pending WTI TODO what about IPIs ???  [AG]
	{
        index = wti_status - 1;

        // get extended pointer on IRQ source device
		src_dev_xp  = core->wti_vector[index];
        src_dev_cxy = GET_CXY( src_dev_xp );
        src_dev_ptr = (device_t *)GET_PTR( src_dev_xp );

		if( src_dev_xp == XPTR_NULL )        // strange, but not fatal => disable IRQ
		{
            printk("\n[WARNING] in %s : no handler for WTI %d on core %d in cluster %x\n",
                   __FUNCTION__ , index , core->lid , local_cxy );
	        core->spurious_irqs ++;
            dev_icu_disable_irq( local_cxy , core->lid , WTI_TYPE , index ); 
		}
        else if( src_dev_cxy != local_cxy )  // WTI must be handled in device cluster
        {
            printk("\n[PANIC] in %s : non local WTI %d on core %d in cluster %x\n",
                   __FUNCTION__ , index , core->lid , local_cxy );
            hal_core_sleep();
        }
        else                                 // call relevant ISR
        {
		    icu_dmsg("\n[INFO] %s received WTI : index = %d for cpu %d in cluster %d\n",
                     __FUNCTION__ , index , core->lid , local_cxy );

	        src_dev_ptr->isr( src_dev_ptr );
        }
	}

	if( hwi_status )      // pending HWI
	{
        index = hwi_status - 1;

        // get pointer on IRQ source device
		src_dev_xp  = core->wti_vector[index];
        src_dev_cxy = GET_CXY( src_dev_xp );
        src_dev_ptr = (device_t *)GET_PTR( src_dev_xp );

		if( src_dev_xp == XPTR_NULL )        // strange, but not fatal => disable IRQ
		{
            printk("\n[WARNING] in %s : no handler for HWI %d on core %d in cluster %x\n",
                   __FUNCTION__ , index , core->lid , local_cxy );
	        core->spurious_irqs ++;
            dev_icu_disable_irq( local_cxy , core->lid , HWI_TYPE , index ); 
		}
        else if( src_dev_cxy != local_cxy )  // HWI must be handled in device cluster
        {
            printk("\n[PANIC] in %s : non local HWI %d on core %d in cluster %x\n",
                   __FUNCTION__ , index , core->lid , local_cxy );
            hal_core_sleep();
        }
        else                    // call relevant ISR
        {
		    icu_dmsg("\n[INFO] %s received HWI : index = %d for cpu %d in cluster %d\n",
                     __FUNCTION__ , index , core->lid , local_cxy );

		    src_dev_ptr->isr( src_dev_ptr );
        }
	}

    if( pti_status )      // pending PTI
	{
        index = pti_status - 1;

		icu_dmsg("\n[INFO] %s received PTI : index = %d for cpu %d in cluster %d\n",
                 __FUNCTION__ , index , core->lid , local_cxy );

        // acknowledge PTI
        dev_icu_ack_timer( index );

        // execute all actions related to TICK event
        core_clock( core );
	}
}  // end dev_icu_irq_handler()

////////////////////////////
uint32_t dev_icu_wti_alloc()
{
    // get pointer on local ICU device descriptor
    core_t   * core = CURRENT_CORE;
    device_t * icu  = core->icu;

    // get bitmap pointer, lock, and size
    bitmap_t   * bitmap = &icu->ext.icu.wti_bitmap;
    spinlock_t * lock   = &icu->ext.icu.wti_lock;
    uint32_t     size   =  icu->ext.icu.wti_nr;

    // get lock protecting WTI allocator
    spinlock_lock( lock );

    // get first free mailbox index
    uint32_t index = (uint32_t)bitmap_ffc( bitmap , size );
  
    // set bitmap entry if found
    if( index < size ) bitmap_set( bitmap , index );

    // release lock
    spinlock_unlock( lock );

    return index;
}  // end dev_icu_wti_alloc()

//////////////////////////////////////////
void dev_icu_wti_release( uint32_t index )
{
    // get pointer on local ICU device descriptor
    core_t   * core = CURRENT_CORE;
    device_t * icu  = core->icu;

    // get bitmap pointer, lock, and size
    bitmap_t   * bitmap = &icu->ext.icu.wti_bitmap;
    spinlock_t * lock   = &icu->ext.icu.wti_lock;
    uint32_t     size   =  icu->ext.icu.wti_nr;

    // check index
    if( index >= size )
    {
        printk("\n[PANIC] in %s : illegal WTI index = %d on core %d in cluster %x\n",
               __FUNCTION__ , index , core->lid , local_cxy );
        hal_core_sleep();
    }

    // get lock protecting WTI allocator
    spinlock_lock( lock );

    // clear bitmap entry 
    bitmap_clear( bitmap , index );

    // release lock
    spinlock_unlock( lock );
}  // end dev_icu_wti_release()

///////////////////////////////////////
xptr_t dev_icu_wti_xptr( cxy_t     cxy,
                         uint32_t  wti_id )
{
    uint32_t * ptr = NULL;    // local pointer on mailbox
    
    // get pointer on local ICU device descriptor
    core_t   * core = CURRENT_CORE;
    device_t * icu  = core->icu;

    // call implementation specific ICU driver
    if( icu->impl == IMPL_ICU_XCU )
    {
        ptr = soclib_xcu_wti_ptr( icu , wti_id );   
    }
    else
    {
        printk("\n[PANIC] in %s : undefined ICU implementation" , __FUNCTION__ );
        hal_core_sleep();
    }

    // return extended pointer on mailbox
    return XPTR( cxy , ptr );

}   // end dev_icu_wti_xptr()


