/*
 * 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(Elf32_Ehdr *header)
{
	if((header->e_ident[EI_CLASS] == ELFCLASS32)
	   && (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] != ELFCLASS32 )
		printk("\n[ERROR] in %s : Elf is not 32-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_read( xptr_t   file_xp,
                                void   * buffer,
                                uint32_t size )
{
	uint32_t  count;

	// load .elf header
	count = vfs_move( true ,
	                  file_xp,
	                  buffer,
	                  size );

	if( count != size )
	{
		printk("\n[ERROR] in %s : failed to read ELF header\n", __FUNCTION__ );
		return -1;
	}

	Elf32_Ehdr * header = (Elf32_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 %s 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;
}

///////////////////////////////////////////////////////////////////////////////////////
// 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_load( xptr_t       file_xp,
                                  void       * segs_base,
                                  uint32_t     nb_segs,
                                  process_t  * process )
{
	error_t      error;
	uint32_t     index;
	uint32_t     file_size;
	uint32_t     mem_size;
	intptr_t     start;
	uint32_t     type;
	uint32_t     flags;
	uint32_t     offset;
	vseg_t     * vseg;

	Elf32_Phdr * seg_ptr = (Elf32_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
		start     = seg_ptr->p_vaddr;
		offset    = seg_ptr->p_offset;
		file_size = seg_ptr->p_filesz;
		mem_size  = seg_ptr->p_memsz;
		flags     = seg_ptr->p_flags;

		// check alignment
		if( start & CONFIG_PPM_PAGE_MASK )
		{
			printk("\n[WARNING] in %s : segment base not aligned = %x\n",
			       __FUNCTION__, start );
		}

		// check size
		if( file_size != mem_size )
		{
			printk("\n[WARNING] in %s : base = %x / mem_size = %x / file_size = %x\n",
			       __FUNCTION__, start , mem_size , file_size);
		}

		// set seek on segment base in file
		error = vfs_lseek( file_xp,
		                   offset,
		                   SEEK_SET,
		                   NULL );

		if( error )
		{
			printk("\n[ERROR] in %s : failed to seek\n", __FUNCTION__ );
			return -1;
		}

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

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

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

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

		vfs_file_count_up( file_xp );
	}

	return 0;
}

///////////////////////////////////////////////
error_t elf_load_process( char      * pathname,
                          process_t * process)
{
	char         path_copy[CONFIG_VFS_MAX_PATH_LENGTH];
	kmem_req_t   req;              // kmem request for program header
	uint32_t     length;           // actual path length
	Elf32_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)
	uint32_t     count;            // bytes counter
	error_t      error;

	// get path length from user space
	length = hal_strlen_from_uspace( pathname );

	if( length >= CONFIG_VFS_MAX_PATH_LENGTH )
	{
		printk("\n[ERROR] in %s : pathname length too long\n", __FUNCTION__ );
		return -1;
	}

	// make a local copy for pathname
	hal_copy_from_uspace( path_copy , pathname , length+1 );

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

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

	// load header in local buffer
	error = elf_header_read( file_xp ,
	                         &header,
	                         sizeof(Elf32_Ehdr) );
	if( error )
	{
		vfs_close( file_xp , file_id );
		return -1;
	}

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

	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(Elf32_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;
	}

	// load seg descriptors array to local buffer
	count = vfs_move( true,
	                  file_xp,
	                  segs_base,
	                  segs_size );

	if( count != segs_size )
	{
		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__ , path_copy );

	// register loadable segments in process VMM
	error = elf_segments_load( 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 , path_copy );

	return 0;
}

