armqemuuartbare-metalreliability

Qemu baremetal emulation - how to view UART output?


Question:

How do I get the UART output from a baremetal program run with Qemu?

Background

Here is the command line invocation I have been using:

qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel $BUILD_DIR/mm.elf -m 512M -s -S

The ELF file I am using (mm.elf) performs a simple matrix multiply operation, and then prints whether it succeeded or failed, and how long it took to run. The ELF was compiled using the Xilinx ARM toolchain. I am using this for software fault injection. Currently I use GDB to ask for the values of the variables which are supposed to be printed. However, as there are many things which could go wrong with printing in the context of fault injection, it would be nice to see what is actually sent over UART.

Related answers:

redirect QEMU window output to terminal running qemu

This has some suggestions I tried, but it isn't applicable because the question was about getting the Linux boot messages in the host terminal window.

How to run a program without an operating system?

This is one isn't very related because it still assumes that the user has a bootloader of some kind. While there must technically be a bootloader for the application to run at all, Xilinx provides this system code in files like boot.S, which are then compiled into the ELF file as code which runs before main.

Things that I have tried:

I tried adding each of these onto the end of my current Qemu command. The results follow the parameters tried.

Investigation

I looked at the disassembly of the ELF file and verified that the address to which the the UART messages are being written is the same as the Qemu setup expects (info mtree). Base address is 0xe0000000, the same in both places.

Goal

I want to be able to capture the output of messages sent to UART. If this is done by redirecting to stdout, that's fine. If it goes through a TCP socket, that's fine too. The fault injection setup uses Python, and Qemu is running as a subprocess, so it would be easy to get the output from either one of those sources.

Note: when run in the fault injection setup, the Qemu invocation is

qemu-system-arm -M xilinx-zynq-a9 -cpu cortex-a9 -nographic -kernel $BUILD_DIR/mm.elf -m 512M -gdb tcp::3345 -S -monitor telnet::3347,server,nowait

The main differences being 1) the GDB port number is different (so multiple instances can run simultaneously) and 2) Qemu is to be controlled using a telnet connection over a socket, so it can be controlled by the Python script.


Solution

  • You need to initialize the UART prior to attempt outputing any characters. The UART0 emulation is working fine for example by using a slightly modified version of this program:

    /opt/qemu-4.2.0/bin/qemu-system-arm -semihosting --semihosting-config enable=on,target=native -nographic -serial mon:stdio -machine xilinx-zynq-a9 -m 768M -cpu cortex-a9 -kernel hello05.elf
    
    Hello number 1
    

    The output of the git diff command after modifications were made was:

    diff --git a/Hello01/Makefile b/Hello01/Makefile
    index 4a1b512..8d6d12a 100644
    --- a/Hello01/Makefile
    +++ b/Hello01/Makefile
    @@ -1,10 +1,10 @@
     ARMGNU ?= arm-linux-gnueabihf
    -COPS    =   
    +COPS    = -g -O0  
     ARCH    = -mcpu=cortex-a9 -mfpu=vfpv3 
    
     gcc : hello01.bin
    
    -all : gcc clang
    +all : gcc 
    
     clean :
        rm -f *.o
    @@ -15,8 +15,6 @@ clean :
        rm -f *.img
        rm -f *.bc
    
    -clang: hello02.bin
    -
     startup.o : startup.s
        $(ARMGNU)-as $(ARCH) startup.s -o startup.o
    
    diff --git a/Hello01/hello01.c b/Hello01/hello01.c
    index 20cb4a4..14ed2a0 100644
    --- a/Hello01/hello01.c
    +++ b/Hello01/hello01.c
    @@ -10,16 +10,16 @@
     */
    
    
    -#define UART1_BASE 0xe0001000
    -#define UART1_TxRxFIFO0 ((unsigned int *) (UART1_BASE + 0x30))
    +#define UART0_BASE 0xe0000000
    +#define UART0_TxRxFIFO0 ((unsigned int *) (UART0_BASE + 0x30))
    
    -volatile unsigned int * const TxRxUART1 = UART1_TxRxFIFO0;
    +volatile unsigned int * const TxRxUART0 = UART0_TxRxFIFO0;
    
     void print_uart1(const char *s) 
     {
         while(*s != '\0') 
         {     /* Loop until end of string */
    -    *TxRxUART1 = (unsigned int)(*s); /* Transmit char */
    +    *TxRxUART0 = (unsigned int)(*s); /* Transmit char */
         s++; /* Next char */
         }
     }
    @@ -28,4 +28,4 @@ void c_entry()
     {
        print_uart1("\r\nHello world!");
        while(1) ; /*dont exit the program*/
    -}
    \ No newline at end of file
    +}
    diff --git a/Hello05/Makefile b/Hello05/Makefile
    index 9d3ca23..bc9bb61 100644
    --- a/Hello05/Makefile
    +++ b/Hello05/Makefile
    @@ -1,5 +1,5 @@
     ARMGNU ?= arm-linux-gnueabihf
    -COPS    =   
    +COPS    =  -g -O0
     ARCH    = -mcpu=cortex-a9 -mfpu=vfpv3 
    
     gcc : hello05.bin
    diff --git a/Hello05/hello05.c b/Hello05/hello05.c
    index 1b92dde..01ce7ee 100644
    --- a/Hello05/hello05.c
    +++ b/Hello05/hello05.c
    @@ -26,7 +26,7 @@
    
     void c_entry() 
     {
    -   init_uart1_RxTx_115200_8N1();
    +   init_uart0_RxTx_115200_8N1();
        printf("\nHello number %d\n",1);
        while(1) ; /*dont exit the program*/
     }
    diff --git a/Hello05/xuartps.c b/Hello05/xuartps.c
    index bdf7ad1..74f68bd 100644
    --- a/Hello05/xuartps.c
    +++ b/Hello05/xuartps.c
    @@ -16,42 +16,42 @@
     void putc(int *p ,char c);
    
     /*
    -* Initiate UART1  ( /dev/ttyACM0 on host computer )
    +* Initiate UART0  ( /dev/ttyACM0 on host computer )
     *   115,200 Baud 8-bit No-Parity 1-stop-bit
     */
    -void init_uart1_RxTx_115200_8N1()
    +void init_uart0_RxTx_115200_8N1()
     {
       /* Disable the transmitter and receiver before writing to the Baud Rate Generator */
    -  UART1->control_reg0=0; 
    +  UART0->control_reg0=0; 
    
       /* Set Baudrate to 115,200 Baud */
    -  UART1->baud_rate_divider =XUARTPS_BDIV_CD_115200;
    -  UART1->baud_rate_gen=     XUARTPS_BRGR_CD_115200;
    +  UART0->baud_rate_divider =XUARTPS_BDIV_CD_115200;
    +  UART0->baud_rate_gen=     XUARTPS_BRGR_CD_115200;
    
       /*Set 8-bit NoParity 1-StopBit*/
    -  UART1->mode_reg0   =   XUARTPS_MR_PAR_NONE;  
    +  UART0->mode_reg0   =   XUARTPS_MR_PAR_NONE;  
    
       /*Enable Rx & Tx*/
    -  UART1->control_reg0=   XUARTPS_CR_TXEN | XUARTPS_CR_RXEN | XUARTPS_CR_TXRES | XUARTPS_CR_RXRES ;      
    +  UART0->control_reg0=   XUARTPS_CR_TXEN | XUARTPS_CR_RXEN | XUARTPS_CR_TXRES | XUARTPS_CR_RXRES ;      
    
    
     }
    
    -void sendUART1char(char s)
    +void sendUART0char(char s)
     {
       /*Make sure that the uart is ready for new char's before continuing*/
    -  while ((( UART1->channel_sts_reg0 ) & UART_STS_TXFULL) > 0) ;
    +  while ((( UART0->channel_sts_reg0 ) & UART_STS_TXFULL) > 0) ;
    
       /* Loop until end of string */
    -  UART1->tx_rx_fifo= (unsigned int) s; /* Transmit char */
    +  UART0->tx_rx_fifo= (unsigned int) s; /* Transmit char */
     }
    
     /* "print.h" uses this function for is's printf implementation */
     void putchar(char c)
     {
       if(c=='\n')
    -    sendUART1char('\r');
    -  sendUART1char(c);
    +    sendUART0char('\r');
    +  sendUART0char(c);
     }
    
     /* <stdio.h>'s printf uses puts to send chars
    @@ -61,9 +61,9 @@ int puts(const char *s)
         while(*s != '\0') 
         { 
          if(*s=='\n')
    -         sendUART1char('\r');
    +         sendUART0char('\r');
    
    -      sendUART1char(*s); /*Send char to the UART1*/       
    +      sendUART0char(*s); /*Send char to the UART0*/       
           s++; /* Next char */
         }
         return 0;
    diff --git a/Hello05/xuartps.h b/Hello05/xuartps.h
    index fc5008f..64e3b88 100644
    --- a/Hello05/xuartps.h
    +++ b/Hello05/xuartps.h
    @@ -13,7 +13,7 @@
        #define u32 unsigned int
     #endif
    
    -#define UART1_BASE 0xe0001000
    +#define UART0_BASE 0xe0000000
     // Register Description as found in
     //    B.33 UART Controller (UART) p.1626
     struct XUARTPS{
    @@ -34,7 +34,7 @@ struct XUARTPS{
             u32 Flow_delay_reg0;            /* Flow Control Delay Register  def=0*/
             u32 Tx_FIFO_trigger_level;};    /* Transmitter FIFO Trigger Level Register */
    
    -static struct XUARTPS *UART1=(struct XUARTPS*) UART1_BASE;        
    +static struct XUARTPS *UART0=(struct XUARTPS*) UART0_BASE;        
    
     /*
         Page 496
    @@ -87,11 +87,11 @@ static struct XUARTPS *UART1=(struct XUARTPS*) UART1_BASE;
     #define XUARTPS_MR_CLKS_REF_CLK 0       /*  0: clock source is uart_ref_clk*/
    
     /*
    -* Initiate UART1  ( /dev/ttyACM0 on host computer )
    +* Initiate UART0  ( /dev/ttyACM0 on host computer )
     *   115,200 Baud 8-bit No-Parity 1-stop-bit
     */
    -void init_uart1_RxTx_115200_8N1();
    -void sendUART1char(char s);
    +void init_uart0_RxTx_115200_8N1();
    +void sendUART0char(char s);
     int puts(const char *s);
     //void putc((void*), char);
    

    The command executed from the ZedBoard-BareMetal-Examples/Hello05 directory for building the modified Hello05 example was:

    make ARMGNU=/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi clean all 
    

    This being said, the last comment from your previous post made me think that you may just want to be able to see the output of your program, but not necessarily by using UART0.

    If this is the case, using the Angel/Semihosting interface would do the job - I understand you may have attempted to go this way.

    Example:

    // hello.c:
    
    #include <stdlib.h>
    
    int main(int argc, char** argv)
    {
        printf("Hello, World!\n");
        return EXIT_SUCCESS;
    }
    

    gcc command:

    /opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc -g -O0 --specs=rdimon.specs -o hello.elf hello.c
    

    qemu command:

    /opt/qemu-4.2.0/bin/qemu-system-arm -semihosting --semihosting-config enable=on,target=native -nographic -serial mon:stdio -machine xilinx-zynq-a9 -m 768M -cpu cortex-a9 -kernel hello.elf
    

    Outcome:

    Hello, World!
    

    Using the semihosting interface would allow you to read/write files, read user input, and to use some of the xUnit testing frameworks available for either C or C++ - I have been for example successfully be using CppUnit with QEMU and the Semihosting interface. at several occasions.

    I hope this help.