8

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
  • using machine xilinx-zynq-a9
  • processor cortex-a9
  • as this is baremetal, the executable is a self contained ELF file
  • -m 512M signifies the platform has 512 MiB of RAM
  • -s is a shortcut for -gdb tcp::1234
  • -S means freeze CPU at startup

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.

  • -serial mon:stdio
    • nothing
  • -serial null -serial mon:stdio (because Cortex-A9 has two UARTs)
    • nothing
  • the above two with -semihosting added
    • nothing
  • -serial stdio
    • cannot use stdio by multiple character devices
    • could not connect serial device to character backend 'stdio'
  • -console=/dev/tty
    • invalid option
  • -curses
    • black screen, no output

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.

thatjames
  • 438
  • 1
  • 5
  • 10
  • My answer also links to: https://stackoverflow.com/questions/38914019/how-to-make-bare-metal-arm-programs-and-run-them-on-qemu/50981397#50981397 which contains two UART QEMU setups. The RPI one runs identical code BTW, just with a different UART address, no "bootloader" (besides whatever unavoidable stuff RPI forces you to run before payload). – Ciro Santilli OurBigBook.com Aug 19 '20 at 15:16

1 Answers1

6

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.

Frant
  • 5,382
  • 1
  • 16
  • 22
  • Semihosting would be the ideal way to view the printed output of the program. For reasons associated with [my research](https://ieeexplore.ieee.org/document/8933038), I am using a different build flow that uses clang for the frontend. If I understand your answer, you're saying that the program must be compiled differently to support semihosting. Is that correct? – thatjames Mar 06 '20 at 17:06
  • @thatjames: This is more that the program should be linked to a different version of libraries, see the content of file `/opt/arm/9/gcc-arm-9.2-2019.12-aarch64-arm-none-eabi/arm-none-eabi/lib/rdimon.specs` for the list of libraries a semihosted program should be linked to. I am not familiar with clang, but if you can link with the set of `newlib/gcc` libraries listed in `rdimon.specs`, chances are that this would work. I would be interested in the outcome. – Frant Mar 06 '20 at 18:00
  • @thatjames: And in the case you would think I properly answered your question, please feel free to accept the answer - I spent a couple of hours working on an answer to your question, mainly because I found it interesting/challenging - the clang thing was not part of the original question I guess. – Frant Mar 06 '20 at 18:09
  • @thatjames: The location for the rdimon spec file is obviously wrong, the correct would be `/opt/arm/9/gcc-arm-9.2-2019.12-x86_64-arm-none-eabi/arm-none-eabi/lib/rdimon.specs`, assuming the `gcc-arm-9.2-2019.12-x86_64-arm-none-eabi` toolchain available from `arm` was extracted into `/opt`. – Frant Mar 06 '20 at 18:41
  • @thatjames: According to [this paper](https://archive.fosdem.org/2018/schedule/event/crosscompile/attachments/slides/2107/export/events/attachments/crosscompile/slides/2107/How_to_cross_compile_with_LLVM_based_tools.pdf), it seems you can use clang for building a semi-hosted application. – Frant Mar 09 '20 at 12:27
  • Yeah, I guess I wasn't clear on exactly the toolchain I was using. I tried some of your suggestions, without any visible success. Probably that's because the semihosting interface isn't being invoked correctly, because there aren't system calls. I will try what you said about using the newlib libraries. – thatjames Mar 09 '20 at 16:42
  • @thatjames: The hello semihosting example should work out of the box. The `UART0` one should too, provided you use the `patch` command and the `.diff` file I provided - I may try to provide a simpler example. – Frant Mar 10 '20 at 12:18
  • you were right about semihosting; I had to change the init functions in my BSP based on [this file](https://github.com/eblot/newlib/blob/master/newlib/libc/sys/arm/crt0.S), and I had to link against `librdimon.a` found in `libnewlib-arm-none-eabi` using the specs file. But now it works, thanks! – thatjames Apr 06 '20 at 23:05
  • @thatjames: thank you for the update, I am glad it worked. – Frant Apr 06 '20 at 23:40