Using QEMU UART interface

Using QEMU UART interface

In the posts so far on the MistraSolutions blog I have covered different peripherals and interfaces used in embedded systems: I2C, SPI, USB.

One of the important interfaces that was not covered so far is the UART interface.

QEMU UART interface

QEMU supports UART interface for the serial console for Linux. When coupled with the correct Linux configuration, the UART interface will be available when QEMU is started.

The UART interface in QEMU is configured by passing the -serial <dev> argument. The different options that are supported can be checked in the official documentation.

Some of the <dev> options that can be interesting:

  • stdio - most commonly used, the console will be printed straight to the current terminal
  • null - no serial interface for the current UART port; this is most often used when multiple UART interfaces are to be used, or if console is not configured for the inteface 0, so the interfaces before interface to be used as console should be set to none (positional indexing is used, so if console should be on serial interface 2, then proper serial initialization would be -serial none -serial none -serial stdio)
  • pty:[name] - create a pseudo-tty port on the host to be used for interacting with the QEMU; if name is passed then symbolic link will be created, as a random interface under /dev/pts/X will be created
    • this is the option that will be used in this post

Enable second UART

Before second UART port can be used, we have to make sure that QEMU supports it. Looking at the Cubieboard source code, it currently supports only one UART port, so modification will be needed to enable other UART ports.

The following patch enables UART1-UART4 ports of the Allwinner A10 emulated processor

diff --git a/hw/arm/allwinner-a10.c b/hw/arm/allwinner-a10.c
index f1b399759a..febd895695 100644
--- a/hw/arm/allwinner-a10.c
+++ b/hw/arm/allwinner-a10.c
@@ -156,9 +156,11 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->sata), 0, qdev_get_gpio_in(dev, 56));

     /* FIXME use a qdev chardev prop instead of serial_hd() */
-    serial_mm_init(get_system_memory(), AW_A10_UART0_REG_BASE, 2,
-                   qdev_get_gpio_in(dev, 1),
-                   115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
+    for (size_t i = 0; i < 4; i++) {
+        serial_mm_init(get_system_memory(), AW_A10_UART0_REG_BASE + i * 0x400, 2,
+                    qdev_get_gpio_in(dev, i + 1),
+                    115200, serial_hd(i), DEVICE_LITTLE_ENDIAN);
+    }

     for (size_t i = 0; i < AW_A10_NUM_USB; i++) {
         g_autofree char *bus = g_strdup_printf("usb-bus.%zu", i);

After QEMU change is done, the Linux device tree for Cubieboard also needs to be updated to enable access to the UART2. The following patch can be used

diff --git a/arch/arm/boot/dts/allwinner/sun4i-a10-cubieboard.dts b/arch/arm/boot/dts/allwinner/sun4i-a10-cubieboard.dts
index f71142432..1d4d7e711 100644
--- a/arch/arm/boot/dts/allwinner/sun4i-a10-cubieboard.dts
+++ b/arch/arm/boot/dts/allwinner/sun4i-a10-cubieboard.dts
@@ -247,6 +247,12 @@ &uart0 {
  status = "okay";
 };

+&uart1 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&uart1_pins>;
+ status = "okay";
+};
+
 &usb_otg {
  dr_mode = "otg";
  status = "okay";

Configure QEMU UART

With UART enabled in QEMU and in the Linux, the QEMU can be started using the following command (this is the minimal command for this example)

qemu-system-arm \
  -machine cubieboard \
  -m 1G \
  -drive file=sd.img,format=raw,if=sd \
  -serial mon:stdio \
  -serial pty:/tmp/qemu-uart \
  -nographic

Once QEMU is started, we can use the microcom utility to open an interactive console to interact with the /dev/ttyS1

microcom /dev/ttyS1

Now we can proceed with the Python utility on the host, to enable the other side of communication.

Python interactive example

A simple Python script that increments ASCII value of each character that is received by 1 and returns it to sender follows (Pyserial is the only dependency)

import sys

import serial

try:
    ser = serial.Serial("/tmp/qemu-uart", baudrate=115200)  # open serial port
except serial.SerialException:
    print("QEMU not started")
    sys.exit(1)

while True:
    try:
        recv_char = ser.read(1)
        send_char = chr(ord(recv_char) + 1).encode()

        print(f"Received {recv_char}, sending {send_char}")

        ser.write(send_char)
    except serial.SerialException:
        print("QEMU is stopped, exiting")
        break
    except KeyboardInterrupt:
        if ser.is_open:
            ser.close()
        break

Summary

UART interface is very useful and interesting examples can be made for QEMU emulation. The great thing is that the peripheral can be made outside of QEMU, so same QEMU instance can be used with different peripherals.

Subscribe
If you would like to get information as soon as new content is published, please subscribe to the "MistraSolutions newsletter".