/*
 * printk.c - Kernel Log & debug messages API implementation.
 * 
 * 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 <hal_types.h>
#include <hal_irqmask.h>
#include <hal_special.h>
#include <dev_txt.h>
#include <remote_spinlock.h>
#include <cluster.h>
#include <chdev.h>
#include <printk.h>

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

extern chdev_directory_t  chdev_dir;  // defined in chdev.h / allocated in kernel_init.c

/////////////////////////////////////
uint32_t snprintf( char     * string,
                   uint32_t   length,
                   char     * format, ... )
{

#define TO_STREAM(x) do { string[ps] = (x); ps++; if(ps==length) return -1; } while(0);

    va_list    args;      // printf arguments
    uint32_t   ps;        // write pointer to the string buffer

    ps = 0;   
    va_start( args , format );

xprintf_text:

    while ( *format != 0 ) 
    {

        if (*format == '%')   // copy argument to string
        {
            format++;
            goto xprintf_arguments;
        }
        else                  // copy one char to string
        {
            TO_STREAM( *format );
            format++;
        }
    }

    va_end( args );
    
    // add terminating NUL chracter
    TO_STREAM( 0 );
    return ps;

xprintf_arguments:

    {
        char              buf[30];    // buffer to display one number
        char *            pbuf;       // pointer on first char to display
        uint32_t          len = 0;    // number of char to display
        static const char HexaTab[] = "0123456789ABCDEF";
        uint32_t          i;
        
        // Ignore fields width and precision 
        for ( ; (*format >= '0' && *format <= '9') || (*format == '.') ; format++ );

        switch (*format) 
        {
            case ('c'):             // char conversion 
            {
                int val = va_arg( args, int );
                buf[0] = val;
                pbuf   = buf;
                len    = 1;
                break;
            }
            case ('d'):             // decimal signed integer
            {
                int val = va_arg( args, int );
                if (val < 0) 
                {
                    TO_STREAM( '-' );
                    val = -val;
                }
                for(i = 0; i < 10; i++) 
                {

                    buf[9 - i] = HexaTab[val % 10];
                    if (!(val /= 10)) break;
                }
                len =  i + 1;
                pbuf = &buf[9 - i];
                break;
            }
            case ('u'):             // decimal unsigned integer
            {
                uint32_t val = va_arg( args, uint32_t );
                for(i = 0; i < 10; i++) 
                {
                    buf[9 - i] = HexaTab[val % 10];
                    if (!(val /= 10)) break;
                }
                len =  i + 1;
                pbuf = &buf[9 - i];
                break;
            }
            case ('x'):             // 32 bits hexadecimal 
            case ('l'):             // 64 bits hexadecimal 
            {
                uint32_t imax;
                uint64_t val;
                
                if ( *format == 'l' )   // 64 bits
                {
                    val = va_arg( args, uint64_t);
                    imax = 16;
                }
                else                    // 32 bits
                {
                    val = va_arg( args, uint32_t);
                    imax = 8;
                }
                
                TO_STREAM( '0' );
                TO_STREAM( 'x' );
                
                for(i = 0; i < imax; i++) 
                {
                    buf[(imax-1) - i] = HexaTab[val % 16];
                    if (!(val /= 16))  break;
                }
                len =  i + 1;
                pbuf = &buf[(imax-1) - i];
                break;
            }
            case ('X'):             // 32 bits hexadecimal on 8 characters
            {
                uint32_t val = va_arg( args , uint32_t );
                for(i = 0; i < 8; i++) 
                {
                    buf[7 - i] = HexaTab[val % 16];
                    val = (val>>4);
                }
                len =  8;
                pbuf = buf;
                break;
            }
            case ('s'):             /* string */
            {
                char* str = va_arg( args, char* );
                while (str[len]) { len++; }
                pbuf = str;
                break;
            }
            default:       // unsupported argument type
            {
                return -1;
            }
        }  // end switch on  argument type

        format++;

        // copy argument to string
        for( i = 0 ; i < len ; i++ )
        {
            TO_STREAM( pbuf[i] );
        }
        
        goto xprintf_text;
    }
} // end snprintf()

//////////////////////////////////////////////////////////////////////////////////////
// This static function is called by printk(), assert() and nolock_printk()
// to display a formated string on TXT0, using a busy waiting policy.
//////////////////////////////////////////////////////////////////////////////////////
// @ format    : printf like format.
// @ args      : va_list of arguments.
//////////////////////////////////////////////////////////////////////////////////////
static void kernel_printf( char     * format, 
                           va_list  * args ) 
{

printf_text:

    while (*format) 
    {
        uint32_t i;
        for (i = 0 ; format[i] && (format[i] != '%') ; i++);
        if (i) 
        {
            dev_txt_sync_write( format, i );
            format += i;
        }
        if (*format == '%') 
        {
            format++;
            goto printf_arguments;
        }
    }

    return;

printf_arguments:

    {
        char      buf[20];
        char    * pbuf = NULL;
        uint32_t  len  = 0;
        static const char HexaTab[] = "0123456789ABCDEF";
        uint32_t i;

        switch (*format++) 
        {
            case ('c'):             /* char conversion */
            {
                int val = va_arg( *args , int );
                len = 1;
                buf[0] = val;
                pbuf = &buf[0];
                break;
            }
            case ('d'):             /* 32 bits decimal signed  */
            {
                int val = va_arg( *args , int );
                if (val < 0) 
                {
                    val = -val;
                    dev_txt_sync_write( "-" , 1 );
                }
                for(i = 0; i < 10; i++) 
                {
                    buf[9 - i] = HexaTab[val % 10];
                    if (!(val /= 10)) break;
                }
                len =  i + 1;
                pbuf = &buf[9 - i];
                break;
            }
            case ('u'):             /* 32 bits decimal unsigned  */
            {
                uint32_t val = va_arg( *args , uint32_t );
                for(i = 0; i < 10; i++) 
                {
                    buf[9 - i] = HexaTab[val % 10];
                    if (!(val /= 10)) break;
                }
                len =  i + 1;
                pbuf = &buf[9 - i];
                break;
            }
            case ('x'):             /* 32 bits hexadecimal unsigned */
            {
                uint32_t val = va_arg( *args , uint32_t );
                dev_txt_sync_write( "0x" , 2 );
                for(i = 0; i < 8; i++) 
                {
                    buf[7 - i] = HexaTab[val & 0xF];
                    if (!(val = (val>>4)))  break;
                }
                len =  i + 1;
                pbuf = &buf[7 - i];
                break;
            }
            case ('X'):             /* 32 bits hexadecimal unsigned  on 10 char */
            {
                uint32_t val = va_arg( *args , uint32_t );
                dev_txt_sync_write( "0x" , 2 );
                for(i = 0; i < 8; i++) 
                {
                    buf[7 - i] = HexaTab[val & 0xF];
                    val = (val>>4);
                }
                len =  8;
                pbuf = buf;
                break;
            }
            case ('l'):            /* 64 bits hexadecimal unsigned */
            {
                unsigned long long val = va_arg( *args , unsigned long long );
                dev_txt_sync_write( "0x" , 2 );
                for(i = 0; i < 16; i++) 
                {
                    buf[15 - i] = HexaTab[val & 0xF];
                    if (!(val = (val>>4)))  break;
                }
                len =  i + 1;
                pbuf = &buf[15 - i];
                break;
            }
            case ('L'):           /* 64 bits hexadecimal unsigned on 18 char */ 
            {
                unsigned long long val = va_arg( *args , unsigned long long );
                dev_txt_sync_write( "0x" , 2 );
                for(i = 0; i < 16; i++) 
                {
                    buf[15 - i] = HexaTab[val & 0xF];
                    val = (val>>4);
                }
                len =  16;
                pbuf = buf;
                break;
            }
            case ('s'):             /* string */
            {
                char* str = va_arg( *args , char* );
                while (str[len]) 
                {
                    len++;
                }
                pbuf = str;
                break;
            }
            default:
            {
                dev_txt_sync_write( "\n[PANIC] in kernel_printf() : illegal format\n", 45 );
            }
        }

        if( pbuf != NULL ) dev_txt_sync_write( pbuf, len );
        
        goto printf_text;
    }

}  // end kernel_printf()

//////////////////////////////////
void printk( char * format , ... )
{
    va_list       args;
    uint32_t      save_sr;

    // get pointers on TXT0 chdev
    xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
    cxy_t     txt0_cxy = GET_CXY( txt0_xp );
    chdev_t * txt0_ptr = GET_PTR( txt0_xp );

    // get extended pointer on remote TXT0 chdev lock
    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get TXT0 lock in busy waiting mode
    remote_spinlock_lock_busy( lock_xp , &save_sr );

    // call kernel_printf on TXT0, in busy waiting mode
    va_start( args , format );
    kernel_printf( format , &args );
    va_end( args );

    // release lock
    remote_spinlock_unlock_busy( lock_xp , save_sr );
}

/////////////////////////////////////////
void nolock_printk( char * format , ... )
{
    va_list       args;

    // call kernel_printf on TXT0, in busy waiting mode
    va_start( args , format );
    kernel_printf( format , &args );
    va_end( args );
}

////////////////////////////////////
void assert( bool_t       condition,
             const char * function_name,
             char       * format, ... )
{
    va_list       args;
    uint32_t      save_sr;

    if( condition == false )
    {
        // get pointers on TXT0 chdev
        xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
        cxy_t     txt0_cxy = GET_CXY( txt0_xp );
        chdev_t * txt0_ptr = GET_PTR( txt0_xp );

        // get extended pointer on remote TXT0 chdev lock
        xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

        // get TXT0 lock in busy waiting mode
        remote_spinlock_lock_busy( lock_xp , &save_sr );

        // call nolock_printk to print function_name
        nolock_printk("\n[PANIC] in %s : " , function_name );

        // call kernel_printf on TXT0, in busy waiting to print format
        va_start( args , format );
        kernel_printf( format , &args );
        va_end( args );

        // release TXT0 lock
        remote_spinlock_unlock_busy( lock_xp , save_sr );

        // suicide
        hal_core_sleep();
    }
}

//////////////////////////
void puts( char * string ) 
{
    uint32_t   save_sr;
    uint32_t   n = 0;

    // compute string length
    while ( string[n] > 0 ) n++;

    // get pointers on TXT0 chdev
    xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
    cxy_t     txt0_cxy = GET_CXY( txt0_xp );
    chdev_t * txt0_ptr = GET_PTR( txt0_xp );

    // get extended pointer on remote TXT0 chdev lock
    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get TXT0 lock in busy waiting mode
    remote_spinlock_lock_busy( lock_xp , &save_sr );

    // display string on TTY0
    dev_txt_sync_write( string , n );

    // release TXT0 lock in busy waiting mode
    remote_spinlock_unlock_busy( lock_xp , save_sr );
}


/////////////////////////
void putx( uint32_t val )
{
    static const char HexaTab[] = "0123456789ABCDEF";

    char      buf[10];
    uint32_t  c;
    uint32_t  save_sr;

    buf[0] = '0';
    buf[1] = 'x';

    // build buffer
    for (c = 0; c < 8; c++) 
    { 
        buf[9 - c] = HexaTab[val & 0xF];
        val = val >> 4;
    }

    // get pointers on TXT0 chdev
    xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
    cxy_t     txt0_cxy = GET_CXY( txt0_xp );
    chdev_t * txt0_ptr = GET_PTR( txt0_xp );

    // get extended pointer on remote TXT0 chdev lock
    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get TXT0 lock in busy waiting mode
    remote_spinlock_lock_busy( lock_xp , &save_sr );

    // display string on TTY0
    dev_txt_sync_write( buf , 10 );

    // release TXT0 lock in busy waiting mode
    remote_spinlock_unlock_busy( lock_xp , save_sr );
}

/////////////////////////
void putl( uint64_t val )
{
    static const char HexaTab[] = "0123456789ABCDEF";

    char      buf[18];
    uint32_t  c;
    uint32_t  save_sr;

    buf[0] = '0';
    buf[1] = 'x';

    // build buffer
    for (c = 0; c < 16; c++) 
    { 
        buf[17 - c] = HexaTab[(unsigned int)val & 0xF];
        val = val >> 4;
    }

    // get pointers on TXT0 chdev
    xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
    cxy_t     txt0_cxy = GET_CXY( txt0_xp );
    chdev_t * txt0_ptr = GET_PTR( txt0_xp );

    // get extended pointer on remote TXT0 chdev lock
    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get TXT0 lock in busy waiting mode
    remote_spinlock_lock_busy( lock_xp , &save_sr );

    // display string on TTY0
    dev_txt_sync_write( buf , 18 );

    // release TXT0 lock in busy waiting mode
    remote_spinlock_unlock_busy( lock_xp , save_sr );
}


// Local Variables:
// tab-width: 4
// c-basic-offset: 4
// c-file-offsets:((innamespace . 0)(inline-open . 0))
// indent-tabs-mode: nil
// End:
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=4:softtabstop=4

