In earlier posts I already wrote about Vybrid, a Freescale ARM SoC consisting of one Cortex-A5 and one Cortex-M4 (microcontroller) CPU. Normally, one would run a Firmware on the Cortex-M4 and Linux on the Cortex-A5 (which is quite well supported by contemporary kernels). But since we have two CPU’s with architectures supported by Linux (ARMv7-A and ARMv7-M), so why not running two independent Linux kernels in one SoC?
Here is how I put this together…
Pushing Linux around
In Vybrid, all resources are shared, even memory. First step is to free some resources for the second OS. This is easily possible by appending mem=128M to the kernel command line. This frees up another 128M for the Cortex-M4, should be enough for now 🙂
Compiling a Kernel for the Cortex-M4
This turned out to be rather easy: Some Kconfig and Makefile altering and I could build a Kernel which was configured for no-MMU configuration and the ARMv7-M instruction set (-march=armv7-m -mthumb). I decided to use a “eXecute In Place” kernel image (xipImage) which is located at 0x8f000000. I also enabled Vybrid low-level debug support on UART2.
Linux as boot loader
By default, the Cortex-M4 is not the boot CPU on Vybrid. So the Cortex-A5 can boot the whole Linux, while the M4 is still in freeze mode. This allows us to use Linux as boot loader for the secondary M4 core. This comes in convinient, since we can use just scp to put new kernels on the Linux filessystem, which we later load to the M4.
Booting the M4 is not very hard, its just writing the entry address in a register and unfreeze it (enable the clocks). I hacked a small utility which does this from user space: m4boot. This utility loads the Kernel as well as the device tree file into a given location (this is currently hard coded to 0x8f000000UL and 0x8fff0000UL). Then, the utility sets the entry point (SRC_GPR2) and the device tree binary location as argument (SRC_GPR3) and starts the M4 core.
Low-level debug support
First we need to have some output to see whether something is running or not. An UART is usually the way to go. Linux on the A5 comes in handy again, by configuring the second UART on Linux fist, I can just put some characters in the data register:
linuxa5:~# stty -F /dev/ttyLP2 115200 linuxa5:~# cat /dev/ttyLP2 &
(we need to open the device because the driver enables clocks only when the device is open).
So I started to put some strb assembly all over the place and started to interpret these single character stutters of the kernel. I soon figured out that the Vybrid earlyprink driver only supports MMU configuration. The patch was easy:
#else /* !CONFIG_MMU */ .macro addruart, rx, tmp ldr \rx, =(VF_UART_PHYSICAL_BASE) @ physical .endm #endif /* CONFIG_MMU */
Finally, nice kernel output. But of course, I haven’t set a machine ID nor a pointer to a device tree binary:
Error: unrecognized/unsupported machine ID (r1 = 0x00000001). Available machine support: ID (hex) NAME ffffffff VF610 on Cortex-M4 (Device Tree Support) Please check your kernel config and/or bootloader.
Linux as boot loader (part 2)
Nowadays, ARM kernels require the register r2 to point to the device tree binary in RAM. It is not possible to set the r2 register of the M4 directly from the A5, hence we need a minimal boot loader running on the M4. For now, I just hacked that in head-nommu.S.
ldr r0, =SRC_BASE ldr r1, =0xffffffff @ Machine ID ldr r2, [r0, #SRC_GPR3 ] @ DT pointer from argument register
The Vybrid SoC has a interrupt router which, by default, only routes interrupts to the first core. Currently, there is no driver which supports this interrupt router. But we can configure the router using U-Boot’s memory write command (mw). To have a working system, I need the interrupts for the PIT timer, the UART2 and ESDHC:
mw.w 0x400018b8 0x3 && mw.w 0x400018fe 0x3 && mw.w 0x400018ce 0x3
Root file system
Finally, a booting kernel! But now, how to get a root file system? A initramfs would have been a solution, however, I came up with something easier: We already have SD-card support for Vybrid, why not just enable that driver on the M4? Running make menuconfig and being able to select new stuff without having alter those drivers is like being in a computer store where everything is for free!! 🙂
Now it was time to compile a root file system. Since we are on a no-MMU architecture we need to use the special flat binary format. Being to lazy building such a toolchain myself I found one on the net: arm-2010q1-189-arm-uclinuxeabi-i686-pc-linux-gnu.tar.bz2
I opted to use BusyBox as base system.
$ export ARCH=arm $ export CROSS_COMPILE=/path/to/arm-2010q1/bin/arm-uclinuxeabi- $ export SKIP_STRIP=y $ ./configure --build=i686-pc-linux-gnu --host=arm-uclinuxeabi --without-tests $ make menuconfig
-march=armv7-m -mthumb -Wl,-elf2flt=-s -Wl,-elf2flt=16384
And build it…
$ make $ make CONFIG_PREFIX=/path/to/sd-card install
That’s it! With that we have a working Linux on the M4. From here, it would be easily possible to enable other peripherals which are supported, for instance GPIO, I2C or even Ethernet. My changes are available through the GIT repositories mentioned below. But if accepted, I will try to push initial support to mainline too.
Thanks to Catalin Marinas, Jonathan Austin and Uwe Kleine-König who pushed support for ARMv7-M and Cortex-M3/M4 in mainline, without their ground-work this would not have been that easy.