/*
 * hal_uspace.c - implementation of Generic User Space Access API for MIPS32
 * 
 * Author  Mohamed Karaoui (2015)
 *         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 <errno.h>
#include <vmm.h>
#include <hal_kernel_types.h>
#include <hal_uspace.h>
#include <hal_irqmask.h>

#include <printk.h>
#include <thread.h>

///////////////////////////////////////////
void hal_copy_from_uspace( void     * k_dst,
                           void     * u_src,
                           uint32_t   size )  
{
    uint32_t save_sr;
	uint32_t i;
	uint32_t wsize;                        // number of words 
    uint32_t src = (uint32_t)u_src;
    uint32_t dst = (uint32_t)k_dst;

#if DEBUG_HAL_USPACE
thread_t * this = CURRENT_THREAD;
printk("\n[%s] thread[%x,%x] enter in cluster %x / u_src %x / k_dst %x / size %d\n", 
__FUNCTION__, this->process->pid, this->trdid, local_cxy, u_src, k_dst, size );
#endif

	if( (dst & 0x3) || (src & 0x3) ) wsize = 0;          // do it all in bytes
    else                             wsize = size >> 2;

    hal_disable_irq( &save_sr );


	for( i = 0 ; i < wsize ; i++ )          // transfer one word per iteration
	{
        asm volatile(
        "mfc2   $15,   $1           \n"   /* save   MMU_MODE                */
        "ori    $14,   $0,  0x7	    \n"  
        "mtc2   $14,   $1			\n"   /* MMU_MODE <= DTLB ON            */
        "lw	    $13,   0(%0)        \n"   /* read data from user space      */
        "mtc2   $15,   $1			\n"   /* restore MMU_MODE               */
	    "sw	    $13,   0(%1)        \n"   /* store data to kernel space     */
        : : "r"( src ) , "r"( dst ) : "$13","$14","$15", "memory" );

        src += 4;
        dst += 4;
    }

	for( i = wsize << 2 ; i < size ; i++ )  // transfer one byte per iteration
	{
        asm volatile(
        "mfc2   $15,   $1           \n"   /* save   MMU_MODE                */
        "ori    $14,   $0,  0x7	    \n"  
        "mtc2   $14,   $1			\n"   /* MMU_MODE <= DTLB ON            */
        "lb	    $13,   0(%0)        \n"   /* read data from user space      */
        "mtc2   $15,   $1			\n"   /* restore MMU_MODE               */
	    "sb	    $13,   0(%1)        \n"   /* store data to kernel space     */
        : : "r"( src ) , "r"( dst ) : "$13","$14","$15", "memory" );

        src += 1;
        dst += 1;
    }

    hal_restore_irq( save_sr );

#if DEBUG_HAL_USPACE
printk("\n[%s] thread[%x,%x] exit\n", 
__FUNCTION__, this->process->pid, this->trdid );
#endif

}  // end hal_copy_from_uspace()

///////////////////////////////////////////
void hal_copy_to_uspace( void     * u_dst,
                         void     * k_src,
                         uint32_t   size )
{
    uint32_t save_sr;
	uint32_t i;
	uint32_t wsize;                   // number of words if aligned
    uint32_t src = (uint32_t)k_src;
    uint32_t dst = (uint32_t)u_dst;

#if DEBUG_HAL_USPACE
thread_t * this = CURRENT_THREAD;
printk("\n[%s] thread[%x,%x] enter in cluster %x / k_src %x / u_dst %x / size %d\n", 
__FUNCTION__, this->process->pid, this->trdid, local_cxy, k_src, u_dst, size );
#endif

	if( (dst & 0x3) || (src & 0x3) ) wsize = 0;          // not aligned
    else                             wsize = size >> 2;

    hal_disable_irq( &save_sr );

	for( i = 0 ; i < wsize ; i++ )          // transfer one word per iteration
	{
        asm volatile(
        "mfc2   $15,   $1           \n"   /* save   MMU_MODE                */
        "lw	    $13,   0(%0)        \n"   /* read data from kernel space    */
        "ori    $14,   $0,  0x7	    \n"  
        "mtc2   $14,   $1			\n"   /* MMU_MODE <= DTLB ON            */
	    "sw	    $13,   0(%1)        \n"   /* store data to user space       */
        "mtc2   $15,   $1			\n"   /* restore MMU_MODE               */
        : : "r"( src ) , "r"( dst ) : "$13","$14","$15", "memory" );

        src += 4;
        dst += 4;
    }

	for( i = wsize << 2 ; i < size ; i++ )  // transfer one byte per iteration
	{
        asm volatile(
        "mfc2   $15,   $1           \n"   /* save   MMU_MODE                */
        "lb	    $13,   0(%0)        \n"   /* read data from kernel space    */
        "ori    $14,   $0,  0x7	    \n"  
        "mtc2   $14,   $1			\n"   /* MMU_MODE <= DTLB ON            */
	    "sb	    $13,   0(%1)        \n"   /* store data to user space       */
        "mtc2   $15,   $1			\n"   /* restore MMU_MODE               */
        : : "r"( src ) , "r"( dst ) : "$13","$14","$15", "memory" );

        src += 1;
        dst += 1;
    }

    hal_restore_irq( save_sr );

#if DEBUG_HAL_USPACE
printk("\n[%s] thread[%x,%x] exit\n", 
__FUNCTION__, this->process->pid, this->trdid );
#endif

}  // end hal_copy_to_uspace()

//////////////////////////////////////////////
void hal_strcpy_from_uspace( char     * k_dst,
                             char     * u_src,
                             uint32_t   size )
{
    uint32_t save_sr;
    uint32_t src = (uint32_t)u_src;
    uint32_t dst = (uint32_t)k_dst;

    hal_disable_irq( &save_sr );

    // loop on characters while ( (character != NUL) and (count < size ) )
    asm volatile(
        ".set noreorder             \n"
        "move   $11,   %0           \n"   /* $11 <= count == size           */
        "move   $12,   %1           \n"   /* $12 <= u_src                   */
        "move   $13,   %2           \n"   /* $13 <= k_dst                   */
        "mfc2   $15,   $1           \n"   /* $15 <= mode DTLB and ITLB off  */
        "ori    $14,   $15,  0x4    \n"   /* $14 <= mode DTLB on            */
        "1:                         \n"
        "mtc2   $14,   $1			\n"   /* MMU_MODE <= DTLB ON            */
        "lb     $10,   0($12)       \n"   /* read char from user space      */
        "mtc2   $15,   $1			\n"   /* restore DTLB and ITLB off      */
	    "sb	    $10,   0($13)       \n"   /* store char to kernel space     */
        "beq    $10,   $0,   2f     \n"   /* exit if char = 0               */
        "addi   $11,   $11, -1      \n"   /* decrement count                */
        "addi   $12,   $12,  1      \n"   /* increment u_src pointer        */
        "beq    $11,   $0,   2f     \n"   /* exit if count == 0             */
        "addi   $13,   $13,  1      \n"   /* increment k_src pointer        */
        "j                   1b     \n"   /* jump to next iteration         */
        "2:                         \n"
        "nop                        \n"
        ".set reorder               \n"
        : 
        : "r"(size),"r"(src),"r"(dst)
        : "$10","$11","$12","$13","$14","$15" );
        
    hal_restore_irq( save_sr ); 

} // hal_strcpy_from_uspace()

////////////////////////////////////////////
void hal_strcpy_to_uspace( char     * u_dst,
                           char     * k_src,
                           uint32_t   size )
{
    uint32_t save_sr;
    uint32_t src = (uint32_t)k_src;
    uint32_t dst = (uint32_t)u_dst;

    hal_disable_irq( &save_sr );

    // loop on characters while ( (character != NUL) and (count < size) ) 
    asm volatile(
        ".set noreorder             \n"
        "move   $11,   %0           \n"   /* $11 <= count == size           */
        "move   $12,   %1           \n"   /* $12 <= k_src                   */
        "move   $13,   %2           \n"   /* $13 <= u_dst                   */
        "mfc2   $15,   $1           \n"   /* $15 <= mode DTLB and ITLB off  */
        "ori    $14,   $15,  0x4    \n"   /* $14 <= mode DTLB on            */
        "1:                         \n"
        "lb     $10,   0($12)       \n"   /* read char from kernel space    */
        "mtc2   $14,   $1			\n"   /* MMU_MODE <= DTLB ON            */
	    "sb	    $10,   0($13)       \n"   /* store char to user space       */
        "mtc2   $15,   $1			\n"   /* restore DTLB and ITLB off      */
        "beq    $10,   $0,   2f     \n"   /* exit if char == 0              */
        "addi   $11,   $11, -1      \n"   /* decrement count                */
        "addi   $12,   $12,  1      \n"   /* increment k_src pointer        */
        "beq    $11,   $0,   2f     \n"   /* exit if count == size          */
        "addi   $13,   $13,  1      \n"   /* increment u_src pointer        */
        "j                   1b     \n"   /* jump to next iteration         */
        "2:                         \n"
        "nop                        \n"
        ".set reorder               \n"
        :
        : "r"(size),"r"(src),"r"(dst)
        : "$10","$11","$12","$13","$14","$15" );
        
    hal_restore_irq( save_sr ); 

} // hal_strcpy_to_uspace()

///////////////////////////////////////////////
uint32_t hal_strlen_from_uspace( char * u_str )
{
    uint32_t save_sr;
    uint32_t count = 0;
    uint32_t str   = (uint32_t)u_str;

    hal_disable_irq( &save_sr ); 

        asm volatile(
        ".set noreorder             \n"
        "move   $13,   %1           \n"   /* $13 <= str                     */
        "mfc2   $15,   $1           \n"   /* $15 <= DTLB and ITLB off       */
        "ori    $14,   $15,  0x4    \n"   /* $14 <= mode DTLB on            */
        "1:                         \n"
        "mtc2   $14,   $1			\n"   /* set DTLB on                    */
        "lb	    $12,   0($13)       \n"   /* read char from user space      */
        "mtc2   $15,   $1			\n"   /* set DTLB off                   */
        "addi   $13,   $13,  1      \n"   /* increment address              */
        "bne    $12,   $0,   1b     \n"   /* loop until NUL found           */
        "addi	%0,    %0,   1      \n"   /* increment count                */
        ".set reorder               \n"
        : "+r"(count) 
        : "r"(str) 
        : "$12","$13","$14","$15" );

    hal_restore_irq( save_sr );

    return count;
}

