/*
 * dev_txt.c - TXT (Text Terminal) generic device API implementation.
 * 
 * Author  Alain Greiner    (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MK
 *
 * 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-kernel; 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 <hal_remote.h>
#include <soclib_tty.h>
#include <thread.h>
#include <rpc.h>
#include <printk.h>
#include <dev_txt.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_txt_init( xptr_t  dev_xp )
{
    // get device descriptor cluster and local pointer
    cxy_t      dev_cxy = GET_CXY( dev_xp );
    device_t * dev_ptr = (device_t *)GET_PTR( dev_xp );

    // get implementation index from device descriptor
    uint32_t  impl = hal_remote_lw( XPTR( dev_cxy , &dev_ptr->impl ) );

    // get channel index from device descriptor
    // uint32_t  channel = hal_remote_lw( XPTR( dev_cxy , &dev_ptr->channel ) );

    // set fields "cmd", "isr", and "name" in device descriptor
    // and call implementation specific init function

    // TODO replace fixed "TXT_TTY" name by a channel indexed "TXT_TTY%d" name
    // as soon as the sprintk() function is available

    if( impl == IMPL_TXT_TTY )
    {
        hal_remote_spt( XPTR( dev_cxy , &dev_ptr->cmd ) , &soclib_tty_command );
        hal_remote_spt( XPTR( dev_cxy , &dev_ptr->isr ) , &soclib_tty_isr );
        // sprintk( name , "TXT_TTY%d" , channel )
        hal_remote_memcpy( XPTR( dev_cxy , &dev_ptr->name ),
                           XPTR( local_cxy , "TXT_TTY" ) , 16 );
        soclib_tty_init( dev_xp );
    }
    else
    {
        printk("\n[PANIC] in %s: undefined TXT device implementation\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // create server thread
    xptr_t     new_thread_xp;
    thread_t * new_thread_ptr;
    error_t    error;

    if( dev_cxy == local_cxy )         // device cluster is local
    {
        error = thread_kernel_create( &new_thread_ptr,
                                      THREAD_DEV,
                                      &dev_txt_server,
                                      dev_ptr,
                                      cluster_select_local_core() );
 
        new_thread_xp = XPTR( local_cxy , new_thread_ptr );
    }
    else                                        // device cluster is remote
    {
        rpc_thread_kernel_create_client( dev_cxy,
                                         THREAD_DEV,
                                         &dev_txt_server,
                                         dev_ptr,
                                         &new_thread_xp,
                                         &error );

        new_thread_ptr = (thread_t *)GET_PTR( new_thread_xp );
    }
    if( error )
    {
        printk("\n[PANIC] in %s : cannot create server thread\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // initialises server field in device descriptor
    hal_remote_spt( XPTR( dev_cxy , &dev_ptr->server ) , new_thread_ptr );
    
    // start server thread
    thread_unblock( new_thread_xp , THREAD_BLOCKED_GLOBAL );

} // end dev_txt_init()


//////////////////////////////////////////////////////////////////////////////////
// This static function is called by dev_txt_read(), dev_txt_write(), and
// dev_txt_sync_write() functions. 
// For the TXT_READ and TXT_WRITE operation types:
//  - it build the command, and registers it in the calling thread descriptor. 
//  - then, it registers the calling thead in device waiting queue.
//  - finally, it blocks on the THREAD_BLOCKED_DEV condition and deschedule.
// For the TXT_SYNC_WRITE operation type:
//  - it directly call the relevant driver, using a busy waiting policy.
////////////////////////////////////i/////////////////////////////////////////////
static error_t dev_txt_access( uint32_t   type,
                               uint32_t   channel,
                               char     * buffer,
                               uint32_t   count )
{
    thread_t * this = CURRENT_THREAD;

    txt_dmsg("\n[INFO] in %s : thread %x in process %x enters\n", 
                 __FUNCTION__ , this->trdid , this->process->pid );

    // check channel argument
    if( channel >= CONFIG_MAX_TXT_CHANNELS )
    {
        printk("\n[PANIC] in %s : illegal channel index = %d\n", __FUNCTION__ , channel );
        hal_core_sleep();
    }

    // get extended pointer on remote TXT device descriptor
    xptr_t  dev_xp = devices_dir.txt[channel];

    if ( dev_xp == XPTR_NULL )
    {
        printk("\n[PANIC] in %s : undefined TXT device descriptor for channel %d\n",
               __FUNCTION__ , channel );
        hal_core_sleep();
    }

    // register command in calling thread descriptor
    this->dev.txt.dev_xp  = dev_xp;
    this->dev.txt.type    = type;
    this->dev.txt.buf_xp  = XPTR( local_cxy , buffer );
    this->dev.txt.count   = count;


    if( (type == TXT_READ) || (type == TXT_WRITE) )  // descheduling policy
    {
        // get a free WTI mailbox  
        uint32_t wti_id;
        while( 1 )
        {
            wti_id = dev_icu_wti_alloc();
            if( wti_id == -1 )  sched_yield();
            else                break;
        }

        // enable WTI IRQ in local ICU and update WTI interrupt vector
        dev_icu_enable_irq( local_cxy , CURRENT_CORE->lid , WTI_TYPE , wti_id , dev_xp );

        // link TXT IRQ to WTI mailbox in PIC component 
        uint32_t irq_id = devices_input_irq.txt[channel];
        dev_pic_bind_irq( irq_id , local_cxy , wti_id );

        // register client thread in waiting queue, activate server thread
        // block client thread on THREAD_BLOCKED_IO and deschedule.
        // it is re-activated by the ISR signaling IO operation completion.
        device_register_command( dev_xp , this );

        // access PIC to unlink the IOC IRQ
        dev_pic_unbind_irq( irq_id );

        // disable WTI IRQ in ICU and update interrupt vector
        dev_icu_disable_irq( local_cxy , CURRENT_CORE->lid , WTI_TYPE , wti_id );

        // release  WTI mailbox
        dev_icu_wti_release( wti_id );

        txt_dmsg("\n[INFO] in %s : thread %x in process %x completes / error = %d\n", 
                     __FUNCTION__ , this->trdid , this->process->pid , this->dev.txt.error );
    }
    else if( type == TXT_SYNC_WRITE )            // busy waiting policy
    {
        // get driver command function pointer from remote TXT device descriptor
        cxy_t       dev_cxy = GET_CXY( dev_xp );
        device_t  * dev_ptr = (device_t *)GET_PTR( dev_xp );
        dev_cmd_t * cmd = (dev_cmd_t *)hal_remote_lpt( XPTR( dev_cxy , &dev_ptr->cmd ) );

        // take the TXT device lock, because the calling thread does NOT
        // register in device waiting queue for this synchronous command,
        // and has exclusive access to the terminal...
        remote_spinlock_lock( XPTR( dev_cxy , &dev_ptr->wait_lock ) );
 
        // call directly driver command 
        cmd( XPTR( local_cxy , this ) );

        // release the TXT device lock
        remote_spinlock_unlock( XPTR( dev_cxy , &dev_ptr->wait_lock ) );
    }    

    // return I/O operation status from calling thread descriptor
    return this->dev.txt.error;  

}  // end dev_txt_access()

/////////////////////////////////////////
error_t dev_txt_write( uint32_t   channel,
                       char     * buffer,
                       uint32_t   count )
{
    return dev_txt_access( TXT_WRITE , channel , buffer , count );
}
 
///////////////////////////////////////////////
error_t dev_txt_sync_write( uint32_t   channel,
                            char     * buffer,
                            uint32_t   count )
{
    return dev_txt_access( TXT_SYNC_WRITE , channel , buffer , count );
}
 
/////////////////////////////////////////
error_t dev_txt_read( uint32_t   channel,
                      char     * buffer )
{
    return dev_txt_access( TXT_READ , channel , buffer , 1 );
}

/////////////////////////////////////
void dev_txt_server( device_t * dev )
{
    xptr_t          client_xp;    // extended pointer on waiting thread
    cxy_t           client_cxy;   // cluster of client thread
    thread_t      * client_ptr;   // local pointer on client thread
    thread_t      * server;       // local pointer on server thread
    xptr_t          root_xp;      // extended pointer on device waiting queue root

    server = CURRENT_THREAD;

    root_xp = XPTR( local_cxy , &dev->wait_root );

	// infinite loop handling commands of threads 
    // registered in the TXT waiting queue
    while( 1 )
    {
        // get lock protecting queue
        remote_spinlock_lock( XPTR( local_cxy , &dev->wait_lock ) );

        // block and deschedule server thread if waiting queue empty 
        if( xlist_is_empty( root_xp ) )
        {
            thread_block( server , THREAD_BLOCKED_DEV_QUEUE );
            remote_spinlock_unlock( XPTR( local_cxy , &dev->wait_lock ) );
            sched_yield();
        }
        else
        {
            remote_spinlock_unlock( XPTR( local_cxy , &dev->wait_lock ) );
        } 

        // get extended pointer on first client thread
        client_xp = XLIST_FIRST_ELEMENT( root_xp , thread_t , wait_list );

        // call driver command function to start I/O operation
        dev->cmd( client_xp );
        
        // get client thread cluster and local pointer
        client_cxy = GET_CXY( client_xp );
        client_ptr = (thread_t *)GET_PTR( client_xp );

        // remove the client thread from waiting queue
        remote_spinlock_lock( XPTR( local_cxy , &dev->wait_lock ) );
        xlist_unlink( XPTR( client_cxy , &client_ptr->wait_list ) );
        remote_spinlock_unlock( XPTR( local_cxy , &dev->wait_lock ) );

    }  // end while

}  // end dev_txt_server()

