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

///////////////////////////////////////////////////////////////////////////////////
//      Extern 
///////////////////////////////////////////////////////////////////////////////////

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

//////////////////////////////////////////////////////////////////////////////////////
// This static function is called by printk(), nolock_printk(), and snprintk(),
// functions to build a string from a printf-like format, and stores it 
// in the buffer defined by the <string> and <length> arguments.
// It does NOT add a terminating NUL character in the <string> buffer.
// If success, it returns the number of bytes actually copied in the string buffer.
// It returns -2 in case of illegal format, it returns -1 if the formated string 
// exceeds the length argument. 
//////////////////////////////////////////////////////////////////////////////////////
// @ string    : buffer allocated by caller.
// @ length    : buffer size in bytes
// @ format    : printf like format.
// @ args      : va_list of arguments.
// @ return string length if success / -1 if buffer too small / -2 if illegal format. 
//////////////////////////////////////////////////////////////////////////////////////
static int32_t format_to_string( char       * string,
                                 uint32_t     length,
                                 const char * format, 
                                 va_list    * args ) 
{

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

    uint32_t   ps = 0;        // index in string buffer

format_to_string_text:

    // handle one character per iteration
    while ( *format != 0 ) 
    {
        if (*format == '%')   // copy argument to string
        {
            format++;
            goto format_to_string_arguments;
        }
        else                  // copy one char to string
        {
            TO_STRING( *format );
            format++;
        }
    }

    TO_STRING( 0 );
    return ps;

format_to_string_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;
        
        switch (*format) 
        {
            case ('c'):             // char conversion 
            {
                int val = va_arg( *args , int );
                buf[0] = (char)val;
                pbuf   = buf;
                len    = 1;
                break;
            }
            case ('d'):             // up to 10 digits decimal signed integer
            {
                int32_t val = va_arg( *args , int32_t );
                if (val < 0) 
                {
                    TO_STRING( '-' );
                    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'):             // up to 10 digits 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'):             // up to 8 digits hexad after "0x"
            case ('X'):             // exactly 8 digits hexa after "0x"
            {
                uint32_t val = va_arg( *args , uint32_t );
                TO_STRING( '0' );
                TO_STRING( 'x' );
                for(i = 0 ; i < 8 ; i++) 
                {
                    buf[7 - i] = HexaTab[val & 0xF];
                    if( (*format == 'x') && ((val >> 4) == 0) )  break;
                    val = val >> 4;
                }
                len =  i + 1;
                pbuf = &buf[7 - i];
                break;
            }
            case ('l'):             // up to 16 digits hexa after "0x"
            case ('L'):             // exactly 16 digits hexa after "0x"
            {
                uint64_t val = (((uint64_t)va_arg( *args, uint32_t)) << 32) |
                               ((uint64_t)va_arg( *args, uint32_t));
                TO_STRING( '0' );
                TO_STRING( 'x' );
                for(i = 0 ; i < 16 ; i++) 
                {
                    buf[15 - i] = HexaTab[val & 0xF];
                    if( (*format == 'l') && ((val >> 4) == 0) )  break;
                    val = val >> 4;
                }
                len =  i + 1;
                pbuf = &buf[15 - i];
                break;
            }
            case ('s'):             /* string */
            {
                char* str = va_arg( *args , char* );
                while (str[len]) { len++; }
                pbuf = str;
                break;
            }
            default:       // unsupported argument type
            {
                return -2;
            }
        }  // end switch on  argument type

        format++;

        // copy argument sub-string to the string buffer
        for( i = 0 ; i < len ; i++ )
        {
            TO_STRING( pbuf[i] );
        }
        
        goto format_to_string_text;
    }
}   // end format_to_string()

//////////////////////////////////
void printk( char * format , ... )
{
    char          buffer[CONFIG_PRINTK_BUF_SIZE];
    va_list       args;
    int32_t       length;

    // build args va_list
    va_start( args , format );

    // 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 lock
    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

    // get TXT0 lock
    remote_busylock_acquire( lock_xp );

    // build a string from format
    length = format_to_string( buffer,
                      CONFIG_PRINTK_BUF_SIZE,
                      format,
                      &args );
    va_end( args );

    if( length > 0  )        // call TXT driver to display formated string on TXT0
    {
        dev_txt_sync_write( buffer , length );
    }
    else if( length == -2 )  // illegal format => display a warning on TXT0
    {
        thread_t * this = CURRENT_THREAD;

        nolock_printk("\n[PANIC] from printk : illegal format\n"
                      "thread[%x,%x] on core[%x,%d] at cycle %l\n",
                      this->process->pid, this->trdid,
                      local_cxy, this->core->lid, hal_get_cycles() );
    }
    else                     // format too long => display a warning on TXT0
    {
        thread_t * this = CURRENT_THREAD;

        nolock_printk("\n[PANIC] from printk : formated string too long\n"
                      "thread[%x,%x] on core[%x,%d] at cycle %l\n",
                      this->process->pid, this->trdid,
                      local_cxy, this->core->lid, hal_get_cycles() );
    }

    // release TXT0 lock
    remote_busylock_release( lock_xp );

}   // end printk()

/////////////////////////////////////////
void nolock_printk( char * format , ... )
{
    char          buffer[CONFIG_PRINTK_BUF_SIZE];
    va_list       args;
    int32_t       length;

    // build args va_list
    va_start( args , format );

    // build a string from format
    length = format_to_string( buffer,
                      CONFIG_PRINTK_BUF_SIZE,
                      format,
                      &args );
    va_end( args );

    if( length > 0  )        // call TXT driver to display formated string on TXT0
    {
        dev_txt_sync_write( buffer , length );
    }
    else if( length == -2 )  // illegal format => display a warning on TXT0
    {
        thread_t * this = CURRENT_THREAD;

        nolock_printk("\n[PANIC] from print : illegal format\n"
                      "thread[%x,%x] on core[%x,%d] at cycle %l\n",
                      this->process->pid, this->trdid,
                      local_cxy, this->core->lid, hal_get_cycles() );
    }
    else                  // buffer too small => display a warning on TXT0
    {
        thread_t * this = CURRENT_THREAD;

        nolock_printk("\n[PANIC] from printk : formated string too long\n"
                      "thread[%x,%x] on core[%x,%d] at cycle %l\n",
                      this->process->pid, this->trdid,
                      local_cxy, this->core->lid, hal_get_cycles() );
    }

}   // end nolock_printk()

//////////////////////////////////////
void assert( const char   * func_name,
             bool_t         expr,
             char         * format , ... )
{
    if( expr == false )
    {
        thread_t * this  = CURRENT_THREAD;
        trdid_t    trdid = this->trdid;
        pid_t      pid   = this->process->pid;
        uint32_t   lid   = this->core->lid;
        uint32_t   cycle = (uint32_t)hal_get_cycles();

        char       buffer[CONFIG_PRINTK_BUF_SIZE];
        va_list    args;

        va_start( args , format );

        // build a string from format
        int32_t   length = format_to_string( buffer,
                                             CONFIG_PRINTK_BUF_SIZE,
                                             format,
                                             &args );
        va_end( args );

        if( length > 0  )  // display panic message on TXT0, including formated string
        {
            printk("\n[ASSERT] in %s / core[%x,%d] / thread[%x,%x] / cycle %d\n       %s\n",
            func_name, local_cxy, lid, pid, trdid, cycle, buffer );
        }
        else              // display minimal panic message on TXT0
        {
            printk("\n[ASSERT] in %s / core[%x,%d] / thread[%x,%x] / cycle %d\n",
            func_name, local_cxy, lid, pid, trdid, cycle );
        }
    }
}   // end assert( __FUNCTION__,)
   
//////////////////////////////////////
int32_t snprintk( char       * buffer,
                  uint32_t     size,
                  char       * format, ... )
{
    va_list       args;
    int32_t       string_length;

    // build args va_list
    va_start( args , format );

    // build a string from format
    string_length = format_to_string( buffer , size , format , &args );
    va_end( args );

    if( (string_length < 0) || (string_length == (int32_t)size) )  // failure
    {
        return -1;
    }
    else                                                           // success
    {
        // add NUL character
        buffer[string_length] = 0;

        return string_length;
    }
}   // end snprintk()

////////////////////////////////
void puts( const char * string ) 
{
    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 );

    if( txt0_xp != XPTR_NULL )
    {
        // get extended pointer on remote TXT0 lock
        xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );

        // display string on TTY0
        remote_busylock_acquire( lock_xp );
        dev_txt_sync_write( string , n );
        remote_busylock_release( lock_xp );
    }
}   // end puts()

///////////////////////////////////////
void nolock_puts( const char * string ) 
{
    uint32_t   n = 0;

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

    // display string on TTY0
    dev_txt_sync_write( string , n );
  
}   // end nolock_puts()



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

    char      buf[10];
    uint32_t  c;

    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 );

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

        // display buf on TTY0
        remote_busylock_acquire( lock_xp );
        dev_txt_sync_write( buf , 10 );
        remote_busylock_release( lock_xp );
    }
}   // end putx()

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

    char      buf[10];
    uint32_t  c;

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

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

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

}   // end nilock_putx()

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

    char      buf[10];
    uint32_t  i;

    // 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 );

    if( txt0_xp != XPTR_NULL )
    {
        // get TXT0 lock 
        remote_busylock_acquire( lock_xp );

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

        // display buf on TTY0
        dev_txt_sync_write( &buf[9-i] , i+1 );

        // release TXT0 lock
        remote_busylock_release( lock_xp );
    }
}   // end putd()

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

    char      buf[10];
    uint32_t  i;

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

    // display buf on TTY0
    dev_txt_sync_write( &buf[9-i] , i+1 );

}   // end nolock_putd()

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

    char      buf[18];
    uint32_t  c;

    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 );

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

        // display string on TTY0
        remote_busylock_acquire( lock_xp );
        dev_txt_sync_write( buf , 18 );
        remote_busylock_release( lock_xp );
    }
}   // end putl()

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

    char      buf[18];
    uint32_t  c;

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

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

}   // end nolock_putl()

/////////////////////////////
void putb( char     * string,
           uint8_t  * buffer,
           uint32_t   size )
{
    uint32_t line;
    uint32_t byte;
    uint32_t nlines;

    nlines = size >> 4;
    if( size & 0xF ) nlines++;

    // 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 );

    if( txt0_xp != XPTR_NULL )
    {
        // get TXT0 lock 
        remote_busylock_acquire( lock_xp );

        // display string on TTY0
        nolock_printk("\n***** %s *****\n", string );

        for ( line = 0 , byte = 0 ; line < nlines ; line++ )
        {
            nolock_printk(" %X | %b %b %b %b | %b %b %b %b | %b %b %b %b | %b %b %b %b |\n",
            buffer + byte,
            buffer[byte+ 0],buffer[byte+ 1],buffer[byte+ 2],buffer[byte+ 3],
            buffer[byte+ 4],buffer[byte+ 5],buffer[byte+ 6],buffer[byte+ 7],
            buffer[byte+ 8],buffer[byte+ 9],buffer[byte+10],buffer[byte+11],
            buffer[byte+12],buffer[byte+13],buffer[byte+14],buffer[byte+15] );

            byte += 16;
        }

        // release TXT0 lock
        remote_busylock_release( lock_xp );
    }
}   // end putb()



// 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

