/*
 * elf.c - elf parser: find and map process CODE and DATA segments
 *
 * 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_uspace.h>
#include <printk.h>
#include <process.h>
#include <vseg.h>
#include <kmem.h>
#include <vfs.h>
#include <elf.h>
#include <syscalls.h>

///////////////////////////////////////////////////////////////////
// This static function checks the .elf header.
// - return true if legal header.
// - return false with an error message if illegal header.
///////////////////////////////////////////////////////////////////
static bool_t elf_isValidHeader(Elf_Ehdr *header)
{
	if((header->e_ident[EI_CLASS] == ELFCLASS)
	   && (header->e_ident[EI_DATA] == ELFDATA2LSB)
	   && (header->e_ident[EI_VERSION] == EV_CURRENT)
/*
	   && (header->e_ident[EI_OSABI] == ELFOSABI_NONE)
*/
	   && ((header->e_machine == EM_MIPS) ||
	       (header->e_machine == EM_MIPS_RS3_LE) ||
	       (header->e_machine == EM_X86_64))
	   && (header->e_type == ET_EXEC))
		return true;

	if( header->e_ident[EI_CLASS] != ELFCLASS )
		printk("\n[ERROR] in %s : Elf is not 32/64-Binary\n", __FUNCTION__ );

	if( header->e_ident[EI_DATA] != ELFDATA2LSB )
		printk("\n[ERROR] in %s : Elf is not 2's complement, little endian\n", __FUNCTION__ );

	if( header->e_ident[EI_VERSION] != EV_CURRENT )
		printk("\n[ERROR] in %s : Elf is not in Current Version\n", __FUNCTION__);
/*
	if( header->e_ident[EI_OSABI] != ELFOSABI_NONE )
		printk("\n[ERROR] in %s : Unexpected Elf ABI, need UNIX System V ABI\n", __FUNCTION__ );
*/
	if( (header->e_machine != EM_MIPS) &&
	    (header->e_machine != EM_MIPS_RS3_LE) &&
	    (header->e_machine != EM_X86_64) )
		printk("\n[ERROR] in %s : unexpected core / accept only MIPS or x86_64\n", __FUNCTION__ );

	if( header->e_type != ET_EXEC )
		printk("\n[ERROR] in %s : Elf is not executable binary\n", __FUNCTION__ );

	return false;
}

///////////////////////////////////////////////////////////////////////////////////////
// This function loads the .elf header in the buffer allocated by the caller.
///////////////////////////////////////////////////////////////////////////////////////
// @ file   : extended pointer on the remote file descriptor.
// @ buffer : pointer on buffer allocated by the caller.
// @ size   : number of bytes to read.
///////////////////////////////////////////////////////////////////////////////////////
static error_t elf_header_load( xptr_t   file_xp,
                                void   * buffer,
                                uint32_t size )
{
	error_t   error;
	xptr_t    buf_xp;

	buf_xp = XPTR( local_cxy , buffer );

	// load .elf header
	error = vfs_kernel_move( true,     // to_buffer
	                         file_xp,
	                         buf_xp,
	                         size );

	if( error )
	{
		printk("\n[ERROR] in %s : cannot read ELF header size : %d\n",
               __FUNCTION__ , size );
		return -1;
	}

	Elf_Ehdr * header = (Elf_Ehdr *)buffer;

	if( (header->e_ident[EI_MAG0] != ELFMAG0) ||
	    (header->e_ident[EI_MAG1] != ELFMAG1) ||
	    (header->e_ident[EI_MAG2] != ELFMAG2) ||
	    (header->e_ident[EI_MAG3] != ELFMAG3) )
	{
		printk("\n[ERROR] in %s : file not in ELF format\n", __FUNCTION__ );
		return -1;
	}

	if( !(elf_isValidHeader( header ) ) )
	{
		printk("\n[ERROR] in %s : not supported Elf\n", __FUNCTION__ );
		return -1;
	}
	return 0;

} // end elf_header_load()

///////////////////////////////////////////////////////////////////////////////////////
// This function registers in the process VMM the CODE and DATA segments.
///////////////////////////////////////////////////////////////////////////////////////
// @ file      : extended pointer on the remote file descriptor.
// @ segs_base : local pointer on buffer containing the segments descriptors array
// @ segs_nr   : number of segments in segment descriptors array.
// @ process   : local pointer on process descriptor.
///////////////////////////////////////////////////////////////////////////////////////
static error_t elf_segments_register( xptr_t       file_xp,
                                      void       * segs_base,
                                      uint32_t     nb_segs,
                                      process_t  * process )
{
	uint32_t     index;
	intptr_t     file_size;
	intptr_t     mem_size;
	intptr_t     file_offset;
	intptr_t     vbase;
	uint32_t     type;
	uint32_t     flags;
	vseg_t     * vseg;

	Elf_Phdr * seg_ptr = (Elf_Phdr *)segs_base;

	// loop on segments
	for( index = 0 ; index < nb_segs ; index++ , seg_ptr++ )
	{
		if( seg_ptr->p_type != PT_LOAD)
			continue;

		// get segment attributes
		vbase       = seg_ptr->p_vaddr;     // vseg base vaddr
		mem_size    = seg_ptr->p_memsz;     // actual vseg size
		file_offset = seg_ptr->p_offset;    // vseg offset in .elf file
		file_size   = seg_ptr->p_filesz;    // vseg size in .elf file
		flags       = seg_ptr->p_flags;

		if( flags & PF_X ) // found CODE segment
		{
			type                       = VSEG_TYPE_CODE;
			process->vmm.code_vpn_base = vbase >> CONFIG_PPM_PAGE_SHIFT;

			elf_dmsg("\n[INFO] %s : found CODE vseg / base = %x / size = %x\n",
			         __FUNCTION__ , vbase , mem_size );
		}
		else               // found DATA segment
		{
			type                       = VSEG_TYPE_DATA;
			process->vmm.data_vpn_base = vbase >> CONFIG_PPM_PAGE_SHIFT;

			elf_dmsg("\n[INFO] %s : found DATA vseg / base = %x / size = %x\n",
			         __FUNCTION__, vbase , mem_size );
		}

		// register vseg in VMM
		vseg = (vseg_t *)vmm_create_vseg( process,
		                                  vbase,
		                                  mem_size,
		                                  type );
		if( vseg == NULL )
		{
			printk("\n[ERROR] in %s : cannot map segment / base = %x / size = %x\n",
			       __FUNCTION__ , vbase , mem_size );
			return -1;
		}

        // get .elf file descriptor cluster and local pointer 
        cxy_t        file_cxy = GET_CXY( file_xp );
        vfs_file_t * file_ptr = (vfs_file_t *)GET_PTR( file_xp );

        // initialize "file_mapper", "file_offset", "file_size" fields in vseg 
        vseg->mapper_xp   = (xptr_t)hal_remote_lwd( XPTR( file_cxy , &file_ptr->mapper ) );
        vseg->file_offset = file_offset;
        vseg->file_size   = file_size;

        // update reference counter in file descriptor
		vfs_file_count_up( file_xp );
	}

	return 0;

} // end elf_segments_register()

///////////////////////////////////////////////
error_t elf_load_process( char      * pathname,
                          process_t * process)
{
	kmem_req_t   req;              // kmem request for program header
	Elf_Ehdr     header;           // local buffer for .elf header
	void       * segs_base;        // pointer on buffer for segment descriptors array
	uint32_t     segs_size;        // size of buffer for segment descriptors array
	xptr_t       file_xp;          // extended pointer on created file descriptor
	uint32_t     file_id;          // file descriptor index (unused)
	error_t      error;

    elf_dmsg("\n[INFO] %s : enters for <%s>\n", __FUNCTION__ , pathname );

    // avoid GCC warning
	file_xp = XPTR_NULL;  
	file_id = -1;

	// open file
	error = vfs_open( process->vfs_cwd_xp,
	                  pathname,
	                  O_RDONLY,
	                  0,
	                  &file_xp,
	                  &file_id );
	if( error )
	{
		printk("\n[ERROR] in %s : failed to open file %s\n", __FUNCTION__ , pathname );
		return -1;
	}

    elf_dmsg("\n[INFO] %s : open file <%s>\n", __FUNCTION__ , pathname );

	// load header in local buffer
	error = elf_header_load( file_xp ,
	                         &header,
	                         sizeof(Elf_Ehdr) );
	if( error )
	{
		printk("\n[ERROR] in %s : cannot get header file %s\n", __FUNCTION__ , pathname );
		vfs_close( file_xp , file_id );
		return -1;
	}

	elf_dmsg("\n[INFO] %s : loaded elf header for %s\n", __FUNCTION__ , pathname );

	if( header.e_phnum == 0 )
	{
		printk("\n[ERROR] in %s : no segments found\n", __FUNCTION__ );
		vfs_close( file_xp , file_id );
		return -1;
	}

	// compute buffer size for segment descriptors array
	segs_size = sizeof(Elf_Phdr) * header.e_phnum;

	// allocate memory for segment descriptors array
	req.type  = KMEM_GENERIC;
	req.size  = segs_size;
	req.flags = AF_KERNEL;
	segs_base = kmem_alloc( &req );

	if( segs_base == NULL )
	{
		printk("\n[ERROR] in %s : no memory for segment descriptors\n", __FUNCTION__ );
		vfs_close( file_xp , file_id );
		return -1;
	}

	// set seek pointer in file descriptor to access segment descriptors array
	error = vfs_lseek( file_xp , header.e_phoff, SEEK_SET , NULL );

	if( error )
	{
		printk("\n[ERROR] in %s : cannot seek for descriptors array\n", __FUNCTION__ );
		vfs_close( file_xp , file_id );
		req.ptr = segs_base;
		kmem_free( &req );
		return -1;
	}

	elf_dmsg("\n[INFO] %s : segments array allocated for %s\n", __FUNCTION__ , pathname );

	// load seg descriptors array to local buffer
	error = vfs_kernel_move( true,                  // to_buffer
	                         file_xp,
	                         XPTR( local_cxy , segs_base ),
	                         segs_size );

	if( error )
	{
		printk("\n[ERROR] in %s : cannot read segments descriptors\n", __FUNCTION__ );
		vfs_close( file_xp , file_id );
		req.ptr = segs_base;
		kmem_free( &req );
		return -1;
	}

	elf_dmsg("\n[INFO] %s loaded segments descriptors for %s \n", __FUNCTION__ , pathname );

	// register loadable segments in process VMM
	error = elf_segments_register( file_xp,
	                               segs_base,
	                               header.e_phnum,
	                               process );
	if( error )
	{
		vfs_close( file_xp , file_id );
		req.ptr = segs_base;
		kmem_free( &req );
		return -1;
	}

	// register process entry point in VMM
	process->vmm.entry_point = (intptr_t)header.e_entry;

	// register extended pointer on .elf file descriptor
	process->vfs_bin_xp = file_xp;

	// release allocated memory for program header
	req.ptr = segs_base;
	kmem_free(&req);

	elf_dmsg("\n[INFO] %s successfully completed / entry point = %x for %s]\n",
		 __FUNCTION__, (uint32_t) header.e_entry , pathname );

	return 0;

}  // end elf_load_process()

