Example Development Flow

This page will contain brief examples of how to compile and load your program into QEMU, and then debug it.



Acquiring the Tools

For this example you will need:

  • aarch64-linux-gnu-gcc
  • gdb-multiarch or aarch64-linux-gnu-gdb
  • device tree compiler (dtc)
  • scp (on the guest)

aarch64-linux-gnu-gcc, aarch64-linux-gnu-gdb, and dtc are bundled with PetaLinux, Yocto, or Vitis tools.
scp is available on the guest by default if using these tools as well.

Compiling Your Application

For this example, we're going to use an example bare metal program called myapp, and we're going to build it for the ARM64 application processing unit (APU) on a ZCU102 development board.

myapp.c can be found here.

This application transmits data through UART0 and has a simple menu with three options:

  1. Generate data to be printed out to the UART
  2. Print out data to the UART
  3. Exit

For compiling it, we'll use AArch64 gcc and include debugging symbols.

aarch64-linux-gnu-gcc -g -Wall myapp.c -o myapp.elf

Loading Your Application

There are a few ways to load your application into the guest image.
For this page we will use the SSH method.

TFTP

Files can be copied between the host and guest by using TFTP.
Remember that the gateway IP between the guest and host is 10.0.2.2.

root@xilinx-zcu102-2020_2:~# tftp -g -r myapp.elf 10.0.2.2
root@xilinx-zcu102-2020_2:~# chmod 755 myapp.elf
root@xilinx-zcu102-2020_2:~# ls -la
total 20
drwx------    2 root     root            60 Dec  2 19:29 .
drwxr-xr-x    3 root     root            60 Aug 28 05:26 ..
-rwxr-xr-x    1 root     root         18656 Dec  2 19:29 myapp.elf

See also: File Transfer with TFTP.

SSH

After booting into Linux, you can copy your application from the host machine to the guest by using SSH.

First, find the IP of the host machine

$ ifconfig
eth0 Link encap:Ethernet HWaddr <MAC addr>
inet addr:<host IP> Bcast:<Bcast IP> Mask:255.255.252.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:29112513 errors:0 dropped:0 overruns:0 frame:0
TX packets:112423727 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:16274872729 (16.2 GB) TX bytes:163292974163 (163.2 GB)
Memory:90100000-9017ffff

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:31093619 errors:0 dropped:0 overruns:0 frame:0
TX packets:31093619 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:2366150233 (2.3 GB) TX bytes:2366150233 (2.3 GB)

Then on the guest machine, use scp to copy the file from the host to the guest.

root@xilinx-zcu102-2020_2:~# scp <your host machine username>@<your host machine IP>:/scratch/doc-example/myapp.elf .

Host '<host IP>' is not in the trusted hosts file.
(ecdsa-sha2-nistp256 fingerprint sha1!! 18:7e:92:d0:33:ed:97:e7:cb:b2:f7:b1:5d:52:5f:a6:34:9a:97:f9)
Do you want to continue connecting? (y/n) y
<host user>@<host IP>'s password: 
myapp.elf                                                   100%   18KB  17.7KB/s   00:00    
root@xilinx-zcu102-2020_2:~#

See also: File Transfer with SSH.

Loading your Application to the Guest Image Using PetaLinux

To add your application to the guest by using PetaLinux, it first must be part of a PetaLinux project.

Adding an Application to the RootFS of a PetaLinux Project

$ cd <petalinux-project-root>
$ petalinux-create -t apps --template install --name myapp --enable

This will create a new application that can be added to your PetaLinux project.

The application can be found in: <petalinux-project-root>/project-spec-meta-user/recipes-apps/<your-application-name>.

Change directory to the files directory and remove the existing myapp application; then copy your application to the current directory.

Building the Application

$ petalinux-config -c rootfs
# apps -> ensure your application is checked.
$ petalinux-build

The newly built image will appear under the <petalinux-project-root>/images/linux/ directory on the host.

Your application will appear in /usr/bin/<your-application-name> in the guest after booting your image.

For more information on the above steps, see chapter 8 in the PetaLinux Tools Reference Guide.

Kernel-Intrusive Application Debugging

Now that myapp.elf is on the guest machine, let's see if it works.

When we debug myapp.elf, we will use GDB on the host machine and connect it to QEMU's GDB server.
This means that we are debugging the QEMU image (the Linux Kernel), along with our application.

More details on intrusive debugging with GDB can be found here.

root@xilinx-zcu102-2020_2:~# ./myapp.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

g
Gen: 71 a7 5e c9 c7 67 82 8f a7 1d 8b 7a 31 44 ad f5
TX->71
TX->a7
TX->5e
TX->c9
TX->c7
TX->67
TX->82
TX->8f
TX->a7
TX->1d
TX->8b
TX->7a
TX->31
TX->44
TX->ad
TX->f5
g
Gen: 81 4d 91 4b 57 9d b8 08 a1 3d 19 8a ff 11 59 70
TX->81
TX->4d
TX->91
TX->4b
TX->57
TX->9d
TX->b8
TX->08
TX->a1
TX->3d
TX->19
TX->8a
TX->ff
TX->11
TX->59
TX->70

It mostly works.  It looks like when we generate the UART data it's transmitted right away.

Let's debug it.

komlodi@machine:/scratch/doc-example$ gdb-multiarch
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) symbol-file myapp.elf
Reading symbols from myapp.elf...done.
(gdb) b 103
Breakpoint 1 at 0x400a80: file myapp.c, line 103.
(gdb) target remote :9000
Remote debugging using :9000
0xffffff8008112eb4 in ?? ()
(gdb) c
Continuing.

Re-run myapp.elf on the guest.

root@xilinx-zcu102-2020_2:~# ./myapp.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

Now our GDB running on the host should hit the breakpoint in the while loop.

Breakpoint 1 at 0x400a40: file myapp.c, line 103.
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 1, prog_loop (uart=0x7f8c055000) at myapp.c:103
103 while (!done) {
(gdb) n
104 fgets(buf, sizeof(buf), stdin);
(gdb)
[Switching to Thread 1.4]

106 switch(buf[0]) {
(gdb)
108 printf("Gen:");
(gdb)

Thread 4 received signal SIGTRAP, Trace/breakpoint trap.

This is a perfect situation of when we want to ignore a signal.  GDB received a SIGTRAP and lost control of the program.

Let's make sure this doesn't happen again.

^C
Thread 4 received signal SIGINT, Interrupt.
0xffffff80087d7b20 in ?? ()
(gdb) thread apply all handle SIGTRAP nostop ignore

Thread 4 (Thread 1.4):
SIGTRAP is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal Stop Print Pass to program Description
SIGTRAP No Yes No Trace/breakpoint trap

Thread 3 (Thread 1.3):
SIGTRAP is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal Stop Print Pass to program Description
SIGTRAP No Yes No Trace/breakpoint trap

Thread 2 (Thread 1.2):
SIGTRAP is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal Stop Print Pass to program Description
SIGTRAP No Yes No Trace/breakpoint trap

Thread 1 (Thread 1.1):
SIGTRAP is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal Stop Print Pass to program Description
SIGTRAP No Yes No Trace/breakpoint trap
(gdb) c
Continuing.


Now let's try that again.

[Switching to Thread 1.3]

Thread 3 hit Breakpoint 2, prog_loop (uart=0x7f8c055000) at myapp.c:106
106	        switch(buf[0]) {
(gdb) 
Continuing.
  
Thread 3 hit Breakpoint 2, prog_loop (uart=0x7f8c055000) at myapp.c:106
106	        switch(buf[0]) {
(gdb) n
108	                printf("Gen:");
(gdb) 
109	                for (i=0; i<sizeof(buf); ++i) {
(gdb) 
110	                    buf[i] = rand() & 0xFF;
(gdb) 
111	                    printf(" %.2x", buf[i]);
(gdb) 
109	                for (i=0; i<sizeof(buf); ++i) {
(gdb) 
110	                    buf[i] = rand() & 0xFF;
(gdb) 
111	                    printf(" %.2x", buf[i]);
(gdb) 
109	                for (i=0; i<sizeof(buf); ++i) {
(gdb) b 113
Breakpoint 3 at 0x400adc: file myapp.c, line 113.
(gdb) c
Continuing.
[Switching to Thread 1.2]

Thread 2 hit Breakpoint 3, prog_loop (uart=0x7f8c055000) at myapp.c:113
113	                puts("");
(gdb) n
115	                uart_tx(uart, buf, sizeof(buf));
(gdb) 


So after we generate the data, it goes right to the transmit case statement.  Let's double check the switch statement.

switch(cmd[0]) {
	case 'g':
		printf("Gen:");
		for (i=0; i<sizeof(buf); ++i) {
			buf[i] = rand() & 0xFF;
			printf(" %.2x", buf[i]);
		}
		puts("");
	case 't':
		uart_tx(uart, buf, sizeof(buf));
		break;
	case '\n':
		done = true;
		break;
	default:
		printf("Unknown command %s", cmd);
		break;
}

We forgot to put a break at the end of the 'g' case statement, so our code fell through to the next case.

This is an easy fix.

switch(cmd[0]) {
	case 'g':
		printf("Gen:");
		for (i=0; i<sizeof(buf); ++i) {
			buf[i] = rand() & 0xFF;
			printf(" %.2x", buf[i]);
		}
		puts("");
		break; // <--
	case 't':
		uart_tx(uart, buf, sizeof(buf));
		break;
	case '\n':
		done = true;
		break;
	default:
		printf("Unknown command %s", cmd);
		break;
}


Now let's put our new code on the guest machine, reload our GDB symbols, and run it.

komlodi@machine:/scratch/doc-example$ aarch64-linux-gnu-gcc -g -Wall myapp.c -o myapp.elf


^C
Thread 2 received signal SIGINT, Interrupt.
0xffffff80080cedf4 in ?? ()
(gdb) symbol-file myapp.elf
Load new symbol table from "myapp.elf"? (y or n) y
Reading symbols from myapp.elf...done.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400a80 in prog_loop at myapp.c:106
breakpoint already hit 5 times
2 breakpoint keep y 0x0000000000400a40 in prog_loop at myapp.c:102
breakpoint already hit 1 time
3 breakpoint keep y 0x0000000000400ae8 in prog_loop at myapp.c:113
breakpoint already hit 1 time
(gdb) d 1 2 3
(gdb) c


root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/doc-example/myapp.elf .
<host user>@1<host IP's password: 
myapp.elf                                                   100%   18KB  18.2KB/s   00:00    
root@xilinx-zcu102-2020_2:~# ./myapp.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

g
Gen: 0f 08 45 3e a8 34 72 37 8d 98 8c ae 87 6a 77 e5
t
TX->0f
TX->08
TX->45
TX->3e
TX->a8
TX->34
TX->72
TX->37
TX->8d
TX->98
TX->8c
TX->ae
TX->87
TX->6a
TX->77
TX->e5
g
Gen: 17 69 69 b3 1c f2 46 3b 62 af 08 39 ac 4b c4 bb
t
TX->17
TX->69
TX->69
TX->b3
TX->1c
TX->f2
TX->46
TX->3b
TX->62
TX->af
TX->08
TX->39
TX->ac
TX->4b
TX->c4
TX->bb

There, that looks better.

Non-Kernel-Intrusive Application Debugging

For this example we will load another application called myapp-segfault.c, which can be found here.
It can be loaded using any of the methods outlined in Loading Your Application, so we will not repeat that here.

In this case, we need to load the source file(s) onto the QEMU guest as well, otherwise GDB will not be able to display what line we're on.


When we debug something non-intrusively in QEMU, we have GDB on the QEMU machine.
Debugging in this way behaves exactly like it would as if you were debugging a program locally on a Linux machine.

This means you cannot debug your kernel using this method.

More information on non-intrusive application debugging can be found here.

First, let's load GDB onto our machine.  We'll use scp in this example and assume we've already downloaded an ARM64 GDB package.

komlodi@machine:/scratch/development-example$ sudo dpkg-deb -R gdb_7.12-6_arm64.deb .
komlodi@machine:/scratch/development-example$ sudo zip -r gdb.zip etc usr

Then on the QEMU guest:

root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/development-example/gdb.zip .
root@xilinx-zcu102-2020_2:~# unzip gdb.zip
root@xilinx-zcu102-2020_2:~# cp -rv etc usr /

Now let's run myapp-segfault.c and see if it works.

root@xilinx-zcu102-2020_2:~# ./myapp-segfault.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

g
Segmentation fault
root@xilinx-zcu102-2020_2:~# ./myapp-segfault.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

t
TX->50
TX->72
TX->6f
TX->67
TX->72
TX->61
TX->6d
TX->20
TX->73
TX->74
TX->61
TX->72
TX->74
TX->21
TX->0a
g
Segmentation fault
root@xilinx-zcu102-2020_2:~# 

It looks like we consistently have a segmentation fault whenever we generate new data.

Let's debug it.

root@xilinx-zcu102-2020_2:~# gdb
gdb: /lib/libncurses.so.5: no version information available (required by gdb)
gdb: /lib/libncurses.so.5: no version information available (required by gdb)
gdb: /lib/libncurses.so.5: no version information available (required by gdb)
gdb: /lib/libtinfo.so.5: no version information available (required by gdb)
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "aarch64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file myapp-segfault.elf
Reading symbols from myapp-segfault.elf...done.
(gdb) r
Starting program: /home/root/myapp-segfault.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

g

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400c68 in prog_loop (uart=0x7fbf6fb000) at myapp-segfault.c:89
89	                    buf[i] = rand() & 0xFF;

We segfault when accessing buf, let's look closer.

(gdb) backtrace
#0  0x0000000000400c68 in prog_loop (uart=0x7fbf6fb000) at myapp-segfault.c:89
#1  0x0000000000400d88 in main () at myapp-segfault.c:120
(gdb) info locals
i = 0
buf = 0x400e68 "Program start!\n"
cmd = "g\n\000\000\000\000\000\000\230\r@\000\000\000\000"
done = false

buf isn't NULL, but the address it points to is very close to execution memory.

Since "Program start!\n" is a constant, it's most likely going to reside in a read-only section of memory.
Let's verify that.

(gdb) maintenance info sections
Exec file:
    `/home/root/myapp-segfault.elf', file type elf64-littleaarch64.
 [0]     0x00400200->0x0040021b at 0x00000200: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
 [1]     0x0040021c->0x0040023c at 0x0000021c: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
 [2]     0x0040023c->0x00400260 at 0x0000023c: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS
 [3]     0x00400260->0x00400308 at 0x00000260: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
 [4]     0x00400308->0x004004d0 at 0x00000308: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
 [5]     0x004004d0->0x0040059a at 0x000004d0: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
 [6]     0x0040059a->0x004005c0 at 0x0000059a: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
 [7]     0x004005c0->0x00400600 at 0x000005c0: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
 [8]     0x00400600->0x00400648 at 0x00000600: .rela.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
 [9]     0x00400648->0x004007c8 at 0x00000648: .rela.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
 [10]     0x004007c8->0x004007dc at 0x000007c8: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
 [11]     0x004007e0->0x00400900 at 0x000007e0: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
 [12]     0x00400900->0x00400e14 at 0x00000900: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
 [13]     0x00400e14->0x00400e24 at 0x00000e14: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
 [14]     0x00400e28->0x00400f1f at 0x00000e28: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
 [15]     0x00400f20->0x00400f24 at 0x00000f20: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
 [16]     0x00411de0->0x00411de8 at 0x00001de0: .init_array ALLOC LOAD DATA HAS_CONTENTS
 [17]     0x00411de8->0x00411df0 at 0x00001de8: .fini_array ALLOC LOAD DATA HAS_CONTENTS
 [18]     0x00411df0->0x00411df8 at 0x00001df0: .jcr ALLOC LOAD DATA HAS_CONTENTS
 [19]     0x00411df8->0x00411fd8 at 0x00001df8: .dynamic ALLOC LOAD DATA HAS_CONTENTS
 [20]     0x00411fd8->0x00411fe8 at 0x00001fd8: .got ALLOC LOAD DATA HAS_CONTENTS
 [21]     0x00411fe8->0x00412080 at 0x00001fe8: .got.plt ALLOC LOAD DATA HAS_CONTENTS
 [22]     0x00412080->0x00412090 at 0x00002080: .data ALLOC LOAD DATA HAS_CONTENTS
 [23]     0x00412090->0x004120a8 at 0x00002090: .bss ALLOC
 [24]     0x00000000->0x0000003b at 0x00002090: .comment READONLY HAS_CONTENTS
 [25]     0x00000000->0x00000030 at 0x000020cb: .debug_aranges READONLY HAS_CONTENTS
 [26]     0x00000000->0x000004c7 at 0x000020fb: .debug_info READONLY HAS_CONTENTS
 [27]     0x00000000->0x0000016c at 0x000025c2: .debug_abbrev READONLY HAS_CONTENTS
 [28]     0x00000000->0x00000153 at 0x0000272e: .debug_line READONLY HAS_CONTENTS
 [29]     0x00000000->0x00000120 at 0x00002888: .debug_frame READONLY HAS_CONTENTS
 [30]     0x00000000->0x000002b5 at 0x000029a8: .debug_str READONLY HAS_CONTENTS

On line [14], we can see the address that buf points to, 0x400e68, resides in that range.
That section is called .rodata, and is labeled as READONLY.  This is why we receive a segmentation fault when we write to it.

There are a few ways to fix this, the most straightforward change would be to make buf an array and copy "Program Start!\n" to it before the while loop.

static void prog_loop(void *uart)                                               
{                                                                               
    size_t i;                                                                   
//    char *buf = "Program start!\n";
    char buf[16]; // <--                                             
    char cmd[16];                                                               
    bool done;                       

    strcpy(buf, "Program start!\n"); // <--                                           
                                                                                
    while (!done) {                                                             
        fgets(cmd, sizeof(cmd), stdin);                                         
                                                                                
        switch(cmd[0]) {                                                        
            case 'g':                                                           
                printf("Gen:");                                                 
                for (i=0; i<strlen(buf); ++i) {                                 
                    buf[i] = rand() & 0xFF;                                     
                    printf(" %.2x", buf[i]);                                    
                }                                                               
                puts("");                                                       
                break;                                                          
            case 't':                                                           
                uart_tx(uart, buf, strlen(buf));                                
                break;                                                          
            case '\n':                                                          
                done = true;                                                    
                break;                                                          
            default:                                                            
                printf("Unknown command %s", cmd);                              
                break;                                                          
        }                                                                       
    }                                                                           
} 

Now let's compile our changes, put the new binary on the guest, and run it.

komlodi@machine:/scratch/development-example$ aarch64-linux-gnu-gcc myapp-segfault.c -Wall -g -o myapp-segfault.elf

On the guest:

root@xilinx-zcu102-2020_2:~# scp <host user>@<host IP>:/scratch/development-example/myapp-segfault.elf .
<host user>@<host IP>'s password: 
myapp-segfault.elf                                          100%   18KB  17.7KB/s   00:00    
root@xilinx-zcu102-2020_2:~# ./myapp-segfault.elf 

***************UART TX MENU***************
g: Generate new data to transmit
t: Transmit the UART data
<RETURN>: Exit

t
TX->50
TX->72
TX->6f
TX->67
TX->72
TX->61
TX->6d
TX->20
TX->73
TX->74
TX->61
TX->72
TX->74
TX->21
TX->0a
g
Gen: c9 ca 06 e6 9b 8b aa c2 d7 69 3d fb 59 27 5f
t
TX->c9
TX->ca
TX->06
TX->e6
TX->9b
TX->8b
TX->aa
TX->c2
TX->d7
TX->69
TX->3d
TX->fb
TX->59
TX->27
TX->5f

root@xilinx-zcu102-2020_2:~# 

QEMU Module Debug Printing

This section gives a brief example showing how to make QEMU modules print debug information.
A more detailed page showing how to do QEMU module debug printing is available here.

If building QEMU from source, QEMU provides a way to enable debug printing for modules.

Now let's say you wanted to see the register reads and writes of the UART.
To do this we need to find the UART module that QEMU is using and then enable debug printing.

Finding the Module

To find out what UART module QEMU is using, we will look in the device tree files.
If you have access to the DTS files, look in those.  Otherwise, unflatten the DTB and look at the DTS output.
For this example, we'll unflatten the DTB.

komlodi@machine:/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images$ dtc -I dtb -O dts system.dtb -o system.dts
komlodi@machine:/scratch/petalinux-images/xilinx-zcu102-2019.2/pre-built/linux/images$ vim system.dts

We're working on a Zynq UltraScale+ MPSoC, so UART0 is at address 0xFF000000. 

1803         serial@ff000000 {                                                       
1804             u-boot,dm-pre-reloc;                                                
1805             compatible = "cdns,uart-r1p12", "xlnx,xuartps";                     
1806             status = "okay";                                                    
1807             interrupt-parent = <0x4>;                                           
1808             interrupts = <0x0 0x15 0x4>;                                        
1809             reg = <0x0 0xff000000 0x0 0x1000>;                                  
1810             clock-names = "uart_clk", "pclk";                                   
1811             power-domains = <0xc 0x21>;                                         
1812             clocks = <0x3 0x38 0x3 0x1f>;                                       
1813             pinctrl-names = "default";                                          
1814             pinctrl-0 = <0x1d>;                                                 
1815             cts-override;                                                       
1816             device_type = "serial";                                             
1817             port-number = <0x0>;                                                
1818         };

Here's the UART in the device tree.  What we care about are the values of the compatible string.
These values are used by QEMU to determine what module should be used to model the hardware.
QEMU will scan the compatible strings from left to right, and use the first one it is capable of modeling.

It isn't immediately obvious what a cdns,uart-r1p12 is, aside from it being a UART.  Let's look for that.

komlodi@machine:/scratch/proj/qemu/build$ find .. -name "*uart*"
../hw/riscv/sifive_uart.c
../hw/char/lm32_juart.c
../hw/char/cmsdk-apb-uart.c
../hw/char/omap_uart.c
../hw/char/grlib_apbuart.c
../hw/char/xilinx_iomod_uart.c
../hw/char/exynos4210_uart.c
../hw/char/milkymist-uart.c
../hw/char/nrf51_uart.c
../hw/char/mcf_uart.c
../hw/char/xilinx_uartlite.c
../hw/char/lm32_uart.c
../hw/char/cadence_uart.c
../hw/char/digic-uart.c

cadence_uart.c looks like the most likely file, let's look at that one.

In most situations you can use grep on the compatible string and find the module that way.


Enabling Module Debug Printing

Most modules will have a sequence of debug code at the top of the file that will look something like this:

#ifdef CADENCE_UART_ERR_DEBUG                                                   
#define DB_PRINT(...) do { \                                                    
	fprintf(stderr,  ": %s: ", __func__); \                                     
    fprintf(stderr, ## __VA_ARGS__); \                                          
    } while (0)                                                                 
#else                                                                           
    #define DB_PRINT(...)                                                       
#endif   

or this:

#ifndef XLNX_ZYNQMP_GPIO_ERR_DEBUG                                              
#define XLNX_ZYNQMP_GPIO_ERR_DEBUG 1                                            
#endif   

In cadence_uart.c, we're looking at the top code block.
Add debug information by adding a definition for CADENCE_UART_ERR_DEBUG.
This will print any time a register in cadence_uart.c is accessed (if the module supports it), and any time the module code has a DB_PRINT statement.

#define CADENCE_UART_ERR_DEBUG // <--

#ifdef CADENCE_UART_ERR_DEBUG                                                   
#define DB_PRINT(...) do { \                                                    
	fprintf(stderr,  ": %s: ", __func__); \                                     
    fprintf(stderr, ## __VA_ARGS__); \                                          
    } while (0)                                                                 
#else                                                                           
    #define DB_PRINT(...)                                                       
#endif   


Some peripherals, such as UART and GPIO, are sometimes used by the guest image for debugging purposes, such as outputting a heartbeat or stdio and stderr output.


This means that enabling debug printing can potentially cause a lot of messages to be printed.
This can be disabled when building your image.

After recompiling QEMU, when we re-run myapp.elf we will see the UART reads and writes.

Gen: 74 0a 00 e7 74 7b 73 2a 6a 52 da 69 a0 b5 ba a2
TX->74
: uart_write:  offset:c0 data:00000074
: uart_read:  offset:b0 data:00000000
TX->0a
: uart_write:  offset:c0 data:0000000a
: uart_read:  offset:b0 data:00000000
TX->00
: uart_write:  offset:c0 data:00000000
: uart_read:  offset:b0 data:00000000
TX->e7
: uart_write:  offset:c0 data:000000e7
: uart_read:  offset:b0 data:00000000
TX->74
: uart_write:  offset:c0 data:00000074
: uart_read:  offset:b0 data:00000000
TX->4b
: uart_write:  offset:c0 data:0000004b
: uart_read:  offset:b0 data:00000000
TX->53
: uart_write:  offset:c0 data:00000043
: uart_read:  offset:b0 data:00000000
TX->2a
: uart_write:  offset:c0 data:0000002a
: uart_read:  offset:b0 data:00000000
TX->6a
: uart_write:  offset:c0 data:0000006a
: uart_read:  offset:b0 data:00000000
TX->52
: uart_write:  offset:c0 data:00000052
: uart_read:  offset:b0 data:00000000
TX->da
: uart_write:  offset:c0 data:000000da
: uart_read:  offset:b0 data:00000000
TX->69
: uart_write:  offset:c0 data:00000069
: uart_read:  offset:b0 data:00000000
TX->a0
: uart_write:  offset:c0 data:000000a0
: uart_read:  offset:b0 data:00000000
TX->b5
: uart_write:  offset:c0 data:000000b5
: uart_read:  offset:b0 data:00000000
TX->ba
: uart_write:  offset:c0 data:000000ba
: uart_read:  offset:b0 data:00000000
TX->a2
: uart_write:  offset:c0 data:000000a2
: uart_read:  offset:14 data:00000000

© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy