/*
 * dev_fbf.c - FBF (Frame Buffer) generic device API implementation.
 * 
 * Author  Alain Greiner    (2016,2017,2018,2019,2020)
 *
 * 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 <kernel_config.h>
#include <hal_kernel_types.h>
#include <hal_gpt.h>
#include <hal_drivers.h>
#include <hal_irqmask.h>
#include <hal_macros.h>
#include <hal_uspace.h>
#include <hal_vmm.h>
#include <thread.h>
#include <printk.h>
#include <string.h>
#include <memcpy.h>
#include <chdev.h>
#include <dev_fbf.h>

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

extern chdev_directory_t  chdev_dir;         // allocated in kernel_init.c

///////////////////////////////////////////
char * dev_fbf_cmd_str( uint32_t cmd_type )
{
    if     ( cmd_type == FBF_GET_CONFIG     )  return "GET_CONFIG";
    else if( cmd_type == FBF_CREATE_WINDOW  )  return "CREATE_WINDOW";
    else if( cmd_type == FBF_ACTIVE_WINDOW  )  return "ACTIVE_WINDOW";
    else if( cmd_type == FBF_DELETE_WINDOW  )  return "DELETE_WINDOW";
    else if( cmd_type == FBF_MOVE_WINDOW    )  return "MOVE_WINDOW";
    else if( cmd_type == FBF_REFRESH_WINDOW )  return "REFRESH_WINDOW";
    else if( cmd_type == FBF_FRONT_WINDOW   )  return "FRONT_WINDOW";
    else if( cmd_type == FBF_RESIZE_WINDOW  )  return "RESIZE_WINDOW";

    else if( cmd_type == FBF_DIRECT_WRITE   )  return "DIRECT_WRITE";
    else if( cmd_type == FBF_DIRECT_READ    )  return "DIRECT_READ";
    else                                       return "undefined";
}

////////////////////////////////////////////////
xptr_t dev_fbf_get_xptr_from_wid( uint32_t wid )
{
    thread_t  * this  = CURRENT_THREAD;
    pid_t       pid   = this->process->pid;
    trdid_t     trdid = this->trdid;

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointer on windows_tbl[wid] 
    xptr_t entry_xp  = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_tbl[wid] );

    // get pointers on searched window 
    xptr_t         window_xp  = hal_remote_l64( entry_xp );
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    if( window_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s / client thread[%x,%x] request a non registered wid (%d)\n",
        __FUNCTION__, pid, trdid, wid );
        return XPTR_NULL;
    }

    // get owner process PID from window descriptor
    pid_t owner_pid = hal_remote_l32( XPTR( window_cxy , &window_ptr->pid ) );

    if( pid != owner_pid )
    {
        printk("\n[ERROR] in %s / client thread[%x,%x] not owner of wid (%d) / owner is (%x)\n",
        __FUNCTION__, pid, trdid, owner_pid, wid );
        return XPTR_NULL;
    }

    return window_xp;

}  // end dev_fbf_get_xptr_from_wid()

////////////////////////////////////
void dev_fbf_init( chdev_t  * fbf )
{
    uint32_t wid;

    // set chdev name
    strcpy( fbf->name, "fbf" );

    // initialize lock protecting the windows
    remote_rwlock_init( XPTR( local_cxy , &fbf->ext.fbf.windows_lock ),
                        LOCK_FBF_WINDOWS ); 

    // initialize root of windows xlist
    xlist_root_init( XPTR( local_cxy , &fbf->ext.fbf.windows_root ) );

    // initialize windows_tbl[] array
    for( wid = 0 ; wid < CONFIG_FBF_WINDOWS_MAX_NR ; wid++ ) 
    {
        fbf->ext.fbf.windows_tbl[wid] = XPTR_NULL;
    }

    // initialize wid allocator bitmap
    bitmap_init( fbf->ext.fbf.windows_bitmap , CONFIG_FBF_WINDOWS_MAX_NR );

    // call driver init function to initialize the harware FBF
    // and initialize the width, height, and subsampling FBF chdev fields
    hal_drivers_fbf_init( fbf );

}  // end dev_fbf_init()

//////////////////////////////////////////
void dev_fbf_get_config( uint32_t * width,
                         uint32_t * height,
                         uint32_t * type )
{
    // get extended pointer on FBF chdev descriptor
    xptr_t  dev_xp = chdev_dir.fbf[0];

assert( __FUNCTION__, (dev_xp != XPTR_NULL) , "undefined FBF chdev descriptor" );

    // get FBF chdev cluster and local pointer
    cxy_t     dev_cxy = GET_CXY( dev_xp );
    chdev_t * dev_ptr = (chdev_t *)GET_PTR( dev_xp );

    // return values
    *width  = hal_remote_l32( XPTR( dev_cxy , &dev_ptr->ext.fbf.width ) );
    *height = hal_remote_l32( XPTR( dev_cxy , &dev_ptr->ext.fbf.height ) );
    *type   = hal_remote_l32( XPTR( dev_cxy , &dev_ptr->ext.fbf.subsampling ) );

}  // end dev_fbf_get_config()

/////////////////////////////////////////////////
uint32_t dev_fbf_create_window( uint32_t   nlines,
                                uint32_t   npixels,
                                uint32_t   l_min,
                                uint32_t   p_min,
                                intptr_t * user_buffer )
{
    fbf_window_t * window;      // window descriptor (created in local cluster)
    vseg_t       * vseg;        // vseg descriptor (created in reference cluster)
    intptr_t       vseg_base;   // vseg base address in user space  

    // get local pointers on calling thread and process
    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

#if DEBUG_DEV_FBF
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enter : nlines %d / npixels %d / l_min %d / p_min %d / cycle %d\n",
__FUNCTION__ , process->pid, this->trdid, nlines, npixels, l_min, p_min, cycle );
#endif

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

// check fbf_xp definition
assert( __FUNCTION__, (fbf_xp != XPTR_NULL) , "undefined FBF chdev descriptor" );

    // get FBF width and height
    uint32_t fbf_width  = hal_remote_l32( XPTR( fbf_cxy , &fbf_ptr->ext.fbf.width ) );
    uint32_t fbf_height = hal_remote_l32( XPTR( fbf_cxy , &fbf_ptr->ext.fbf.height ) );

    // check new window size and coordinates
    if( (((l_min + nlines) > fbf_height) || ((p_min + npixels) > fbf_width)) )
    {
        printk("\n[ERROR] in %s / thread[%x,%x]" 
        "illegal new coordinates (%d,%d) for window (%d,%d) in fbf (%d,%d)\n",
        process->pid, this->trdid, p_min, l_min, npixels, nlines, fbf_width, fbf_height );
        return -1;
    }

    // build extended pointers on windows lock, root, and wid allocator 
    xptr_t windows_lock_xp   = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t windows_root_xp   = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );
    xptr_t windows_bitmap_xp = XPTR( fbf_cxy ,  fbf_ptr->ext.fbf.windows_bitmap );
 
    // allocate memory for the window descriptor in local cluster
    window  = kmem_alloc( bits_log2(sizeof(fbf_window_t)) , AF_ZERO );

    if( window == NULL )
    {
        printk("\n[ERROR] in %s / thread[%x,%x] cannot allocate window descriptor\n",
        __FUNCTION__, process->pid, this->trdid );
        return -1;
    }

#if (DEBUG_DEV_FBF & 1)
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] created window descriptor %x / cycle %d\n",
__FUNCTION__ , process->pid, this->trdid, window, cycle );
#endif

    // getpointers on reference process
    xptr_t      ref_xp  = process->ref_xp;
    process_t * ref_ptr = GET_PTR( ref_xp );
    cxy_t       ref_cxy = GET_CXY( ref_xp );

    // allocate a new vseg, and introduce it in the reference process VSL
    if( ref_cxy == local_cxy )
    {
        vseg = vmm_create_vseg( process,            // owner process
                                VSEG_TYPE_ANON,     // localised, public
                                0,                  // base, unused for ANON
                                nlines * npixels,   // size
                                0,                  // file_offset, unused for ANON 
                                0,                  // file_size, unused for ANON
                                XPTR_NULL,          // mapper_xp, unused for ANON
                                local_cxy );        // mapping cluster
    }
    else
    {
        rpc_vmm_create_vseg_client( ref_cxy,
                                    ref_ptr,
                                    VSEG_TYPE_ANON,
                                    0,                 // base, unused for ANON
                                    nlines * npixels,  // size
                                    0,                 // file_offset, unused for ANON
                                    0,                 // file size, unused for ANON
                                    XPTR_NULL,         // mapper_xp, unused for ANON
                                    local_cxy,
                                    &vseg );
    } 
        
    if( vseg == NULL )
    {
        printk("\n[ERROR] in %s / thread[%x,%x] cannot create vseg in reference cluster\n",
        __FUNCTION__, process->pid, this->trdid );
        kmem_free( window , bits_log2(sizeof(fbf_window_t)) );
        return -1;
    }

    // get vseg base
    vseg_base = (intptr_t)hal_remote_lpt( XPTR( ref_cxy , &vseg->min ) );

#if (DEBUG_DEV_FBF & 1)
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] allocated vseg / base %x / cycle %d\n",
__FUNCTION__ , process->pid, this->trdid, vseg_base, cycle );
#endif

    // take the lock protecting windows in write mode
    remote_rwlock_wr_acquire( windows_lock_xp );

    // allocate a wid from allocator in FBF descriptor extension
    uint32_t wid = bitmap_remote_alloc( windows_bitmap_xp , CONFIG_FBF_WINDOWS_MAX_NR );

    if( wid == 0xFFFFFFFF )
    {
        printk("\n[ERROR] in %s / thread[%x,%x] cannot allocate buffer for window\n",
        __FUNCTION__, process->pid, this->trdid );
        kmem_free( window , bits_log2(sizeof(fbf_window_t)) );
        vmm_remove_vseg( process , vseg );
        return -1;
    }
 
    // initialize window descriptor
    window->pid     = process->pid;
    window->wid     = wid;
    window->height  = nlines;
    window->width   = npixels;
    window->l_min   = l_min;
    window->p_min   = p_min;
    window->hidden  = true;
    window->buffer  = (uint8_t *)vseg_base;

    // register new window in xlist rooted in FBF extension
    xlist_add_last( windows_root_xp , XPTR( local_cxy , &window->xlist ) );

    // build extended pointer on relevant entry in windows_tbl[] array
    xptr_t windows_tbl_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_tbl[wid] );

    // register new window in windows_tbl[] stored in FBF extension
    hal_remote_s64( windows_tbl_xp , XPTR( local_cxy , window ) );

    // release the lock protecting windows in write mode
    remote_rwlock_wr_release( windows_lock_xp );

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit / wid %d / buffer %x / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, wid , window->buffer, cycle );
#endif

    // return pointer on allocated buffer
    *user_buffer = vseg_base;

    return wid;

}  // end dev_fbf_create_window()

/////////////////////////////////////////////
error_t dev_fbf_active_window( uint32_t  wid,
                               uint32_t  active )
{

#if DEBUG_DEV_FBF
thread_t  * thi   = CURRENT_THREAD;
uint32_t    cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enters : wid %d / active %d / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, wid, active, cycle );
#endif

    // get extended pointer on window to be activated
    xptr_t window_xp  = dev_fbf_get_xptr_from_wid( wid );

    if( window_xp == XPTR_NULL ) return -1;

    // get cluster and local pointer on target window
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    // set/reset hidden flag in window descriptor
    hal_remote_s32( XPTR( window_cxy , &window_ptr->hidden ) , (active == 0) ? 1 : 0 );

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end dev_fbf_active_window()

////////////////////////////////////////////////////////////////////////////////////////
// This static function is called by dev_fbf_refresh_window(), dev_fbf_move_window(),
// dev_fbf_front_window(), dev_fbf_delete_window(), and dev_fbf_resize_window().
// It updates all lines of a pseudo window identified by the <p_min>, <p_max>, <l_min>, 
// and <l_max> arguments, that are dynamically computed by the caller. 
// This function scan all registered windows to take into account the overlap priorities
// defined by the FBF xlist of windows. It takes the lock protecting xlist in read mode. 
////////////////////////////////////////////////////////////////////////////////////////
// Implementation Note:
// This function contains two loops.
// - the external loop builds one line of the pseudo window per iteraiion in a local
//   line_buffer. One line  contains [p_max - p_min] pixels. Then, it calls the FBF
//   driver (one driver call per line) to write this line into the Frame Buffer.
// - the internal loop scan the list of the registered windows in increasing priority,
//   and for each registered window that has a non empty intersection with the handled
//   line, it updates the line_buffer, using the hal_copy_from_uspace() function
//   to get the most up-to-date user-defined data.
////////////////////////////////////////////////////////////////////////////////////////
// @ p_min   : [in]  upper left corner X coordinate in FBF reference
// @ p_max   : [in]  upper left corner Y coordinate in FBF reference.
// @ l_min   : [in]  lower right corner X coordinate in FBF reference (excluded).
// @ l_max   : [in]  lower right corner Y coordinate in FBF reference (excluded).
////////////////////////////////////////////////////////////////////////////////////////
error_t fbf_update( uint32_t    p_min,
                    uint32_t    l_min,
                    uint32_t    p_max,
                    uint32_t    l_max )
{
    uint32_t       line;                     // iterator to scan the FBF lines
    uint32_t       pixel;                    // iterator to scan pixels in one FBF line
    xptr_t         iter_xp;                  // iterator to scan the list of windows
    error_t        error;

    // this intermediate buffer to build one pseudo-window line
    uint8_t  line_buffer[CONFIG_FBF_WINDOWS_MAX_WIDTH];

    // get pointer on calling thread and core lid
    thread_t  * this = CURRENT_THREAD;

#if DEBUG_DEV_FBF
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] enter : p_min %d / l_min %d / p_max %d / l_max %d / cycle %d\n",
__FUNCTION__, p_min, l_min, p_max, l_max, cycle );
#endif

    // get pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // get frame buffer width and height
    uint32_t fbf_width  = hal_remote_l32( XPTR( fbf_cxy , &fbf_ptr->ext.fbf.width ) );
    uint32_t fbf_height = hal_remote_l32( XPTR( fbf_cxy , &fbf_ptr->ext.fbf.height ) );

// check arguments
assert( __FUNCTION__, (p_min < fbf_width)  && (p_max <= fbf_width) && 
        (l_min < fbf_height) && (l_max <= fbf_height) , "illegal arguments" );

    // get pointer on driver command function
    dev_cmd_t * cmd = hal_remote_lpt( XPTR( fbf_cxy , &fbf_ptr->cmd ) );

    // build extended pointers on windows xlist root and lock
    xptr_t  windows_root_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );
    xptr_t  windows_lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );

    error = 0;

    // 1. external loop on pseudo window lines (in FBF reference) 
    for( line = l_min ; line < l_max ; line++ )
    {
        // reset the line buffer to default value
        for( pixel = 0 ; pixel < (p_max - p_min) ; pixel++ ) line_buffer[pixel] = 127;

        // take the lock in read mode
        remote_rwlock_rd_acquire( windows_lock_xp );

        // 2. internal loop on all registered windows
        XLIST_FOREACH( windows_root_xp , iter_xp )
        {
            // get pointers on current target window
            xptr_t         tgt_xp  = XLIST_ELEMENT( iter_xp , fbf_window_t , xlist );
            fbf_window_t * tgt_ptr = GET_PTR( tgt_xp );
            cxy_t          tgt_cxy = GET_CXY( tgt_xp );

            // get target window min and max coordinates in FBF reference
            bool_t    hidden   = hal_remote_l32( XPTR( tgt_cxy , &tgt_ptr->hidden ) );
            uint32_t  w_l_min  = hal_remote_l32( XPTR( tgt_cxy , &tgt_ptr->l_min ) );
            uint32_t  w_p_min  = hal_remote_l32( XPTR( tgt_cxy , &tgt_ptr->p_min ) );
            uint32_t  w_height = hal_remote_l32( XPTR( tgt_cxy , &tgt_ptr->height ) );
            uint32_t  w_width  = hal_remote_l32( XPTR( tgt_cxy , &tgt_ptr->width  ) );
            uint32_t  w_l_max  = w_l_min + w_height;
            uint32_t  w_p_max  = w_p_min + w_width;

            // does nothing when target window is hidden
            // or the pseudo window line does not overlap the target window
            if( (hidden == true)   ||
                (line  <  w_l_min) ||
                (line  >= w_l_max) ||
                (p_max <  w_p_min) ||
                (p_min >= w_p_max) ) continue;

            // get pointer on buffer associated to target window in user space
            uint8_t * w_buffer = hal_remote_lpt( XPTR( tgt_cxy , &tgt_ptr->buffer ) ); 

            // get min & max indexes for pixels to be moved in FBF reference
            uint32_t f_pixel_min = (p_min < w_p_min) ? w_p_min : p_min;
            uint32_t f_pixel_max = (p_max < w_p_max) ? p_max : w_p_max;

            // compute number of pixels to move from w_buffer to f_buffer
            uint32_t npixels = f_pixel_max - f_pixel_min;

            // compute offset in line_buffer
            uint32_t line_offset = f_pixel_min - p_min; 

            // compute line index in window  
            uint32_t w_line = line - w_l_min;

            // compute offset in window buffer
            uint32_t w_offset = (w_line * w_height) + f_pixel_min - w_p_min;

            // move pixels from w_buffer (user space) to line_buffer (kernel space)
            hal_copy_from_uspace( XPTR( local_cxy  , &line_buffer[line_offset] ),
                                  &w_buffer[w_offset], 
                                  npixels );
        }  // end for windows

        // release the lock 
        remote_rwlock_rd_release( windows_lock_xp );

        // compute offset in FBF
        uint32_t fbf_offset = p_min + (line * fbf_width);

        // register command in calling thread descriptor
        this->fbf_cmd.dev_xp    = fbf_xp;
        this->fbf_cmd.type      = FBF_DRIVER_KERNEL_WRITE;
        this->fbf_cmd.buffer    = line_buffer;
        this->fbf_cmd.npixels   = p_max - p_min;
        this->fbf_cmd.offset    = fbf_offset;

        // call driver to display one line
        cmd( XPTR( local_cxy , this ) );

        error |= this->fbf_cmd.error;
    
    }  // end for lines

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] exit / cycle %d\n",
__FUNCTION__, cycle );
#endif

    // return I/O operation status
    return error;

}  // end fbf_update()

//////////////////////////////////////////////
error_t dev_fbf_delete_window( uint32_t  wid )
{
    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

#if DEBUG_DEV_FBF
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enters : wid %d / cycle %d\n",
__FUNCTION__ , process->pid, this->trdid, wid, cycle );
#endif

    // get extended pointer on window to be deleted
    xptr_t window_xp  = dev_fbf_get_xptr_from_wid( wid );

    if( window_xp == XPTR_NULL ) return -1;

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointers on windows lock, windows_tbl[wid] and wid allocator 
    xptr_t windows_lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t wid_bitmap_xp   = XPTR( fbf_cxy ,  fbf_ptr->ext.fbf.windows_bitmap );
    xptr_t windows_tbl_xp  = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_tbl[wid] );

    // get cluster and local pointer on window
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    // get relevant info from window descriptor
    uint32_t   p_min   = hal_remote_l32( XPTR( window_cxy , &window_ptr->p_min ) );
    uint32_t   l_min   = hal_remote_l32( XPTR( window_cxy , &window_ptr->l_min ) );
    uint32_t   npixels = hal_remote_l32( XPTR( window_cxy , &window_ptr->width ) );
    uint32_t   nlines  = hal_remote_l32( XPTR( window_cxy , &window_ptr->height ) );
    uint8_t  * buffer  = hal_remote_lpt( XPTR( window_cxy , &window_ptr->buffer ) );

    // 1. set the hidden bit in window descriptor
    hal_remote_s32( XPTR( window_cxy , &window_ptr->hidden ) , true );

    // 2. refresh the window in FBF
    fbf_update( p_min, l_min, p_min + npixels, l_min + nlines );

    // 3. take the lock protecting windows in write mode
    remote_rwlock_wr_acquire( windows_lock_xp );

    // 4. remove the window from windows_tbl[] array
    hal_remote_s64( windows_tbl_xp , XPTR_NULL );

    // 5. remove the window from xlist      
    xlist_unlink( XPTR( window_cxy , &window_ptr->xlist ) );

    // 6. release wid to bitmap
    bitmap_remote_clear( wid_bitmap_xp , wid );

    // 7. release the lock protecting windows in write mode
    remote_rwlock_wr_release( windows_lock_xp );
 
    // 8. release memory allocated for window descriptor
    kmem_remote_free( window_cxy , window_ptr , bits_log2(sizeof(fbf_window_t)) );

    // 9. release the associated vseg
    vmm_global_delete_vseg( process , (intptr_t)buffer );
    
#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end dev_fbf_delete_window()

////////////////////////////////////////////
error_t dev_fbf_move_window( uint32_t  wid,
                             uint32_t  l_new,
                             uint32_t  p_new )
{

#if DEBUG_DEV_FBF
thread_t  * this  = CURRENT_THREAD;
uint32_t    cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enters : wid %d / l_new %d / p_new %d / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, wid, l_new, p_new, cycle );
#endif

    // get extended pointer on window to be moved
    xptr_t window_xp  = dev_fbf_get_xptr_from_wid( wid );

    if( window_xp == XPTR_NULL ) return -1;

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointers on windows lock and root 
    xptr_t windows_lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t windows_root_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );


    // get cluster and local pointer on target window
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    // get target window coordinates, width and height
    uint32_t p_old     = hal_remote_l32( XPTR( window_cxy , &window_ptr->p_min ) );
    uint32_t l_old     = hal_remote_l32( XPTR( window_cxy , &window_ptr->l_min ) );
    uint32_t nlines    = hal_remote_l32( XPTR( window_cxy , &window_ptr->height ) );
    uint32_t npixels   = hal_remote_l32( XPTR( window_cxy , &window_ptr->width ) );

    // build extended pointer on window xlist_entry
    xptr_t xlist_entry_xp =  XPTR( window_cxy , &window_ptr->xlist );

    // does nothing if no change
    if( (p_new == p_old) && (l_new == l_old) )  return 0;

    // 1. set the "hidden" flag in window descriptor
    hal_remote_s32( XPTR( window_cxy , &window_ptr->hidden ) , true );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] hidden set\n", __FUNCTION__ );
#endif

    // 2. update the FBF for the old window position
    fbf_update( p_old , l_old , p_old + npixels, l_old + nlines );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] refreshed old window\n", __FUNCTION__ );
#endif

    // 3. take the lock protecting windows in write mode
    remote_rwlock_wr_acquire( windows_lock_xp );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] lock taken\n", __FUNCTION__ );
#endif

    // 4. set the new coordinates in the window descriptor,
    hal_remote_s32( XPTR( window_cxy , &window_ptr->l_min ), l_new );
    hal_remote_s32( XPTR( window_cxy , &window_ptr->p_min ), p_new );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] l_min & p_min updated\n", __FUNCTION__ );
#endif

    // 5. gives the window the highest priority 
    xlist_unlink( xlist_entry_xp );
    xlist_add_last( windows_root_xp , xlist_entry_xp );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] set high priority\n", __FUNCTION__ );
#endif

    // 6. release the lock protecting windows in write mode
    remote_rwlock_wr_release( windows_lock_xp );
 
#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] lock released\n", __FUNCTION__ );
#endif

    // 7. reset the "hidden" flag in window descriptor
    hal_remote_s32( XPTR( window_cxy , &window_ptr->hidden ) , false );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] hidden reset\n", __FUNCTION__ );
#endif

    // 8. update the FBF for the new window position
    fbf_update( p_new , l_new , p_new + npixels, l_new + nlines );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] refresh new new window\n", __FUNCTION__ );
#endif

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end dev_fbf_move_window()

/////////////////////////////////////////////
error_t dev_fbf_resize_window( uint32_t  wid,
                               uint32_t  width,
                               uint32_t  height )
{
    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

#if DEBUG_DEV_FBF
uint32_t    cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enters : wid %d / width %d / height %d / cycle %d\n",
__FUNCTION__ , process->pid , this->trdid , wid, width , height , cycle );
#endif

    // get extended pointer on window to be resized
    xptr_t window_xp  = dev_fbf_get_xptr_from_wid( wid );

    if( window_xp == XPTR_NULL ) return -1;

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointers on windows lock and root 
    xptr_t windows_lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t windows_root_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );

    // get cluster and local pointer on target window 
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    // get process owner PID, width, height, and buffer
    uint32_t p_min   = hal_remote_l32( XPTR( window_cxy , &window_ptr->p_min ) );
    uint32_t l_min   = hal_remote_l32( XPTR( window_cxy , &window_ptr->l_min ) );
    uint32_t nlines  = hal_remote_l32( XPTR( window_cxy , &window_ptr->height ) );
    uint32_t npixels = hal_remote_l32( XPTR( window_cxy , &window_ptr->width ) );
    void   * base    = hal_remote_lpt( XPTR( window_cxy , &window_ptr->buffer ) );

    // build extended pointer on window xlist_entry
    xptr_t xlist_entry_xp =  XPTR( window_cxy , &window_ptr->xlist );

    // does nothing if no change
    if( (width == npixels) && (height == nlines) ) return 0;

    // compute old_size and new size
    uint32_t old_size = nlines * npixels;
    uint32_t new_size = width * height;

    // 1. set the "hidden" flag in window descriptor
    hal_remote_s32( XPTR( window_cxy , &window_ptr->hidden ) , true );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] hidden set\n", __FUNCTION__ );
#endif

    // 2. refresh the FBF for the current window size
    fbf_update( p_min , l_min , p_min + npixels, l_min + nlines );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] refreshed old window\n", __FUNCTION__ );
#endif

    // 3. take the lock protecting windows in write mode
    remote_rwlock_wr_acquire( windows_lock_xp );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] lock taken\n", __FUNCTION__ );
#endif

    // 4. set the new width & height in the window descriptor,
    hal_remote_s32( XPTR( window_cxy , &window_ptr->width  ), width );
    hal_remote_s32( XPTR( window_cxy , &window_ptr->height ), height );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] width & height updated\n", __FUNCTION__ );
#endif

    // 5. resize vseg if required
    vmm_global_resize_vseg( process, (intptr_t)base, (intptr_t)base, width * height );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] vseg resized\n", __FUNCTION__ );
#endif

    // 6. fill buffer extension if required
    if( new_size > old_size )  memset( base + old_size , 0 , new_size - old_size );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] buffer extension initialized\n", __FUNCTION__ );
#endif

    // 7. gives the window the highest priority 
    xlist_unlink( xlist_entry_xp );
    xlist_add_last( windows_root_xp , xlist_entry_xp );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] set high priority\n", __FUNCTION__ );
#endif

    // 8. release the lock protecting windows in write mode
    remote_rwlock_wr_release( windows_lock_xp );
 
#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] lock released\n", __FUNCTION__ );
#endif

    // 9. reset the "hidden" flag in window descriptor
    hal_remote_s32( XPTR( window_cxy , &window_ptr->hidden ) , false );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] hidden reset\n", __FUNCTION__ );
#endif

    // 10. refresh the FBF for the new window position
    fbf_update( p_min , l_min , p_min + width, l_min + height );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] refreshed new window\n", __FUNCTION__ );
#endif

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end dev_fbf_resize_window()

//////////////////////////////////////////////
error_t dev_fbf_refresh_window( uint32_t  wid,
                                uint32_t  line_min,
                                uint32_t  line_max )
{

#if DEBUG_DEV_FBF
thread_t  * thi   = CURRENT_THREAD;
uint32_t    cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enters for wid %d / first %d / last %d / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, wid, line_min, line_max, cycle );
#endif

    // get extended pointer on window to be refreshed
    xptr_t window_xp  = dev_fbf_get_xptr_from_wid( wid );

    if( window_xp == XPTR_NULL ) return -1;

    // get cluster and local pointer on target window 
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    // get p_min, l_min, nlines  & npixels from window descriptor
    uint32_t p_min   = hal_remote_l32( XPTR( window_cxy , &window_ptr->p_min ) ); 
    uint32_t l_min   = hal_remote_l32( XPTR( window_cxy , &window_ptr->l_min ) ); 
    uint32_t npixels = hal_remote_l32( XPTR( window_cxy , &window_ptr->width ) ); 
    uint32_t nlines  = hal_remote_l32( XPTR( window_cxy , &window_ptr->height ) ); 

    // check <line_min> and <line_max> arguments
    if( (line_min >= nlines) || (line_max > nlines) || (line_min >= line_max) )
    {
        printk("\n[ERROR] in %s : illegal arguments / l_first %d / l_last %d / nlines %d\n",
        __FUNCTION__, line_min, line_max, nlines );
        return -1;
    }

    // update FBF 
    fbf_update( p_min , l_min + line_min , p_min + npixels , l_min + line_max );

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit for wid %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, wid, cycle );
#endif

    return 0;

}  // end dev_fbf_refresh_window()

////////////////////////////////////////////
error_t dev_fbf_front_window( uint32_t wid )
{

#if DEBUG_DEV_FBF
thread_t  * this  = CURRENT_THREAD;
uint32_t    cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] enters for wid %d / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, wid, cycle );
#endif

    // get extended pointer on window to be refreshed
    xptr_t window_xp  = dev_fbf_get_xptr_from_wid( wid );

    if( window_xp == XPTR_NULL ) return -1;

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointer on windows lock and root
    xptr_t  windows_lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t  windows_root_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );

    // get cluster and local pointers on window 
    cxy_t          window_cxy = GET_CXY( window_xp );
    fbf_window_t * window_ptr = GET_PTR( window_xp );

    // get target window coordinates, width, height, and hidden
    uint32_t p_min     = hal_remote_l32( XPTR( window_cxy , &window_ptr->p_min ) );
    uint32_t l_min     = hal_remote_l32( XPTR( window_cxy , &window_ptr->l_min ) );
    uint32_t nlines    = hal_remote_l32( XPTR( window_cxy , &window_ptr->height ) );
    uint32_t npixels   = hal_remote_l32( XPTR( window_cxy , &window_ptr->width ) );
    bool_t   hidden    = hal_remote_l32( XPTR( window_cxy , &window_ptr->hidden ) );

    // build extended pointer on window xlist_entry
    xptr_t xlist_entry_xp =  XPTR( window_cxy , &window_ptr->xlist );

    // 1. take the lock protecting windows in write mode
    remote_rwlock_wr_acquire( windows_lock_xp );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] lock taken\n", __FUNCTION__ );
#endif

    // 2. gives the window the highest priority 
    xlist_unlink( xlist_entry_xp );
    xlist_add_last( windows_root_xp , xlist_entry_xp );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] set high priority \n", __FUNCTION__ );
#endif

    // 3. release the lock protecting windows from write mode
    remote_rwlock_wr_release( windows_lock_xp );
 
#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] lock released\n", __FUNCTION__ );
#endif

    // 4. update the FBF for this window when not hidden
    if( hidden == false ) fbf_update( p_min , l_min , p_min + npixels, l_min + nlines );

#if ( DEBUG_DEV_FBF & 1 )
printk("\n[%s] refresh window in FBF\n", __FUNCTION__ );
#endif

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit for wid %d / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, wid, cycle );
#endif

    return 0;

}   // end dev_fbf_front_window()

/////////////////////////////////////////
void dev_fbf_display_windows( pid_t pid )
{
    xptr_t  iter_xp;

    // display header
    printk("\n***** registered FBF windows *****\n"
           "   wid   | hide  | lzero | pzero | lines | pixel | pid\n" );

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointer on windows lock and root
    xptr_t  lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t  root_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );

    // take the lock in read mode
    remote_rwlock_rd_acquire( lock_xp );

    XLIST_FOREACH_BACKWARD( root_xp , iter_xp )
    {
        xptr_t         w_xp  = XLIST_ELEMENT( iter_xp , fbf_window_t , xlist );
        fbf_window_t * w_ptr = GET_PTR( w_xp );
        cxy_t          w_cxy = GET_CXY( w_xp );

        uint32_t wid       = hal_remote_l32( XPTR( w_cxy , &w_ptr->wid   ) );
        uint32_t owner_pid = hal_remote_l32( XPTR( w_cxy , &w_ptr->pid   ) );
        uint32_t hide      = hal_remote_l32( XPTR( w_cxy , &w_ptr->hidden ) );
        uint32_t lzero     = hal_remote_l32( XPTR( w_cxy , &w_ptr->l_min ) );
        uint32_t pzero     = hal_remote_l32( XPTR( w_cxy , &w_ptr->p_min ) );
        uint32_t lines     = hal_remote_l32( XPTR( w_cxy , &w_ptr->height ) );
        uint32_t pixels    = hal_remote_l32( XPTR( w_cxy , &w_ptr->width ) );

        if( (pid == 0) || (pid == owner_pid) )
        { 
            printk("%d\t | %d\t | %d\t | %d\t | %d\t | %d\t | %x\n",
                    wid,   hide,  lzero, pzero, lines, pixels, pid );
        }
    }

    // release the lock
    remote_rwlock_rd_release( lock_xp );

}  // end dev_fbf_display_windows()

/////////////////////////////////
void dev_fbf_cleanup( pid_t pid )
{
    xptr_t  iter_xp;

    // get cluster and pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // build extended pointer on windows lock and root
    xptr_t  lock_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_lock );
    xptr_t  root_xp = XPTR( fbf_cxy , &fbf_ptr->ext.fbf.windows_root );

    // take the lock in read mode
    remote_rwlock_rd_acquire( lock_xp );

    XLIST_FOREACH( root_xp , iter_xp )
    {
        xptr_t         w_xp  = XLIST_ELEMENT( iter_xp , fbf_window_t , xlist );
        fbf_window_t * w_ptr = GET_PTR( w_xp );
        cxy_t          w_cxy = GET_CXY( w_xp );

        // get owner process PID and WID
        uint32_t   owner_pid  = hal_remote_l32( XPTR( w_cxy , &w_ptr->pid ) );
        uint32_t   wid        = hal_remote_l32( XPTR( w_cxy , &w_ptr->wid ) );

        // delete matching window
        if( pid == owner_pid ) dev_fbf_delete_window( wid );
    }

    // release the lock from read mode
    remote_rwlock_rd_release( lock_xp );

}  // end dev_fbf_cleanup()




///////////////////////////////////////////////
// TODO Deprecated : january 2020 [AG]
///////////////////////////////////////////////
error_t dev_fbf_move_data( bool_t     is_write,
                           void     * user_buffer,
                           uint32_t   npixels,
                           uint32_t   offset )
{
    // get pointer on calling thread
    thread_t * this = CURRENT_THREAD;

#if DEBUG_DEV_FBF
uint32_t   cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] :  buffer %x / npixels %d / offset %x / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid,  
user_buffer, npixels, offset, cycle );
#endif

    // get pointers on FBF chdev
    xptr_t      fbf_xp  = chdev_dir.fbf[0];
    cxy_t       fbf_cxy = GET_CXY( fbf_xp );
    chdev_t   * fbf_ptr = GET_PTR( fbf_xp );

    // get frame buffer width and height
    uint32_t width  = hal_remote_l32 ( XPTR( fbf_cxy , &fbf_ptr->ext.fbf.width ) );
    uint32_t height = hal_remote_l32 ( XPTR( fbf_cxy , &fbf_ptr->ext.fbf.height ) );

    // check offset and npixels versus FBF size
    if( ((offset + npixels) > (width * height)) )
    {
        printk("\n[ERROR] in %s : offset (%d) + npixels (%d) / width (%d) / height (%d)\n",
        __FUNCTION__, offset, npixels, width, height ); 
        return -1;
    }

    // register command in calling thread descriptor
    this->fbf_cmd.dev_xp    = fbf_xp;
    this->fbf_cmd.type      = is_write ? FBF_DRIVER_USER_WRITE : FBF_DRIVER_USER_READ;
    this->fbf_cmd.buffer    = user_buffer;
    this->fbf_cmd.offset    = offset;
    this->fbf_cmd.npixels   = npixels;

    // get driver command function
    dev_cmd_t * cmd = (dev_cmd_t *)hal_remote_lpt( XPTR( fbf_cxy , &fbf_ptr->cmd ) );

    // call driver 
    cmd( XPTR( local_cxy , this ) );

    error_t error = this->fbf_cmd.error;

#if DEBUG_DEV_FBF
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_DEV_FBF < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    // return I/O operation status
    return error;

}  // end dev_fbf_move_data()


