Adding ARM platform to QEMU 5.2.0

The SoC is being developed by the ASIC team, but the software team wants to develop the software concurrently for the hardware that is not yet available, at least not for another 3 months or longer. In this situation what do you do ?

In the early phase of SoC development you don't usually have hardware available, but you want to develop the software concurrently with the ASIC development. One option is to find the platform with the closest architecture and develop the software based on that platform, but it not always that easy to find. If you could, why would you want to continue with your SoC development ? You are already steps behind your competitors. This option will also come with a lot of clean-up work later on. The other option is to software emulated your platform such that we can develop and test your software implementation to its nearest target platform. This is what QEMU comes in to play. This blog is one of two parts series. One is to add new ARM platform into QEMU to emulate the hardware while the other part is to port FreeRTOS to support the new platform.

An open source, QEMU from qemu.org is a generic machine emulator and virtualizer that supports various CPU architectures and platforms. QEMU runs on most mainstream OS, Linux, Windows, and MacOS. Since it is an open source, you can customize by adding the new platform into this package, build, install and use it. This blogs documents about how I add a custom ARM Cortex R-5 platform in to the existing package. I call it real-time experimental SoC, rtx-soc platform. This platform will be added to the supported ARM CPU architectur the QEMU.

Components

Two main components needed: QEMU package (and other s/w dependencies required by this package) and the host PC (Linux). The software packages required by QEMU are listed in its respective QEMU site for building the QEMU. It is most likely that the needed dependencies are met if you fully installed the Linux distribution. If not, only minimal effort is needed. I choose the latest release version qemu-5.2.0 of early 2021 since I find it to be easier than the earlier version for adding new target. I have actually done this with version qemu-5.1.0 and found that the latest version is easier to port.

The host PC is for building and running the QEMU to virtualize the h/w platform. For my case I use Linux x64 Slackware with kernel built to support virtualization. The PC BIOS should have virtualization enable as well.

QEMU

  • QEMU

    • version: qemu-5.2.0, which is the latest as of Jan 2021.

    • Emulation: ARM and AARCH64 on Linux x64 host.

  • SoC target plaform to be added:

    • ARM Cortex-R5 with 4MB on-chip RAM (OCR).

    • APB peripherals: UART, Timer, I2C

    • GICv2 Interrupt controller

    • Flash and others

Before you begin the work, you might as well test build the stock release as-is for the ARM emulation. If all the dependencies are met the package should be built successfully. To build as-is for ARM, extract the package then cd qemu-5.2.0 to configure and build,

./configure --target-list=arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --prefix=/opt/qemu-5.2.0/

This configure for ARMv7 and ARMv8 and install to /opt/qemu-5.2.0 when you do make install. The output of the built QEMU is in its build/ directory. You can as well run QEMU from this directory without having to install it to the desired location.

Check the machines supported by this build,

qemu-system-aarch64 -M help

Will list the ARM platforms supported, for examples,

Supported machines are:
akita                Sharp SL-C1000 (Akita) PDA (PXA270)
ast2500-evb          Aspeed AST2500 EVB (ARM1176)
ast2600-evb          Aspeed AST2600 EVB (Cortex A7)
borzoi               Sharp SL-C3100 (Borzoi) PDA (PXA270)
canon-a1100          Canon PowerShot A1100 IS
cheetah              Palm Tungsten|E aka. Cheetah PDA (OMAP310)
collie               Sharp SL-5500 (Collie) PDA (SA-1110)
connex               Gumstix Connex (PXA255)
..

Adding platform to QEMU

In QEMU directories structure, I only needed to work in two directories, hw/arm and default-configs/devices to add the new target platform and to modified the existing configuration incorporating new platform.

IR device

For this work, I am using a VS1838B IR receiver having three pins, power, ground, and output. I do not even have the datasheet of it. I hook it up and get the scope output and from what I see I know that it will work for what I want to do.

../../images/hardware/IR.BMP

IR signal from its output pin

This device is probably equivalent to TSOP4838 or TSOP1738 type of IR receiver, an integrated circuit with AGC and BP filter.

../../images/hardware/connecting-ir.jpg

This is how easy it is to connect. Two pins plug straight to header with the signal pin to UART4_RXD.

Tools, software and accessory

  • IR receiver

  • Wires

  • Any IR remote controller transmitter (from old VCR or old TV etc..)

  • v4l-utils tool, ir-keytable for testing.

  • BeagleBone Black

Implementation

pinmuxing

The IR driver will be running on the current kernel version of my BBLK board which is 4.1.13. The kernel source file is in am335x-evm-sdk-src-02.00.01.07 of TI. First thing I need to do is to redo pinmux for T17 (Table 4.1, page 29) 4. T17 is the ball pin of Am3358, not the BBLK P9's header pin. On P9 it is pin 11. This is the pin for UART4_RXD. For this pin to function as UART4_RXD, it needs to be muxed to mode 6 of operation. To pinmux, I need to edit the device tree source, am335x-boneblack.dts, and add to '&am33xx_pinmux' block to override their default settings. I do not need TX operation of this UART since it is only for RX operation.

&am33xx_pinmux {
         ..

         uart4_pins: pinmux_uart4_pins {
         pinctrl-single,pins = <
                 0x70 (PIN_INPUT_PULLUP | MUX_MODE6)     /* uart4_rxd.uart4_rxd*/
         >;
        };
 };

I also need to enable UART4 that was defined, but was marked disabled otherwise I will not be able to use it. I can override this setting by adding the following to the dts file,

&uart4 {
        pinctrl-names = "default";
        pinctrl-0 = <&uart4_pins>;
        compatible = "cir-uart";
        status = "okay";
};

So I add the block shown above just below '&mcasp0' block. This is to set 'uart4' to use the pinmux that I define and set its status to be okay (enable). I also set the platform driver compatibilty to 'cir-uart'. This is going to be my new IR driver. The platform driver will invoke my driver upon insmod. I am done with pinmuxing and enabling UART4 device as far as device tree is concerned.

I recompile the device tree and add to the zImage as an FDT. On reboot, the new MUX will take effect. The simple part is done, now come the hard part. Getting the UART4 to work the way I want to.

kernel driver

In reference to TI's AM335x 1, chapter 19, Universal Receiver/Transmitter, the clock for this UART is the external PER_CLKOUTM2 PRCM / 4 that feeds the UART's fclk. The UART has to be clocked for the driver to be able to access its registers and configure it for use.

The first thing this driver does is to register with the kernel platform framework having this platform_driver table,

       static struct platform_driver cir_platform_driver = {
               .driver = {
               .name           = "cir-uart",
               .of_match_table = cir_dt_ids,
               },
               .probe                  = cir_probe,
               .remove                 = cir_remove,
       };

The cir-uart matches to what I defined in the device tree earlier. The will enable the platform driver framework to call its cir_probe for device probing and initializing the device.

       static int cir_probe(struct platform_device * pdev)
       {
               ...
               struct resource * regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
               struct resource * irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
               ...
               /* use uart4_fck*/
               clk = clk_get(&pdev->dev,"dpll_per_m2_div4_ck"); //expect 48MHZ clock feed
               ..
               device_init_wakeup(&pdev->dev, true);
               pm_runtime_use_autosuspend(&pdev->dev);
               pm_runtime_set_autosuspend_delay(&pdev->dev,-1);
               pm_runtime_irq_safe(&pdev->dev);
               pm_runtime_enable(&pdev->dev);
               pm_runtime_get_sync(&pdev->dev);
               cirdev->clk = clk;
               clk_prepare_enable(clk);

The device probe get the device resource information from the platfrom framework. I look up the source code in arch/arm/mach-omap2 to find out the name of the clock that is the most likely be the one I should be using. This happens to be dll_per_m2_div4_ck clock. The driver will get this clock line, wakeup the device and ask pm_runtime.. to put it into use. The remaining part is the typical drill, memory mapped I/O device, request IRQ line etc..

Next the driver allocates RC device structure, fills in the RC device operations and its information and register for service with the RC framework driver. In the RC device structure, I have mostly empty functions defined since the device does not need special handling. It is only for the formality. I have it mapped to RC_MAP_RC6_MCE, but it can be changed at run-time. Work queue is also used for this driver for input event processing as the BH portion of interrupt handling.

       ir_props = rc_allocate_device();
       ..
       ir_props->driver_type = RC_DRIVER_IR_RAW;
       ir_props->allowed_protocols = RC_BIT_ALL;
       ir_props->priv = (void * )cirdev;
       ir_props->s_idle = cir_set_idle;
       ir_props->open = cir_open;
       ir_props->close = cir_close;
       ..
       ir_props->map_name = RC_MAP_RC6_MCE;
       ..
       INIT_WORK((struct work_struct * )&cirdev->bh,irevent_bh);
       INIT_LIST_HEAD((struct list_head * )&cirdev->head);
       err = rc_register_device(cirdev->irprops);

The UART is at the reset state and won't get initialized until the RC called its cir_open() operation. This will happen when /dev/input/eventx is opened by external application to make use of it.

Once the IR stream bits is received and decoded by the UART, the RX interrupt will be generated and will be serviced by cir_irq_handler interrupt handler. The handler put the received byte into the buffer and schedule the BH workqueue. The BH handler then takes the byte off from the buffer and pushes it upstream to the IR core driver.

       ..
       if ( (pulse == 0) || (pulse == 0xff) ) {
               ev.pulse = pulse ? 1 : 0 ;
               ev.duration = (protocol == RC_TYPE_SONY12 ) ? 600000 * 8: 562500 * 8 ;
               ir_raw_event_store_with_filter(cirdev->irprops, &ev);
               continue;
       }
       for (j = 0; j < 8; j++) {
               ev.pulse = pulse & 1;
               pulse = pulse >> 1;
               if (protocol == RC_TYPE_SONY12) {
                       ev.duration = 600000 ; //duration * 1000; //in ns
               }
               else
                       ev.duration = 562500; //duration * 1000; //in ns
                       ir_raw_event_store_with_filter(cirdev->irprops, &ev);
               }
                ..
       ir_raw_event_handle(cirdev->irprops);

For simplicity, I omit the sleep/wakeup support in the driver, instead I add proc file interface for debugging purpose. The proc file is a simple registers dump using sequential file mechanism.

       struct file_operations proc_regs_fops = {
               .open =  proc_seq_open,
               .read = seq_read,
               .llseek = seq_lseek,
               .write = proc_reg_write,
               .owner = THIS_MODULE,
       };
       ..
       if (!(cir_procdir_entry = proc_mkdir("cir",NULL))) {
               goto exit_free_data;
       }
       cirdev->proc_entry = cir_procdir_entry;
       if (!(entry = proc_create_data("regs",S_IFREG | S_IRUGO | S_IWUSR,
                                                                  cirdev->proc_entry,
                                                                  &proc_regs_fops,
                                                                  NULL)) ) {

Once I compile the driver, cir.ko, I can load and test it. As part of input event, the input event driver, evdev is also used.

# modprobe evdev
# insmod cir.ko

Testing

Having the debug code, upon driver loading I can see that the platform framework call it with,

# insmod ci# insmod cir.ko
[   40.972690] cir_probe: entering with regs start 0x481a8000, size 0x2000, irq 156
[   40.980266] cir_probe:uartclk 48000000, wakeirq 0, id   (null), sbase 0xfa1a8000, mapbase 481a8000
[   40.990945] cir_probe: fck rate 48000000

So far so good. My debugging messages indicate that I get the UART4 resource information correctly.To make sure I get what I think I really get is to add few extra debug code to actually read the AM3358 registers 1 just to verify the setting.

[   99.034228] get_uart_clock, CM_PER_L4LS_CLKCTRL=0x4502
[   99.039409] get_uart_clock, CM_PER_UART4_CLKCTRL=0x2
[   99.045336] get_uart_clock: MDR1 reg = 0x7, CFPS 0x69

The first two lines is the value of CM_PER_L4LS_CLKCTRL 1 and CM_PER_UART4_CLKCTRL respectively. This is the indication that clock line is activated correctly. The last line is to read two UART registers, MDR1 and CFPS. If clock line is not activated, reading the two UART registers would have resulted in kernel crash because the IO bus would be stuck and cause I/O fault to happen. It is the painful way to know something is wrong.

I can check IRQ45 of UART4 is registered with the kernel correctly.

# cat /proc/interrupts
           CPU0
 16:       3050      INTC  68 Level     gp_timer
 ...
156:          0      INTC  45 Level     cir
...

There is my interrupt handler and current count is at 0. While at it I can check my proc file for registers dump at the device's idle state.

# cat /proc/cir/regs
rhr                      0x44
acreg                    0x10
..

Having verify the information that I expect, I have more confident to do further test. The next step is to test with ir-keytable utility.

# ir-keytable
Found /sys/class/rc/rc0/ (/dev/input/event0) with:
        Driver (null), table rc-rc6-mce
        Supported protocols: unknown other lirc rc-5 jvc sony nec sanyo mce-kbd rc-6 sharp xmp
        Enabled protocols: lirc rc-6
        Name: CIR Infrared Remote Receiver
        bus: 0, vendor/product: 0000:0000, version: 0x0000
        Repeat delay = 500 ms, repeat period = 125 ms

Looks like the tool recognize the registered IR device. I will change to use SONY protocol instead of lirc rc-6 so I issue this command,

# ir-keytable -c -p SONY
Old keytable cleared
Protocols changed to sony
# ir-keytable
Found /sys/class/rc/rc0/ (/dev/input/event0) with:
        Driver (null), table rc-rc6-mce
        Supported protocols: unknown other lirc rc-5 jvc sony nec sanyo mce-kbd rc-6 sharp xmp
        Enabled protocols: sony
        Name: CIR Infrared Remote Receiver
        bus: 0, vendor/product: 0000:0000, version: 0x0000
        Repeat delay = 500 ms, repeat period = 125 ms

So far so good. Next is to run the test with the IR receiver and SONY based remote control I found in my junk box.

# ir-keytable -t
Testing events. Please, press CTRL-C to abort.
1214.743017: event type EV_MSC(0x04): scancode = 0x19000a
1214.743017: event type EV_SYN(0x00).
...
218.540761: event type EV_MSC(0x04): scancode = 0x19000b
1218.540761: event type EV_SYN(0x00).
1218.585536: event type EV_MSC(0x04): scancode = 0x19000b

Scan code of SONY remote control is detected and decoded. For power button it is 0x1900a and for menu button it is 0x19000b. With this information, I can create the keymaps for it. Luckily this remote control has only a small number of buttons so it is created quickly. I store this keymap file as sony in /etc/rc_keymaps.

# cat /etc/rc_keymaps/sony
0x19000a KEY_POWER
0x19000b KEY_MENU
0x19000c KEY_UP
0x19000d KEY_REWIND
0x19000e KEY_ENTER
0x19000f KEY_FORWARD
0x190010 KEY_DOWN

Next I load the keymap mapping for the next test.

# ir-keytable -c -p sony -w /etc/rc_keymaps/sony
Old keytable cleared
Wrote 7 keycode(s) to driver
Protocols changed to sony
# ir-keytable -t
Testing events. Please, press CTRL-C to abort.
1608.313105: event type EV_MSC(0x04): scancode = 0x19000a
1608.313105: event type EV_KEY(0x01) key_down: KEY_POWER(0x0001)
1608.313105: event type EV_SYN(0x00).
...
1613.269840: event type EV_MSC(0x04): scancode = 0x190010
1613.269840: event type EV_KEY(0x01) key_down: KEY_DOWN(0x0001)
1613.269840: event type EV_SYN(0x00).

Now that I can see that the key events are generated with respect to their scancodes. Changing protocol to match the type of remote control is also flexible, for example, I can change protocol to NEC, nec to use with an old TV remote control that uses NEC protocol,

# ir-keytable -c -p nec
Old keytable cleared
Protocols changed to nec
# ir-keytable -t
Testing events. Please, press CTRL-C to abort.
2280.369927: event type EV_MSC(0x04): scancode = 0x847904
2280.369927: event type EV_SYN(0x00).
2285.859057: event type EV_MSC(0x04): scancode = 0x84790a
2285.859057: event type EV_SYN(0x00).

Should I need to use this remote control, I would want to create the keymap for its scancodes; however, this remote control has too many buttons, I will not create keymap for it for the time being. The scancodes above are for play and stop buttons respectively. They would be mapped to KEY_PLAY and KEY_STOP of the map file.

Conclusion

While there are many choices to use the IR of this type. One would be the GPIO type of driver to handle the IR stream, perhaps bit-banging it. If there is a small piece of hardware that left unused, it is better to use it. Since it is already included in the cost of the product, I better find the use of it.

This driver is available in my github repository, https://github.com/souktha/ir. There is a lot of room to improve and it is not yet robust. If I load/unload multiple times, it would crash. Once I have enough time in my hand, I will fix it. Perhaps it is because I oversimplify it by ignoring certain aspect of pm_runtime.

Citations

1(1,2,3)

AM335x Sitara Processors Technical Reference Manual, Literature number: SPRUH73M, October 2011 - Revised January 2016, Texas Instruments.

2

BeagleBone Black System Reference Manual, Revision C.1, May 22, 2014, Gerald Coley, Robert P J Day (BBB_SRM.pdf)

3

BeagleBone Black Document Number 450-5500-001, Rev C, March 21, 2014 (BBB_SCH.pdf)

4

AM335x Sitara Processors, Rev I, Texas Instruments, SPRS7171 (am3358.pdf)