/*
 * elf.c - elf parser: find and map process CODE and DATA segments
 *
 * Authors   Alain Greiner    (2016,2017,2018,2019,2020)
 *
 * 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_kernel_types.h>
#include <hal_special.h>
#include <hal_uspace.h>
#include <printk.h>
#include <process.h>
#include <thread.h>
#include <mapper.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_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_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 static 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_ORDER;
		}
		else               // found DATA segment
		{
			type                       = VSEG_TYPE_DATA;
			process->vmm.data_vpn_base = vbase >> CONFIG_PPM_PAGE_ORDER;
		}

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

        // get local pointer on .elf file mapper
        mapper_t * mapper_ptr = (mapper_t *)hal_remote_lpt( XPTR( file_cxy , 
                                                                  &file_ptr->mapper ) );
		// register vseg in VMM
		vseg = (vseg_t *)vmm_create_vseg( process,
                                          type,
		                                  vbase,
		                                  mem_size,
                                          file_offset,
                                          file_size,
                                          XPTR( file_cxy , mapper_ptr ),
		                                  local_cxy );  
		if( vseg == NULL )
		{
			printk("\n[ERROR] in %s : cannot map segment / base = %x / size = %x\n",
			       __FUNCTION__ , vbase , mem_size );
			return -1;
		}

#if DEBUG_ELF_LOAD
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_ELF_LOAD < cycle )
printk("\n[%s] thread[%x,%x] found %s vseg / base %x / size %x\n"
"  file_size %x / file_offset %x / mapper[%x,%x] / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, 
vseg_type_str(vseg->type) , vseg->min , vseg->max - vseg->min ,
vseg->file_size , vseg->file_offset , 
GET_CXY(vseg->mapper_xp), GET_PTR(vseg->mapper_xp), cycle );
#endif

	}

	return 0;

} // end elf_segments_register()

//////////////////////////////////////////////
error_t elf_load_process( xptr_t      file_xp,
                          process_t * process )
{
    uint32_t     new_offset;       // unused, required by vfs_lseek() 
	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
    char         name[CONFIG_VFS_MAX_NAME_LENGTH];
	error_t      error;

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

    // get inode pointer
    vfs_inode_t * inode_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->inode ) );
 
    // get file name for error reporting and debug
    vfs_inode_get_name( XPTR( file_cxy , inode_ptr ) , name );
    
#if DEBUG_ELF_LOAD
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_ELF_LOAD < cycle )
printk("\n[%s] thread[%x,%x] enter for <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, name, cycle );
#endif

	// 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 for <%s>\n", __FUNCTION__ , name );
		return -1;
	}

#if (DEBUG_ELF_LOAD & 1)
if( DEBUG_ELF_LOAD < cycle )
printk("\n[%s] loaded elf header for <%s>\n"
       "      - size    = %x\n"
       "      - version = %x\n"
       "      - entry   = %x\n"
       "      - machine = %x\n"
       "      - offset  = %x\n"
       "      - segnum  = %x\n",
       __FUNCTION__, name,
       header.e_ehsize,
       header.e_version,
       header.e_entry,
       header.e_machine,
       header.e_phoff,
       header.e_phnum );
#endif

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

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

	// allocate memory for segment descriptors array
	segs_base = kmem_alloc( bits_log2(segs_size) , AF_NONE );

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

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

	if( error )
	{
		printk("\n[ERROR] in %s : cannot seek for descriptors array\n", __FUNCTION__ );
		kmem_free( segs_base , bits_log2(segs_size) );
		return -1;
	}

#if (DEBUG_ELF_LOAD & 1)
if( DEBUG_ELF_LOAD < cycle )
printk("\n[%s] segments array allocated for <%s>\n", __FUNCTION__ , name );
#endif

	// 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__ );
		kmem_free( segs_base , bits_log2(segs_size) );
		return -1;
	}

#if (DEBUG_ELF_LOAD & 1)
if( DEBUG_ELF_LOAD < cycle )
printk("\n[%s] loaded segments descriptors for <%s>\n", __FUNCTION__ , name );
#endif

	// register loadable segments in process VMM
	error = elf_segments_register( file_xp,
	                               segs_base,
	                               header.e_phnum,
	                               process );
	if( error )
	{
		printk("\n[ERROR] in %s : cannot register segments descriptors\n", __FUNCTION__ );
		kmem_free( segs_base , bits_log2(segs_size) );
		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
    kmem_free( segs_base , bits_log2(segs_size) );

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

	return 0;

}  // end elf_load_process()

