/*
 * chdev.c - channel device descriptor operations 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 <kernel_config.h>
#include <hal_types.h>
#include <hal_special.h>
#include <printk.h>
#include <boot_info.h>
#include <xlist.h>
#include <kmem.h>
#include <thread.h>
#include <rpc.h>
#include <chdev.h>
#include <devfs.h>

////////////////////////////////////////////
char * chdev_func_str( uint32_t func_type ) 
{
  	if     ( func_type == DEV_FUNC_RAM ) return "RAM";
  	else if( func_type == DEV_FUNC_ROM ) return "ROM";
  	else if( func_type == DEV_FUNC_FBF ) return "FBF";
  	else if( func_type == DEV_FUNC_IOB ) return "IOB";
  	else if( func_type == DEV_FUNC_IOC ) return "IOC";
  	else if( func_type == DEV_FUNC_MMC ) return "MMC";
  	else if( func_type == DEV_FUNC_DMA ) return "DMA";
  	else if( func_type == DEV_FUNC_NIC ) return "NIC";
  	else if( func_type == DEV_FUNC_TIM ) return "TIM";
  	else if( func_type == DEV_FUNC_TXT ) return "TXT";
  	else if( func_type == DEV_FUNC_ICU ) return "ICU";
  	else if( func_type == DEV_FUNC_PIC ) return "PIC";
    else                                 return "undefined";
}

/////////////////////////////////////////
chdev_t * chdev_create( uint32_t    func,
                        uint32_t    impl,
                        uint32_t    channel,
                        uint32_t    is_rx,
                        xptr_t      base )
{
    chdev_t    * chdev;
    kmem_req_t   req;

    // allocate memory for chdev
    req.type   = KMEM_DEVICE;
    req.flags  = AF_ZERO;
    chdev      = (chdev_t *)kmem_alloc( &req );

    if( chdev == NULL ) return NULL;

    // initialize waiting threads queue and associated lock
    remote_spinlock_init( XPTR( local_cxy , &chdev->wait_lock ) );
    xlist_root_init( XPTR( local_cxy , &chdev->wait_root ) );

    // initialize attributes
    chdev->func    =  func;
    chdev->impl    =  impl;
    chdev->channel =  channel;
    chdev->is_rx   =  is_rx;
    chdev->base    =  base; 

    return chdev;

}  // end chdev_create()

///////////////////////////////////
void chdev_print( chdev_t * chdev )
{
    printk("\n - func      = %s"
           "\n - channel   = %d"
           "\n - base      = %l"
           "\n - cmd       = %x"
           "\n - isr       = %x"
           "\n - chdev     = %x\n",
           chdev_func_str(chdev->func),
           chdev->channel,
           chdev->base,
           chdev->cmd,
           chdev->isr,
           chdev );
}

////////////////////////////////////////////////
void chdev_register_command( xptr_t     chdev_xp,
                             thread_t * thread )
{
    thread_t * thread_ptr = CURRENT_THREAD;

    // get device descriptor cluster and local pointer
    cxy_t     chdev_cxy = GET_CXY( chdev_xp );
    chdev_t * chdev_ptr = (chdev_t *)GET_PTR( chdev_xp );

    // build extended pointers on client thread xlist and device root
    xptr_t  xp_list = XPTR( local_cxy , &thread_ptr->wait_list );
    xptr_t  xp_root = XPTR( chdev_cxy , &chdev_ptr->wait_root );

    // get lock protecting queue
    remote_spinlock_lock( XPTR( chdev_cxy , &chdev_ptr->wait_lock ) );

    // register client thread in waiting queue 
    xlist_add_last( xp_root , xp_list );

    // unblock server thread
    thread_unblock( XPTR( chdev_cxy , &chdev_ptr->server ) , THREAD_BLOCKED_DEV_QUEUE );

    // release lock
    remote_spinlock_unlock( XPTR( chdev_cxy , &chdev_ptr->wait_lock ) );

    // client thread goes to blocked state and deschedule
    thread_block( thread_ptr , THREAD_BLOCKED_IO );
    sched_yield( NULL );

}  // end chdev_register_command()

///////////////////////////////////////////////
void chdev_sequencial_server( chdev_t * chdev )
{
    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 this thread
    xptr_t          root_xp;      // extended pointer on device waiting queue root

    server = CURRENT_THREAD;

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

    // take the lock protecting the chdev waiting queue, before entering the
	// infinite loop handling commands registered in this queue.
    // In the loop, the lock is released during the handling of one command.

    remote_spinlock_lock( XPTR( local_cxy , &chdev->wait_lock ) );

    while( 1 )
    {
        // check waiting queue state
        if( xlist_is_empty( root_xp ) ) // block and deschedule if waiting queue empty 
        {
            // release lock 
            remote_spinlock_unlock( XPTR( local_cxy , &chdev->wait_lock ) );

            // block and deschedule
            thread_block( server , THREAD_BLOCKED_DEV_QUEUE );
            sched_yield( NULL );
        } 
        else
        {
            // release lock 
            remote_spinlock_unlock( XPTR( local_cxy , &chdev->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 execute I/O operation
        chdev->cmd( client_xp );
        
        // get client thread cluster and local pointer
        client_cxy = GET_CXY( client_xp );
        client_ptr = (thread_t *)GET_PTR( client_xp );

        // take the lock, and remove the client thread from waiting queue
        remote_spinlock_lock( XPTR( local_cxy , &chdev->wait_lock ) );
        xlist_unlink( XPTR( client_cxy , &client_ptr->wait_list ) );

    }  // end while

}  // end chdev_sequencial_server()

