/*
 * devfs.c - DEVFS File system API implementation.
 *
 * Author   Mohamed Lamine Karaoui (2014,2015)
 *          Alain Greiner (2016,2017)
 *
 * Copyright (c) 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 <printk.h>
#include <kmem.h>
#include <string.h>
#include <chdev.h>
#include <core.h>
#include <thread.h>
#include <vfs.h>
#include <errno.h>
#include <devfs.h>
#include <rpc.h>


//////////////////////////////////////////////////////////////////////////////////////////
//          Extern  variables         
//////////////////////////////////////////////////////////////////////////////////////////

extern vfs_ctx_t          fs_context[FS_TYPES_NR];   // allocated in vfs.c file

extern remote_barrier_t   global_barrier;            // allocated in kernel_init.c
 
extern chdev_directory_t  chdev_dir;                 // allocated in kernel_init.c

////////////////////////////////////////////////////////////////////////////////////////
//                DEVFS private functions
////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////
// This function creates in the local cluster the dentry and the associated inode,
// for a DEVFS directory (level 0 or level 1 in DEVFS tree).
////////////////////////////////////////////////////////////////////////////////////////
// @ name        : directory entry name.
// @ parent_xp   : extended pointer on parent inode.
// @ inode_xp    : [out] buffer for extended pointer on created inode.
////////////////////////////////////////////////////////////////////////////////////////
static void devfs_create_directory( char        * name,
                                    xptr_t        parent_xp,
                                    xptr_t      * inode_xp )
{
    error_t       error;
    xptr_t        new_dentry_xp;     // extended pointer on created dentry
    xptr_t        new_inode_xp;      // extended pointer on created inode
 
    // get parent inode cluster and local pointer
    cxy_t         parent_cxy = GET_CXY( parent_xp );
    vfs_inode_t * parent_ptr = (vfs_inode_t *)GET_PTR( parent_xp );

    // create vfs_dentry in cluster containing the parent inode
    if( parent_cxy == local_cxy )
    {
        error = vfs_dentry_create( FS_TYPE_DEVFS,
                                   name,
                                   parent_ptr,
                                   &new_dentry_xp );
    }
    else
    {
        rpc_vfs_dentry_create_client( parent_cxy,
                                      FS_TYPE_DEVFS,
                                      name,
                                      parent_ptr,
                                      &new_dentry_xp,
                                      &error );
    }

    if ( error )
    {
        printk("\n[PANIC] in %s : cannot create dentry for %s in cluster %x/n",
               __FUNCTION__ , name , local_cxy );
        hal_core_sleep();
    }

    // create vfs_inode in local cluster TODO define the 4 arguments below [AG]
    uint32_t  attr   = 0;
    uint32_t  rights = 0;
    uid_t     uid    = 0;
    gid_t     gid    = 0;
    error = vfs_inode_create( new_dentry_xp,
                              FS_TYPE_DEVFS,
                              INODE_TYPE_DIR,
                              attr,
                              rights,
                              uid,
                              gid,
                              &new_inode_xp );
    if( error )
    {
        printk("\n[PANIC] in %s : cannot create inode for %s in cluster %x/n",
               __FUNCTION__ , name , local_cxy );
        hal_core_sleep();
    }

    // return extended pointer on directory inode
    *inode_xp = new_inode_xp;

}  // end devfs_create_directory()
                                    
////////////////////////////////////////////////////////////////////////////////////////
// This function creates in the local cluster the dentry and the associated inode,
// for a chdev (level 2 in DEVFS tree).
////////////////////////////////////////////////////////////////////////////////////////
// @ chdev    : local pointer on local chdev.
// @ name     : directory entry name.
// @ parent   : local pointer on local parent inode.
// @ inode_xp : [out] buffer for extended pointer on created inode.
////////////////////////////////////////////////////////////////////////////////////////
static void devfs_register_chdev( chdev_t     * chdev,
                                  char        * name,
                                  vfs_inode_t * parent,
                                  xptr_t      * inode_xp )
{
    error_t  error;
    xptr_t   new_dentry_xp;
    xptr_t   new_inode_xp;

    devfs_dmsg("\n[INFO] %s : create dentry for %s\n", __FUNCTION__ , name );
    
    // create vfs_dentry in local cluster
    error = vfs_dentry_create( FS_TYPE_DEVFS,
                               name,
                               parent,
                               &new_dentry_xp );
    if ( error )
    {
        printk("\n[PANIC] in %s : cannot create dentry for %s in cluster %x/n",
               __FUNCTION__ , name , local_cxy );
        hal_core_sleep();
    }

    devfs_dmsg("\n[INFO] %s : create inode for %s\n", __FUNCTION__ , name );
    
    // create vfs_inode in local cluster
    uint32_t  attr   = 0;
    uint32_t  rights = 0;
    uid_t     uid    = 0;
    gid_t     gid    = 0;
    error = vfs_inode_create( new_dentry_xp,
                              FS_TYPE_DEVFS,
                              INODE_TYPE_DEV,
                              attr,
                              rights,
                              uid,
                              gid,
                              &new_inode_xp );
    if( error )
    {
        printk("\n[PANIC] in %s : cannot create inode for %s in cluster %x/n",
               __FUNCTION__ , name , local_cxy );
        hal_core_sleep();
    }

    // return extended pointer on chdev inode
    *inode_xp = new_inode_xp;
    
}  // end devfs_register_chdev()



///////////////////////////////////////////////////////////////////////////////////////
// Generic API : the following functions are called by the VFS, 
//               and must be defined by all supported file systems.
///////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////

////////////////////////////////////////////
error_t devfs_mount( xptr_t   parent_inode_xp,
                     char   * devfs_root_name )
{
    assert( (CURRENT_CORE->lid == 0) , __FUNCTION__ , "only CP0 should do it" );

    vfs_inode_t * parent_inode_ptr;
    cxy_t         parent_inode_cxy;
    vfs_ctx_t   * vfs_ctx;

    char          node_name[16];
    uint32_t      channel;

    xptr_t        root_inode_xp;
    xptr_t        external_inode_xp;
    xptr_t        internal_inode_xp;
    xptr_t        chdev_inode_xp;

    chdev_t     * chdev_ptr;

    // get number of kernel instances and extended pointer on global barrier 
    cluster_t * cluster     = LOCAL_CLUSTER;
    uint32_t    nb_clusters = cluster->x_size * cluster->y_size;
    xptr_t      barrier_xp  = XPTR( cluster->io_cxy , &global_barrier );

    // get VFS root inode cluster and local pointer
    parent_inode_cxy = GET_CXY( parent_inode_xp );
    parent_inode_ptr = (vfs_inode_t *)GET_PTR( parent_inode_xp );

    // get local pointer on VFS context for DEVFS
    vfs_ctx = &fs_context[FS_TYPE_DEVFS];

    ///// step 1 : all clusters initialize local DEVFS context  /////

    devfs_ctx_init( vfs_ctx , parent_inode_xp );

    ///// step 2 : cluster_0 creates DEVFS root    /////

    if( local_cxy == 0 )
    {
        devfs_create_directory( devfs_root_name,
                                parent_inode_xp,
                                &root_inode_xp );
    }

    // synchronize all clusters
    remote_barrier( barrier_xp , nb_clusters );

    ///// step 3 : all clusters create "internal" directory and chdevs  /////

    // TODO check device existence : (chdev_xp != XPTR_NULL) in chdev_dir

    snprintf( node_name , 16 , "internal_%x" , local_cxy );

    devfs_create_directory( node_name,
                            root_inode_xp,
                            &internal_inode_xp );

    // create ICU chdev inode
    chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.icu[local_cxy] );
    devfs_register_chdev( chdev_ptr,
                          "icu",
                          (vfs_inode_t *)GET_PTR( internal_inode_xp ),
                          &chdev_inode_xp );

    // create MMC chdev inode
    chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.mmc[local_cxy] );
    devfs_register_chdev( chdev_ptr,
                          "mmc", 
                          (vfs_inode_t *)GET_PTR( internal_inode_xp ),
                          &chdev_inode_xp );

    // create DMA chdev inodes (one DMA channel per core)
    for( channel = 0 ; channel < cluster->cores_nr ; channel++ )
    {
        chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.dma[channel] );
        snprintf( node_name , 16 , "dma_%d" , channel );
        devfs_register_chdev( chdev_ptr,
                              node_name,
                              (vfs_inode_t *)GET_PTR( internal_inode_xp ),
                              &chdev_inode_xp );
    }

    ///// step 4 : cluster_io creates "external" directory and chdevs /////

    // TODO check device existence : (chdev_xp != XPTR_NULL) in chdev_dir

    if( local_cxy == cluster->io_cxy )
    {
        devfs_create_directory( "external",
                                root_inode_xp,
                                &external_inode_xp );

        // create IOB chdev inode
        chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.iob );
        devfs_register_chdev( chdev_ptr,
                              "iob",
                              (vfs_inode_t *)GET_PTR( external_inode_xp ),
                              &chdev_inode_xp );
        
        // create PIC chdev inode
        chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.pic );
        devfs_register_chdev( chdev_ptr,
                              "pic",
                              (vfs_inode_t *)GET_PTR( external_inode_xp ),
                              &chdev_inode_xp );

        // create TXT chdev inodes
        for( channel = 0 ; channel < CONFIG_MAX_TXT_CHANNELS ; channel++ )
        {
            chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.txt[channel] );
            snprintf( node_name , 16 , "txt_%d" , channel );
            devfs_register_chdev( chdev_ptr,
                                  node_name,
                                  (vfs_inode_t *)GET_PTR( external_inode_xp ),
                                  &chdev_inode_xp );
        }

        // create IOC chdev inodes
        for( channel = 0 ; channel < CONFIG_MAX_IOC_CHANNELS ; channel++ )
        {
            chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.ioc[channel] );
            snprintf( node_name , 16 , "ioc_%d" , channel );
            devfs_register_chdev( chdev_ptr,
                                  node_name,
                                  (vfs_inode_t *)GET_PTR( external_inode_xp ),
                                  &chdev_inode_xp );
        }

        // create FBF chdev inodes
        for( channel = 0 ; channel < CONFIG_MAX_IOC_CHANNELS ; channel++ )
        {
            chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.ioc[channel] );
            snprintf( node_name , 16 , "fbf_%d" , channel );
            devfs_register_chdev( chdev_ptr,
                                  node_name,
                                  (vfs_inode_t *)GET_PTR( external_inode_xp ),
                                  &chdev_inode_xp );
        }

        // create NIC_RX chdevs
        for( channel = 0 ; channel < CONFIG_MAX_NIC_CHANNELS ; channel++ )
        {
            chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.nic_rx[channel] );
            snprintf( node_name , 16 , "nic_rx_%d" , channel );
            devfs_register_chdev( chdev_ptr,
                                  node_name,
                                  (vfs_inode_t *)GET_PTR( external_inode_xp ),
                                  &chdev_inode_xp );
        }

        // create NIC_TX chdev inodes
        for( channel = 0 ; channel < CONFIG_MAX_NIC_CHANNELS ; channel++ )
        {
            chdev_ptr = (chdev_t *)GET_PTR( chdev_dir.nic_tx[channel] );
            snprintf( node_name , 16 , "nic_tx_%d" , channel );
            devfs_register_chdev( chdev_ptr,
                                  node_name,
                                  (vfs_inode_t *)GET_PTR( external_inode_xp ),
                                  &chdev_inode_xp );
        }
    }

    // synchronize all clusters
    remote_barrier( barrier_xp , nb_clusters );

    return 0;

}  // end devfs_init()


////////////////////////////////////////////
error_t devfs_ctx_init( vfs_ctx_t * vfs_ctx,
                        xptr_t      root_inode_xp )
{
    vfs_ctx->type    = FS_TYPE_DEVFS;
    vfs_ctx->attr    = 0;                // not READ_ONLY / not SYNC
    vfs_ctx->count   = 0;                // unused for DEVFS
    vfs_ctx->blksize = 0;                // unused for DEVFS
    vfs_ctx->root_xp = root_inode_xp;
    vfs_ctx->extend  = NULL;             // unused for DEVFS

    spinlock_init( &vfs_ctx->lock );

    bitmap_init( vfs_ctx->bitmap , CONFIG_VFS_MAX_INODES );

    return 0;
}


////////////////////////////////////////////////////
error_t devfs_inode_create( vfs_inode_t * vfs_inode,
                            chdev_t     * chdev )
{
    kmem_req_t      req;
    devfs_inode_t * devfs_inode;

    // allocate memory for FATFS inode extension
	req.type    = KMEM_DEVFS_INODE;
	req.size    = sizeof(devfs_inode_t);
    req.flags   = AF_KERNEL | AF_ZERO;
	devfs_inode = (devfs_inode_t *)kmem_alloc( &req );

    if( devfs_inode == NULL ) return ENOMEM;

    // link DEVFS inode to VFS inode
    vfs_inode->extend = devfs_inode;

    // initialise DEVFS inode  
    devfs_inode->chdev = chdev;
 
    return 0;
}

///////////////////////////////////////////////////
void devfs_inode_destroy( vfs_inode_t * vfs_inode )
{
    kmem_req_t      req;
    devfs_inode_t * devfs_inode;

    // get pointer on DEVFS inode 
    devfs_inode = (devfs_inode_t *)vfs_inode->extend;

    req.type = KMEM_DEVFS_INODE;
    req.ptr  = devfs_inode;
    kmem_free( &req );

	vfs_inode->extend = NULL;
}


/* deprecated [AG]

error_t devfs_open_file( vfs_file_t * file,
                         void       * extend );
{
	error_t err;
	register struct devfs_context_s *ctx;
	register struct devfs_file_s *info;
	chdev_t       * chdev;
	vfs_inode_t   * inode;
	dev_request_t   rq;
	kmem_req_t      req;

	inode = file->inode;

	info = file->fr_pv;
	ctx  = (struct devfs_context_s *)&inode->i_ctx->ctx_devfs;

	if(!(inode->i_attr & VFS_DIR))
	{
		dev = (struct device_s*)inode->i_pv;
    
		if(dev->type == DEV_INTERNAL)
			return EPERM;

		if(dev->type == DEV_BLK)
			VFS_SET(inode->i_attr,VFS_DEV_BLK);
		else
			VFS_SET(inode->i_attr,VFS_DEV_CHR);
 
		if(dev->op.dev.open != NULL)
		{
			rq.fremote = file;
			if((err=dev->op.dev.open(dev, &rq)))
				return err;
		}

		priv->dev = (void*)dev;

		return 0;
	}

	if(info == NULL)
	{
		req.type  = KMEM_DEVFS_FILE;
		req.size  = sizeof(*info);
		req.flags = AF_KERNEL;
		info      = kmem_alloc(&req);
	}

	if(info == NULL) return ENOMEM;

	metafs_iter_init(&devfs_db.root, &info->iter);
	info->ctx  = ctx;
	file->fr_pv = info;
  
	metafs_print(&devfs_db.root);
	return 0;
}

#define TMP_BUFF_SZ 512

//FIXME:
//add a "while" loop for the case where the
//buffer TMP_BUFF_SZ is smaller than
//buffer->size
//////////////////////////////
devfs_read( vfs_file_t * file,
            char       * buffer )
{
	chdev_t       * chdev;
	dev_request_t   rq;
	uint32_t        count;
    uint8_t         buff[TMP_BUFF_SZ];

    // get pointer on chdev
	chdev = (chdev_t *)file->extend;

    if( chdev->func == DEV_FUNC_TXT )
    {
    }
    if( chdev->func == DEV_FUNC_IOC )
    {
    }
    else
    {
        printk("\n[PANIC] in %s : illegal device functionnal type

	rq.dst   = &buff[0];
	rq.count = TMP_BUFF_SZ;
	rq.flags = 0;
	rq.file  = file;

	if((count = dev->op.dev.read(dev, &rq)) < 0)
		return count;

        buffer->scpy_to_buff(buffer, &buff[0], count);
	return count;
}

//FIXME: To improve this an avoid the extra copy,
//we could set along with the buffer(src and dest) 
//the functions to manipulate them, such as in 
//do_exec.c
///////////////////////////////
devfs_write( vfs_file_t * file,
             char       * buffer )
{
	register struct device_s *dev;
	uint8_t buff[TMP_BUFF_SZ];
	dev_request_t rq;
	
	dev = (struct device_s*)file->f_private.dev;
	
	//FIXME avoid the extra copy ?
	buffer->scpy_from_buff(buffer, (void*)&buff[0], TMP_BUFF_SZ);
	rq.src   = (void*)&buff[0];
	rq.count = buffer->size;
	rq.flags = 0;
	rq.file  = file;
  
	return dev->op.dev.write(dev, &rq);
}

VFS_LSEEK_FILE(devfs_lseek)
{
	register struct device_s *dev;
	dev_request_t rq;

	dev = (struct device_s*)file->fr_inode->i_pv;

	if(dev->op.dev.lseek == NULL)
		return VFS_ENOTUSED;
  
	rq.fremote = file;
	return dev->op.dev.lseek(dev, &rq);
}

VFS_CLOSE_FILE(devfs_close)
{
	register struct device_s *dev;
	dev_request_t rq;

	if(file->fr_inode->i_attr & VFS_DIR)
		return 0;

	dev = (struct device_s*)file->fr_inode->i_pv;

	if(dev->op.dev.close == NULL)
		return 0;
  
	rq.fremote = file;
	return dev->op.dev.close(dev, &rq);
}

VFS_RELEASE_FILE(devfs_release)
{  
	kmem_req_t req;

	if(file->fr_pv == NULL) 
		return 0;
  
	req.type = KMEM_DEVFS_FILE;
	req.ptr  = file->fr_pv;
	kmem_free(&req);

	file->fr_pv = NULL;
	return 0;
}

VFS_READ_DIR(devfs_readdir)
{
	register struct devfs_file_s *info;
	register struct metafs_s *current;
	register struct device_s *current_dev;
  
	info = file->fr_pv;
  
	if(file->fr_pv == NULL)
		return ENOTDIR;

	if((current = metafs_lookup_next(&devfs_db.root, &info->iter)) == NULL)
		return EEODIR;

	current_dev    = metafs_container(current, struct device_s, node);
	dirent->u_attr = (current_dev->type == DEV_BLK) ? VFS_DEV_BLK : VFS_DEV_CHR;

	strcpy((char*)dirent->u_name, metafs_get_name(current));

	dirent->u_ino = (uint_t) current_dev->base_paddr;

	return 0;
}

VFS_MMAP_FILE(devfs_mmap)
{
	register struct device_s *dev;
	dev_request_t rq;
  
	dev = (struct device_s*)file->f_private.dev;

	if(dev->op.dev.mmap == NULL)
		return ENODEV;

	rq.flags  = 0;
	rq.file   = file;
	rq.region = region;

	return dev->op.dev.mmap(dev, &rq);
}

VFS_MMAP_FILE(devfs_munmap)
{
	register struct device_s *dev;
	dev_request_t rq;

	dev = (struct device_s*)file->f_private.dev;

	if(dev->op.dev.munmap == NULL)
		return ENODEV;

	rq.flags  = 0;
	rq.file   = file;
	rq.region = region;

	return dev->op.dev.munmap(dev, &rq);
}


*/


