Ignore:
Timestamp:
Apr 26, 2017, 2:14:33 PM (7 years ago)
Author:
alain
Message:

Modify the boot_info_t struct to describe external peripherals in all clusters.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tools/bootloader_tsar/boot_utils.c

    r1 r6  
     1/*
     2 * boot_utils.c - TSAR bootloader utilities implementation.
     3 *
     4 * Authors :   Alain Greiner / Vu Son  (2016)
     5 *
     6 * Copyright (c) UPMC Sorbonne Universites
     7 *
     8 * This file is part of ALMOS-MKH.
     9 *
     10 * ALMOS-MKH is free software; you can redistribute it and/or modify it
     11 * under the terms of the GNU General Public License as published by
     12 * the Free Software Foundation; version 2.0 of the License.
     13 *
     14 * ALMOS-MKH is distributed in the hope that it will be useful, but
     15 * WITHOUT ANY WARRANTY; without even the implied warranty of
     16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     17 * General Public License for more details.
     18 *
     19 * You should have received a copy of the GNU General Public License
     20 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
     21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
     22 */
     23
    124#include <stdarg.h>
    225
     
    528#include <boot_utils.h>
    629
     30
     31/****************************************************************************
     32 *                        Global variables                                  *
     33 ****************************************************************************/
     34
     35extern boot_remote_spinlock_t  tty0_lock;   // allocated in boot.c
     36
    737/****************************************************************************
    838 *                              Remote accesses.                            *
    939 ****************************************************************************/
    1040
     41//////////////////////////////////
    1142uint32_t boot_remote_lw(xptr_t xp)
    1243{
    13     uint32_t res;   /* Value to be read, stored at the remote address.      */
    14     uint32_t ptr;   /* Classic pointer to the distant memory location.      */
    15     uint32_t cxy;   /* Identifier of the cluster containing the distant
    16                        memory location.                                     */
    17 
    18     /* Extracting information from the extended pointer. */
     44    uint32_t res; 
     45    uint32_t ptr;
     46    uint32_t cxy;
     47
     48    // Extracting information from the extended pointer
    1949    ptr = (uint32_t)GET_PTR(xp);
    2050    cxy = (uint32_t)GET_CXY(xp);
    2151
    22     /* Assembly instructions to get the work done. */
     52    // Assembly instructions to get the work done.
    2353    asm volatile("mfc2  $15,    $24\n"  /* $15 <= CP2_DATA_PADDR_EXT        */
    2454                 "mtc2  %2,     $24\n"  /* CP2_DATA_PADDR_EXT <= cxy        */
     
    3767} // boot_remote_lw()
    3868
    39 /****************************************************************************/
    40 
     69/////////////////////////////////////////////
    4170void boot_remote_sw(xptr_t xp, uint32_t data)
    4271{
     
    6291} // boot_remote_sw()
    6392
    64 /****************************************************************************/
    65 
     93//////////////////////////////////////////////////////
    6694int32_t boot_remote_atomic_add(xptr_t xp, int32_t val)
    6795{
     
    98126} // boot_remote_atomic_add()
    99127
    100 /****************************************************************************/
    101 
    102 void boot_remote_memcpy(xptr_t dest, xptr_t src, unsigned int size)
     128///////////////////////////////////////////////////////////////
     129void boot_remote_memcpy(xptr_t dest, xptr_t src, uint32_t size)
    103130{
    104131    uint32_t words_nr;  /* Number of 32-bit words to be copied.             */
     
    194221 ****************************************************************************/
    195222
    196 void boot_memcpy(void* dest, void* src, unsigned int size)
    197 {
    198     /* Word-by-word copy if both addresses are word-aligned. */
    199     if ((((unsigned int)dest & 0x3) == 0) &&
    200         (((unsigned int)src & 0x3)  == 0))
    201     {
    202         // 'size' might not be a multiple of 4 bytes, we have to copy a few
    203         // bytes left (at most 3) byte-by-byte later.
     223///////////////////////////////////////////////////////
     224void boot_memcpy(void * dst, void * src, uint32_t size)
     225{
     226    uint32_t * wdst = dst;
     227    uint32_t * wsrc = src;
     228
     229    // word-by-word copy if both addresses are word-aligned
     230    if ( (((uint32_t)dst & 0x3) == 0) && (((uint32_t)src & 0x3)  == 0) )
     231    {
    204232        while (size > 3)
    205233        {
    206             *(unsigned int*)dest++ = *(unsigned int*)src++;
     234            *wdst++ = *wsrc++;
    207235            size -= 4;
    208236        }
    209237    }
    210238
    211     /*
    212      * Byte-by-byte copy if:
    213      * - At least 1 of the 2 addresses is not word-aligned,
    214      * - 'size' value is not a multiple of 4 bytes.
    215      */
     239    unsigned char * cdst = (unsigned char *)wdst;
     240    unsigned char * csrc = (unsigned char *)wsrc;
     241
     242    // byte-by-byte copy if:
     243    // - At least 1 of the 2 addresses is not word-aligned,
     244    // - 'size' value is not a multiple of 4 bytes.
    216245    while (size)
    217         *(unsigned char*)dest++ = *(unsigned char*)src++;
    218 
     246    {
     247        *cdst++ = *csrc++;
     248    }
    219249} // boot_memcpy()
    220250
    221 /****************************************************************************/
    222 
    223 void boot_memset(void* base, int val, unsigned int size)
    224 {
    225     unsigned int wval;      /* Word-sized value to word-by-word filling.        */
    226 
    227     /* Word-by-word filling if the base address is word-aligned. */
    228     // Extracting the first 2 bytes of 'val'.
     251////////////////////////////////////////////////////
     252void boot_memset(void * dst, int val, uint32_t size)
     253{
    229254    val &= 0xFF;
    230     // Making it word-sized.
    231     wval = (val << 24) | (val << 16) | (val << 8) | val;
    232 
    233     if (((unsigned int)base & 0x3) == 0)
    234     {
    235         // 'size' might not be a multiple of 4 bytes, we have to fill a
    236         // few bytes left (at most 3) byte-by-byte later.
     255
     256    // build a word-sized value
     257    uint32_t   wval = (val << 24) | (val << 16) | (val << 8) | val;
     258    uint32_t * wdst = (uint32_t *)dst;
     259
     260    // word per word if address aligned
     261    if (((uint32_t)dst & 0x3) == 0)
     262    {
    237263        while (size > 3)
    238264        {
    239             *(unsigned int*)base++ = wval;
     265            *wdst++ = wval;
    240266            size -= 4;
    241267        }
    242268    }
    243269   
    244     /*
    245      * Byte-by-byte filling if:
    246      * - The base address is not word-aligned,
    247      * - 'size' value is not a multiple of 4 bytes.
    248      */
     270    char * cdst = (char *)wdst;
     271
     272    // byte per byte
    249273    while (size--)
    250         *(unsigned char*)base++ = val;
    251 
     274    {
     275        *cdst++ = (char)val;
     276    }
    252277} // boot_memset()
    253278
     
    256281 ****************************************************************************/
    257282
     283///////////////////////////////////////
    258284void boot_strcpy(char* dest, char* src)
    259285{
     
    267293} // boot_strcpy()
    268294
    269 /****************************************************************************/
    270 
    271 unsigned int boot_strlen(char* s)
    272 {
    273     unsigned int res = 0;   /* Length of the string (in bytes).             */
     295/////////////////////////////
     296uint32_t boot_strlen(char* s)
     297{
     298    uint32_t res = 0;   /* Length of the string (in bytes).             */
    274299
    275300    if (s != NULL)
     
    283308} // boot_strlen()
    284309
    285 /****************************************************************************/
    286 
     310///////////////////////////////////
    287311int boot_strcmp(char* s1, char* s2)
    288312{
     
    308332 ****************************************************************************/
    309333
     334/////////////////////////
    310335void boot_puts(char* str)
    311336{
     
    314339} // boot_puts()
    315340   
    316 /****************************************************************************/
    317 
    318 void boot_printf(char* format, ...)
    319 {
    320     va_list      arg;           /* Used to iterate arguments list.          */
    321     char         buf[16];       /* Buffer for argument conversion.          */     
    322     char*        print_pt;      /* String pointer for argument printing.    */
    323     int          arg_val;       /* Raw value of the argument.               */
    324     unsigned int arg_len;       /* Length of a argument (in bytes).         */
    325     unsigned int nb_printed;    /* Iterator for text printing loop.         */
    326     unsigned int conv_index;    /* Index for argument conversion loop.      */
    327 
    328     const char conv_tab[] = "0123456789ABCDEF";
    329 
    330     /* Starting the arguments iterating process with a va_list. */
    331     va_start(arg, format);
    332 
    333 print_text:
    334    
    335     while (*format)
    336     {
    337         /* Counting the number of ordinary characters. */
    338         for (nb_printed = 0;
    339              (format[nb_printed] != '\0') && (format[nb_printed] != '%');
    340              nb_printed++);
    341 
    342         /* Copying them unchanged to the boot TTY terminal. */
    343         if (nb_printed > 0)
     341///////////////////////////////////////
     342void boot_printf( char * format , ... )
     343{
     344    va_list args;
     345    va_start( args , format );
     346
     347    // take the lock protecting TTY0
     348    boot_remote_lock( XPTR( 0 , &tty0_lock ) );
     349
     350printf_text:
     351
     352    while ( *format )
     353    {
     354        uint32_t i;
     355        for (i = 0 ; format[i] && (format[i] != '%') ; i++);
     356        if (i)
    344357        {
    345             if (boot_tty_write(format, nb_printed))
    346                 goto error;
    347             format += nb_printed;
     358            boot_tty_write( format , i );
     359            format += i;
    348360        }
    349 
    350         /* Skipping the '%' character. */
    351         if (*format == '%')
     361        if (*format == '%')
    352362        {
    353363            format++;
    354             goto print_argument;
     364            goto printf_arguments;
    355365        }
    356366    }
    357367
    358     /* Freeing the va_list. */
    359     va_end(arg);
    360 
     368    // release the lock
     369    boot_remote_unlock( XPTR( 0 , &tty0_lock ) );
     370
     371    va_end( args );
    361372    return;
    362373
    363 print_argument:
    364 
    365     /* Analyzing the conversion specifier. */
    366     switch (*format++)
    367     {
    368         // A character.
    369         case ('c'):
     374printf_arguments:
     375
     376    {
     377        char      buf[20];
     378        char    * pbuf = NULL;
     379        uint32_t  len  = 0;
     380        static const char HexaTab[] = "0123456789ABCDEF";
     381        uint32_t i;
     382
     383        switch (*format++)
    370384        {
    371             // Retrieving the argument.
    372             arg_val  = va_arg(arg, int);
    373 
    374             // Preparing for the printing.
    375             arg_len  = 1;
    376             buf[0]   = arg_val;
    377             print_pt = &buf[0];
    378             break;
     385            case ('c'):             /* char conversion */
     386            {
     387                int val = va_arg( args , int );
     388                len = 1;
     389                buf[0] = val;
     390                pbuf = &buf[0];
     391                break;
     392            }
     393            case ('d'):             /* 32 bits decimal signed  */
     394            {
     395                int val = va_arg( args , int );
     396                if (val < 0)
     397                {
     398                    val = -val;
     399                    boot_tty_write( "-" , 1 );
     400                }
     401                for(i = 0; i < 10; i++)
     402                {
     403                    buf[9 - i] = HexaTab[val % 10];
     404                    if (!(val /= 10)) break;
     405                }
     406                len =  i + 1;
     407                pbuf = &buf[9 - i];
     408                break;
     409            }
     410            case ('u'):             /* 32 bits decimal unsigned  */
     411            {
     412                uint32_t val = va_arg( args , uint32_t );
     413                for(i = 0; i < 10; i++)
     414                {
     415                    buf[9 - i] = HexaTab[val % 10];
     416                    if (!(val /= 10)) break;
     417                }
     418                len  = i + 1;
     419                pbuf = &buf[9 - i];
     420                break;
     421            }
     422            case ('x'):             /* 32 bits hexadecimal unsigned */
     423            {
     424                uint32_t val = va_arg( args , uint32_t );
     425                boot_tty_write( "0x" , 2 );
     426                for(i = 0; i < 8; i++)
     427                {
     428                    buf[7 - i] = HexaTab[val & 0xF];
     429                    if (!(val = (val>>4)))  break;
     430                }
     431                len =  i + 1;
     432                pbuf = &buf[7 - i];
     433                break;
     434            }
     435            case ('X'):             /* 32 bits hexadecimal unsigned  on 10 char */
     436            {
     437                uint32_t val = va_arg( args , uint32_t );
     438                boot_tty_write( "0x" , 2 );
     439                for(i = 0; i < 8; i++)
     440                {
     441                    buf[7 - i] = HexaTab[val & 0xF];
     442                    val = (val>>4);
     443                }
     444                len  = 8;
     445                pbuf = buf;
     446                break;
     447            }
     448            case ('l'):            /* 64 bits hexadecimal unsigned */
     449            {
     450                uint64_t val = va_arg( args , uint64_t );
     451                boot_tty_write( "0x" , 2 );
     452                for(i = 0; i < 16; i++)
     453                {
     454                    buf[15 - i] = HexaTab[val & 0xF];
     455                    if (!(val = (val>>4)))  break;
     456                }
     457                len  = i + 1;
     458                pbuf = &buf[15 - i];
     459                break;
     460            }
     461            case ('L'):           /* 64 bits hexadecimal unsigned on 18 char */
     462            {
     463                uint64_t val = va_arg( args , uint64_t );
     464                boot_tty_write( "0x" , 2 );
     465                for(i = 0; i < 16; i++)
     466                {
     467                    buf[15 - i] = HexaTab[val & 0xF];
     468                    val = (val>>4);
     469                }
     470                len  = 16;
     471                pbuf = buf;
     472                break;
     473            }
     474            case ('s'):             /* string */
     475            {
     476                char* str = va_arg( args , char* );
     477                while (str[len])
     478                {
     479                    len++;
     480                }
     481                pbuf = str;
     482                break;
     483            }
     484            default:
     485            {
     486                boot_tty_write( "\n[PANIC] in boot_printf() : illegal format\n", 43 );
     487            }
    379488        }
    380489
    381         // A 32-bit signed decimal notation of an integer.
    382         case ('d'):
    383         {
    384             // Retrieving the argument.
    385             arg_val  = va_arg(arg, int);
    386 
    387             // Printing the minus sign if needed.
    388             if (arg_val < 0)
    389             {
    390                 arg_val = -arg_val;
    391                 if (boot_tty_write("-", 1))
    392                     goto error;
    393             }
    394 
    395             // Converting the argument raw value to a character string.
    396             // Note that the maximum value for this type is 2.147.483.647
    397             // (2^31 - 1), a 10-digit number.
    398             for (conv_index = 0; conv_index < 10; conv_index++)
    399             {
    400                 // Writing to the buffer, starting from the least significant
    401                 // digit.
    402                 buf[9 - conv_index] = conv_tab[arg_val % 10];
    403 
    404                 // Getting to the next digit, stop when no more digit.
    405                 if ((arg_val /= 10) == 0)
    406                     break;
    407             }
    408 
    409             // Preparing for the printing.
    410             arg_len  = conv_index + 1;
    411             print_pt = &buf[9 - conv_index];
    412             break;
    413         }
    414 
    415         // A 32-bit unsigned decimal notation of an integer.
    416         case ('u'):
    417         {
    418             // Retrieving the argument.
    419             arg_val  = va_arg(arg, unsigned int);
    420 
    421             // Converting the argument raw value to a character string.
    422             // Note that the maximum value for this type is 4.294.967.295
    423             // (2^32 - 1), also a 10-digit number.
    424             for (conv_index = 0; conv_index < 10; conv_index++)
    425             {
    426                 // Writing to the buffer, starting from the least significant
    427                 // digit.
    428                 buf[9 - conv_index] = conv_tab[arg_val % 10];
    429 
    430                 // Getting to the next digit, stop when no more digit.
    431                 if ((arg_val /= 10) == 0)
    432                     break;
    433             }
    434 
    435             // Preparing for the printing.
    436             arg_len  = conv_index + 1;
    437             print_pt = &buf[9 - conv_index];
    438             break;
    439         }
    440 
    441         // A 32-bit unsigned hexadecimal notation of an integer.
    442         case ('x'):
    443         {
    444             // Retrieving the argument.
    445             arg_val  = va_arg(arg, unsigned int);
    446 
    447             // Printing the hexadecimal prefix.
    448             if (boot_tty_write("0x", 2))
    449                 goto error;
    450 
    451             // Converting the argument raw value to a character string.
    452             // Note that the maximum value for this type is 0xFFFFFFFF
    453             // (2^32 - 1), a 8-digit hexadecimal number.
    454             for (conv_index = 0; conv_index < 8; conv_index++)
    455             {
    456                 // Writing to the buffer, starting from the least significant
    457                 // digit.
    458                 buf[7 - conv_index] = conv_tab[arg_val % 16];
    459 
    460                 // Getting to the next digit, stop when no more digit.
    461                 if ((arg_val >>= 4) == 0)
    462                     break;
    463             }
    464 
    465             // Preparing for the printing.
    466             arg_len  = conv_index + 1;
    467             print_pt = &buf[7 - conv_index];
    468             break;
    469         }
    470 
    471         // A 64-bit unsigned hexadecimal notation of an integer.
    472         case ('l'):
    473         {
    474             // Retrieving the argument.
    475             arg_val  = va_arg(arg, unsigned long long);
    476 
    477             // Printing the hexadecimal prefix.
    478             if (boot_tty_write("0x", 2))
    479                 goto error;
    480 
    481             // Converting the argument raw value to a character string.
    482             // Note that the maximum value for this type is 0xFFFFFFFFFFFFFFFF
    483             // (2^64 - 1), a 16-digit hexadecimal number.
    484             for (conv_index = 0; conv_index < 16; conv_index++)
    485             {
    486                 // Writing to the buffer, starting from the least significant
    487                 // digit.
    488                 buf[15 - conv_index] = conv_tab[arg_val % 16];
    489 
    490                 // Getting to the next digit, stop when no more digit.
    491                 if ((arg_val >>= 4) == 0)
    492                     break;
    493             }
    494 
    495             // Preparing for the printing.
    496             arg_len  = conv_index + 1;
    497             print_pt = &buf[15 - conv_index];
    498             break;
    499         }
    500 
    501         // A NUL terminated string.
    502         case ('s'):
    503         {
    504             // Retrieving the argument.
    505             print_pt = va_arg(arg, char*);
    506 
    507             // Preparing for the printing.
    508             arg_len  = boot_strlen(print_pt);
    509             break;
    510         }
    511 
    512         default:
    513             goto error;
    514 
    515     }
    516 
    517     /* Printing the converted argument. */
    518     if (boot_tty_write(print_pt, arg_len))
    519         goto error;
    520 
    521     goto print_text;
    522 
    523 error:
    524 
    525     /* Trying to print an error message then exit. */
    526     boot_puts("\n[BOOT ERROR] boot_printf(): "
    527               "Cannot print the whole message\n"
    528              );
    529 
    530     boot_exit();
    531 
    532 } // boot_printf()
     490        if( pbuf != NULL ) boot_tty_write( pbuf, len );
     491       
     492        goto printf_text;
     493    }
     494}  // boot_printf()
     495
     496
     497
     498
     499
    533500
    534501/****************************************************************************
     
    536503 ****************************************************************************/
    537504
     505////////////////
    538506void boot_exit()
    539507{
    540     boot_printf("\n[BOOT PANIC] Suiciding at cycle %d...\n",
    541                 boot_get_proctime()
    542                );
    543 
    544     while (1)
    545         asm volatile ("nop");
     508    boot_printf("\n[BOOT PANIC] core %x suicide at cycle %d...\n",
     509                boot_get_procid() , boot_get_proctime() );
     510
     511    while (1) asm volatile ("nop");
    546512
    547513} // boot_exit()
    548514
    549 /****************************************************************************/
    550 
    551 unsigned int boot_get_proctime()
    552 {
    553     unsigned int res;       /* Value stored in the CP0_COUNT register.      */
     515////////////////////////////
     516uint32_t boot_get_proctime()
     517{
     518    uint32_t res;       /* Value stored in the CP0_COUNT register.      */
    554519
    555520    asm volatile("mfc0 %0, $9" : "=r"(res));
     
    559524} // boot_get_proctime()
    560525
    561 /****************************************************************************/
    562 
    563 unsigned int boot_get_procid()
    564 {
    565     unsigned int res;       /* Value stored in the CP0_PROCID register.     */
     526
     527//////////////////////////
     528uint32_t boot_get_procid()
     529{
     530    uint32_t res;       /* Value stored in the CP0_PROCID register.     */
    566531
    567532    asm volatile("mfc0 %0, $15, 1" : "=r"(res));
     
    571536} // boot_get_procid()
    572537
    573 /****************************************************************************/
    574 
    575 void boot_barrier(xptr_t xp_barrier, uint32_t count)
    576 {
    577     boot_barrier_t* ptr;        /* Classic pointer to the toggling
    578                                    barrier.                                 */
    579     uint32_t        cxy;        /* Identifier of the cluster containing
    580                                    the toggling barrier.                    */
    581     uint32_t        expected;   /* Expected barrier state after reset.      */
    582     uint32_t        current;    /* Number of processors reached the
    583                                    barrier.                                 */
    584 
    585     /* Extracting information from the extended pointer. */
    586     ptr = (boot_barrier_t*)GET_PTR(xp_barrier);
    587     cxy = (uint32_t)       GET_CXY(xp_barrier);
    588 
    589     /*
    590      * Explicitly testing the barrier sense value because no initialization
    591      * has been previously done.
    592      */
    593     if (boot_remote_lw(XPTR(cxy, &ptr->sense)) == 0)
    594         expected = 1;
    595     else
    596         expected = 0;
     538
     539////////////////////////////////////////////////
     540void boot_remote_barrier( xptr_t     xp_barrier,
     541                          uint32_t   count)
     542{
     543    boot_remote_barrier_t * ptr;
     544    uint32_t                cxy;
     545    uint32_t                expected;
     546    uint32_t                current;
     547
     548    // Extract information from the extended pointer
     549    ptr = (boot_remote_barrier_t*)GET_PTR(xp_barrier);
     550    cxy = (uint32_t)              GET_CXY(xp_barrier);
     551
     552    // Explicitly test the barrier sense value because no initialization
     553    if (boot_remote_lw(XPTR(cxy, &ptr->sense)) == 0) expected = 1;
     554    else                                             expected = 0;
    597555   
    598     /* Incrementing the counter. */
     556    // Atomically increment counter
    599557    current = boot_remote_atomic_add(XPTR(cxy, &ptr->current), 1);
    600558   
    601     /* The processor arrived last resets the barrier and toggles its sense. */
     559    // The processor arrived last resets the barrier and toggles its sense
    602560    if (current == (count - 1))
    603561    {
     
    605563        boot_remote_sw(XPTR(cxy, &ptr->sense), expected);
    606564    }
    607     /* Other processors poll the sense. */
     565    // Other processors poll the sense
    608566    else
    609567    {
     
    613571} // boot_barrier()
    614572
     573////////////////////////////////////////
     574void boot_remote_lock( xptr_t  lock_xp )
     575
     576{
     577    // Extract information from the extended pointer
     578    boot_remote_spinlock_t * ptr = (boot_remote_spinlock_t *)GET_PTR( lock_xp );
     579    uint32_t                 cxy = GET_CXY( lock_xp );
     580
     581    // get next free ticket
     582    uint32_t ticket = boot_remote_atomic_add( XPTR( cxy , &ptr->ticket ) , 1 );
     583
     584    // poll the current slot index
     585    while ( boot_remote_lw( XPTR( cxy , &ptr->current ) ) != ticket )
     586    {
     587        asm volatile ("nop");
     588    }
     589
     590}  // boot_remote_lock()
     591
     592/////////////////////////////////////////
     593void boot_remote_unlock( xptr_t lock_xp )
     594{
     595    asm volatile ( "sync" );   // for consistency
     596
     597    // Extract information from the extended pointer
     598    boot_remote_spinlock_t * ptr = (boot_remote_spinlock_t *)GET_PTR( lock_xp );
     599    uint32_t                 cxy = GET_CXY( lock_xp );
     600    xptr_t            current_xp = XPTR( cxy , &ptr->current );
     601
     602    // get current index value
     603    uint32_t current = boot_remote_lw( current_xp );
     604
     605    // increment current index
     606    boot_remote_sw( current_xp , current + 1 );
     607
     608}  // boot_remote_unlock()
     609
     610
Note: See TracChangeset for help on using the changeset viewer.