Cómo habilitar virtualización KVM sobre ARM (Allwinner A20)

Hace algún tiempo, ARM Holdings presentó las extensiones de virtualización para su arquitectura de procesadores, las cuales están presentes en algunos modelos de la familia Cortex, como el Cortex-A7 y Cortex-A15. Aunque se trata de una tecnología reciente, los hipervisores KVM y Xen cuentan ya con soporte para dichas extensiones, y permiten levantar SS.OO. invitados de forma similar a como lo hacen en x86.

Si bien es cierto que los SoCs (System-on-Chip) y las placas de desarrollo no invitan a desplegar un número importante de máquinas virtuales, dado el escaso número de cores y RAM que proveen, este supone un primer paso de cara a la estabilización de estas tecnologías, lo que permitirá que ya esté madura para cuando se popularicen los encapsulados de ARM destinados a servidores. Adicionalmente, es una estrategia interesante para poder correr otros SS.OO. (como la familia *BSD) en hardware ARM, sin tener que lidiar con la naturaleza extremadamente heterogénea del mismo.

En esta guía vamos a ver cómo habilitar la virtualización KVM sobre la placa de desarrollo Olinuxino-A20-Micro.

 

Sobre la placa Olinuxino-A20-Micro

A20-OLinuXino-MICRO-0

Esta placa es uno de los últimos trabajos de Olimex, una empresa Europea especializada en circuitos electrónicos, comprometida con el Hardware Abierto. El corazón de esta placa es un SoC Allwinner A20 con procesador Cortex-A7 dual-core, con 1 GB de memoria RAM (lista completa de especificaciones).

A efectos prácticos, es una placa muy similar a la popular Cubieboard2, y la mayor parte de los pasos descritos en esta guía sirven para esta última, cambiando las referencias correspondientes.

 

Requisitos

  • Una placa base Olinuxino-A20-MICRO (seguramente funcione también con una Olinuxino-A20-LIME, pero no lo he probado).
  • Una tarjeta SD de, al menos, 4 GB.
  • (OPCIONAL pero MUY recomendable) Una cable USB a puerto serie, para poder ver el arranque de la placa. El que vende Olimex (USB-Serial-Cable-F) funciona perfectamente.
  • Un PC con una distribución reciente de GNU/Linux.

 

Paso 1: Instalar el compilador cruzado para ARM

Para poder compilar u-boot y el kernel, necesitamos un compilador cruzado para ARM con EABI y Hard Float. La mayor parte de distribuciones modernas tienen un sus repositorios los paquete necesarios. En nuestro caso, usando Ubuntu 14.04, el paquete principal se llama gcc-arm-linux-gnueabihf:

slp@ubuntu1404:~$ sudo apt-get install gcc-arm-linux-gnueabihf

 

Paso 2: Compilar el cargador de arranque (u-boot)

Cuando iniciamos nuestra placa, el cargador inicial, integrado en la memoria del SoC, busca un cargador secundario a partir del bloque 8192 de la tarjeta SD.

La imágenes para tarjetas SD de Olimex ya tienen dicho cargador, pero necesitamos reemplazarlo por una versión que detecte cuando la CPU está modo HYP, y evite cambiar a SVC32.

Creamos un directorio para almacenar todos el software que vamos a compilar, y bajamos el código de su repositorio de git:

slp@ubuntu1404:~$ mkdir kvm-arm
slp@ubuntu1404:~$ cd kvm-arm/
slp@ubuntu1404:~/kvm-arm$ git clone -b sunxi-next --depth=1 git://github.com/jwrdegoede/u-boot-sunxi.git
Clonar en «u-boot-sunxi»...
remote: Counting objects: 9155, done.
remote: Compressing objects: 100% (7854/7854), done.
remote: Total 9155 (delta 1708), reused 3512 (delta 1113)
Receiving objects: 100% (9155/9155), 13.73 MiB | 1.58 MiB/s, done.
Resolving deltas: 100% (1708/1708), done.
Checking connectivity... hecho.

Y compilamos u-boot, especificando la configuración de nuestra placa y el prefijo del compilador cruzado:

slp@ubuntu1404:~/kvm-arm/u-boot-sunxi$ make CROSS_COMPILE=arm-linux-gnueabihf- A20-OLinuXino_MICRO_config
Configuring for A20-OLinuXino_MICRO - Board: sun7i, Options: A20_OLINUXINO_M,CONS_INDEX=1,STATUSLED=226,SPL,SUNXI_EMAC
slp@ubuntu1404:~/kvm-arm/u-boot-sunxi$ make CROSS_COMPILE=arm-linux-gnueabihf-

Si tenemos más de un procesador en el equipo en el que estamos compilando, no puede interesar añadir la opción -j4 para paralelizar la compilación.

Si todo ha ido bien, deberíamos tener un fichero con nombre u-boot-sunxi-with-spl.bin, listo para ser grabado en la tarjeta SD.

 

Paso 3: Preparar el script de arranque

Para evitar tener que introducir los comandos manualmente, u-boot busca el fichero boot.scr en varios dispositivos de bloques, y lo carga en caso de encontrarlo. Dicho fichero contiene la lista de comandos a ejecutar, en formato binario. A continuación, vamos a crear un nuevo script de arranque.

En el directorio base de u-boot-sunxi, abrimos un editor de texto y creamos el fichero boot.cmd, en el que pegamos el siguiente contenido:

setenv kernel_addr_r 0x46000000 # 8M
setenv fdt_addr 0x49000000 # 2M
setenv fdt_high 0xffffffff # Load fdt in place instead of relocating

fatload mmc 0 ${kernel_addr_r} /uImage
setenv bootargs "console=ttyS0,115200 ro root=/dev/mmcblk0p2 rootwait"

fatload mmc 0 ${fdt_addr} /sun7i-a20-olinuxino-micro.dtb

bootm ${kernel_addr_r} - ${fdt_addr}

Guardamos el fichero, y la compilamos con mkimage:

slp@ubuntu1404:~/kvm-arm/u-boot-sunxi$ ./tools/mkimage -C none -A arm -T script -d boot.cmd boot.scr
Image Name:
Created: Wed Jul 23 13:48:35 2014
Image Type: ARM Linux Script (uncompressed)
Data Size: 365 Bytes = 0.36 kB = 0.00 MB
Load Address: 00000000
Entry Point: 00000000
Contents:
Image 0: 357 Bytes = 0.35 kB = 0.00 MB

Ya tenemos listo el script de arranque binario en el fichero boot.scr.

 

Paso 4: Compilar el kernel

Como suele ser habitual en el mundo ARM, el fabricante del SoC (Allwinner, en este caso) tras liberar una versión inicial del kernel con soporte para el modelo en cuestión, se desentiende del mismo y no invierte esfuerzo alguno en mantenerlo o intentar introducir los cambios en la versión oficial de Linux (denominada mainline). Eso ocasiona que el soporte en el kernel se quede atascado en versiones antiguas, que carecen de características nuevas como, en el caso que nos interesa, soporte para KVM.

Por fortuna, los voluntarios de la comunidad linux-sunxi han estado trabajando en enviar los cambios para soportar esta familia de SoCs en el kernel oficial, y la versión 3.16 tienen soporte para gran parte del hardware y, además, integra KVM.

Así pues, descargamos y descomprimimos la última versión disponible del kernel, que en el momento de escribir esto era la 3.16-rc6 (en teoría, cualquier versión superior debería funcionar igualmente, aunque no puedo asegurarlo):

slp@ubuntu1404:~/kvm-arm$ wget https://www.kernel.org/pub/linux/kernel/v3.x/testing/linux-3.16-rc6.tar.xz
slp@ubuntu1404:~/kvm-arm$ tar xf linux-3.16-rc6.tar.xz

A continuación, generamos el fichero de configuración por defecto, y lanzamos el menú de configuración del kernel (si os falla este último paso, es muy posible que necesitéis instalar el paquete libncurses5-dev, o su equivalente en vuestra distribución):

slp@ubuntu1404:~/kvm-arm$ cd linux-3.16-rc6/
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm sunxi_defconfig
#
# configuration written to .config
#
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm menuconfig

En el menú de configuración, debemos activar las siguientes opciones:

  • General setup -> Control Group support
  • System Type -> Support for Large Physical Address Extension
  • Device Drivers -> Block devices -&gt Loopback device support
  • Virtualization
  • Virtualization -> Kernel-based Virtual Machine (KVM) support (NEW)

El resto, podemos dejarlo tal y como está. Al salir de menuconfig, recordad guardar la configuración.

Finalmente, lanzamos la compilación del kernel y los DTBs (ficheros de descripción del hardware):

slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ export PATH=$PATH:/home/slp/kvm-arm/u-boot-sunxi/tools
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- LOADADDR=0x40008000 uImage dtbs

Esto se tomará algún tiempo (menos de lo que suele costar compilar un kernel de x86, dado el menor número de componentes), y una vez finalizado, tendremos los ficheros que necesitamos, arch/arm/boot/zImage y arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dtb.

 

Paso 5: Preparar la tarjeta SD

Ahora que tenemos todas las piezas, necesitamos preparar la tarjeta SD para que arranque con el nuevo kernel. Si ya teníamos instalada la imagen oficial por defecto de Olimex, no hace falta que la reemplacemos. En caso contrario, deberemos descargar y grabar la última imagen disponible en la página de Olinuxino-A20-MICRO.

Una vez que tengamos la tarjeta SD con la imagen oficial, escribimos el nuevo u-boot al principio de la misma:

slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ cd ~/kvm-arm
slp@ubuntu1404:~/kvm-arm$ sudo dd if=u-boot-sunxi/u-boot-sunxi-with-spl.bin of=/dev/mmcblk0 bs=1k count=8

Seguidamente, montamos la primera partición de la tarjeta, y copiamos el script de arranque, el kernel, y el DTB:

slp@ubuntu1404:~/kvm-arm$ sudo mount /dev/mmcblk0p1 /mnt
slp@ubuntu1404:~/kvm-arm$ sudo cp u-boot-sunxi/boot.scr /mnt
slp@ubuntu1404:~/kvm-arm$ sudo cp linux-3.16-rc6/arch/arm/boot/uImage /mnt
slp@ubuntu1404:~/kvm-arm$ sudo cp linux-3.16-rc6/arch/arm/boot/dts/sun7i-a20-olinuxino-micro.dtb /mnt
slp@ubuntu1404:~/kvm-arm$ sudo umount /mnt

Con esto, ya deberíamos tener lista la tarjeta SD para el primer arranque.

 

Paso 6: Primer arranque e instalación de QEMU

Si estáis usando el cable USB a serie, al arrancar con la tarjeta preparada en el punto anterior deberíais ver los mensajes de los nuevos u-boot y kernel. En caso contrario, deberéis esperar uno o dos minutos, para luego intentar conectar por SSH.

Para poder lanzar nuestro primer guest, necesitamos una versión reciente de QEMU. La versión que viene con la imagen oficial, basada en Debian, es algo vieja, por lo que deberemos compilarla nosotros mismos.

Descargamos las dependencias de compilación para el paquete qemu actual:

slp@a20:~$ sudo apt-get build-dep qemu
slp@a20:~$ sudo apt-get install libpixman-1-dev

A continuación, creamos un directorio para fuentes, descargamos QEMU 2.0 y lo descomprimimos:

slp@a20:~$ mkdir srcs
slp@a20:~$ cd srcs
slp@a20:~/srcs$ wget http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2
--2014-07-24 06:28:00--  http://wiki.qemu-project.org/download/qemu-2.0.0.tar.bz2
Resolving wiki.qemu-project.org (wiki.qemu-project.org)... 140.211.15.109
Connecting to wiki.qemu-project.org (wiki.qemu-project.org)|140.211.15.109|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12839647 (12M) [application/x-bzip2]
Saving to: `qemu-2.0.0.tar.bz2'

100%[======================================>] 12,839,647  2.08M/s   in 8.2s    

2014-07-24 06:28:09 (1.49 MB/s) - `qemu-2.0.0.tar.bz2' saved [12839647/12839647]
slp@a20:~/srcs$ tar xf qemu-2.0.0.tar.bz2

Configuramos la compilación indicando que queremos KVM (normalmente, el script de configuración lo activa automáticamente, pero de esta forma nos aseguramos que si no puede hacerlo por algún motivo, nos avise) y que nos produzca un binario sólo con soporte para emulación de ARM:

slp@a20:~/srcs$ cd qemu-2.0.0/
slp@a20:~/srcs/qemu-2.0.0$ ./configure --enable-kvm --target-list=arm-softmmu

Finalmente, lanzamos la compilación e instalación (por defecto, pondrá los binarios en /usr/local/bin):

slp@a20:~/srcs/qemu-2.0.0$ make
slp@a20:~/srcs/qemu-2.0.0$ sudo make install

 

Paso 7: Compilando un kernel para el Guest

Cuando usamos QEMU con KVM, el hardware que se está simulando es una Versatile Express A15, una de las plataformas de referencia que provee la propia ARM Holdings. Por lo tanto, necesitaremos un kernel con soporte para dicha placa.

Volvemos al directorio de compilación que hemos empleado en el Paso 3, cargamos la configuración por defecto de Versatile Express A15 y lanzamos el menú de configuración:

slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_defconfig
slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

Activamos las siguientes opciones:

  • General setup -> Configure standard kernel features (expert users)
  • General setup -> open by fhandle syscalls
  • Enable the block layer -> Support for large (2TB+) block devices and files

Y generamos una kernel en formato zImage:

slp@ubuntu1404:~/kvm-arm/linux-3.16-rc6$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage

 

Paso 8: Lanzando nuestro primer Guest

Además del kernel, necesitaremos también preparar un sistema raíz con las distribución que queremos emplear. Como vamos a emular una CPU con arquitectura ARMv7, la opción más óptima es que descarguemos una distribución compilada para earmv7hf. En esta guía, vamos a utilizar una imagen mínima (JeOS) de OpenSuSE, pero serviría cualquier otra que sea de nuestro gusto.

Creamos un directorio para contener los ficheros necesarios para el Guest:

slp@a20:~/srcs/qemu-2.0.0$ mkdir -p ~/kvm-arm/opensuse
slp@a20:~/srcs/qemu-2.0.0$ cd ~/kvm-arm/opensuse
slp@a20:~/kvm-arm/opensuse$

Copiamos el kernel que hemos compilado en el Paso 4, esta vez en formato zImage, y el DTB correspondiente para la placa Versatile Express A15:

slp@a20:~/kvm-arm/opensuse$ scp slp@10.x.x.x:~/kvm-arm/linux-3.16-rc6/arch/arm/boot/zImage .
slp@10.x.x.x's password: 
zImage                                        100% 2558KB   2.5MB/s   00:00    
slp@a20:~/kvm-arm/opensuse$ scp slp@10.x.x.x:~/kvm-arm/linux-3.16-rc6/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb .
slp@10.x.x.x's password: 
vexpress-v2p-ca15-tc1.dtb                     100%   12KB  12.0KB/s   00:00    

Ahora creamos una imagen raw que contenga el userland de OpenSuSE:

slp@a20:~/kvm-arm/opensuse$ wget http://download.opensuse.org/ports/armv7hl/factory/images/openSUSE-Factory-ARM-JeOS.armv7-rootfs.armv7l-1.12.1-Build175.2.tbz
slp@a20:~/kvm-arm/opensuse$ qemu-img create -f raw opensuse-factory.img 1G
Formatting 'opensuse-factory.img', fmt=raw size=1073741824
slp@a20:~/kvm-arm/opensuse$ sudo losetup /dev/loop0 opensuse-factory.img
[sudo] password for slp:
slp@a20:~/kvm-arm/opensuse$ sudo mkfs.ext4 /dev/loop0
mke2fs 1.42.5 (29-Jul-2012)
Discarding device blocks: done                            
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
65536 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done

slp@a20:~/kvm-arm/opensuse$ sudo mount /dev/loop0 /mnt
slp@a20:~/kvm-arm/opensuse$ sudo tar xf openSUSE-Factory-ARM-JeOS.armv7-rootfs.armv7l-1.12.1-Build175.2.tbz -C /mnt
slp@a20:~/kvm-arm/opensuse$ sudo umount /mnt
slp@a20:~/kvm-arm/opensuse$ sudo losetup -d /dev/loop0

Finalmente, lanzamos nuestro primer Guest:

slp@a20:~/kvm-arm/opensuse$ sudo qemu-system-arm -enable-kvm -m 512m -M vexpress-a15 -cpu host -kernel zImage -dtb vexpress-v2p-ca15-tc1.dtb -append "root=/dev/vda console=ttyAMA0 rootwait" -drive if=none,file=opensuse-factory.img,id=factory -device virtio-blk-device,drive=factory -net nic -net user -monitor null -serial stdio -nographic
Booting Linux on physical CPU 0x0
Initializing cgroup subsys cpuset
Linux version 3.16.0-rc6 (slp@ubuntu1404) (gcc version 4.8.2 (Ubuntu/Linaro 4.8.2-16ubuntu4) ) #12 SMP Thu Jul 24 11:48:10 CEST 2014
CPU: ARMv7 Processor [410fc074] revision 4 (ARMv7), cr=10c53c7d
CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
Machine model: V2P-CA15
Memory policy: Data cache writealloc
PERCPU: Embedded 7 pages/cpu @9fbdf000 s7552 r8192 d12928 u32768
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 130048
Kernel command line: root=/dev/vda console=ttyAMA0 rootwait
PID hash table entries: 2048 (order: 1, 8192 bytes)
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes)
Memory: 513156K/524288K available (4604K kernel code, 188K rwdata, 1284K rodata, 235K init, 150K bss, 11132K reserved)
...
[  OK  ] Started OpenSSH Daemon.
[  OK  ] Reached target Multi-User System.
[  OK  ] Reached target Graphical Interface.

Welcome to openSUSE Factory "Bottle" - Kernel 3.16.0-rc6 (ttyAMA0).


linux login: 

Ya tenemos listo nuestro Guest para empezar a jugar con él. Las credenciales por defecto en esta imagen son “root/linux”:

linux login: root
Password: linux

Last login: Thu Jul 24 11:57:53 on ttyAMA0
Have a lot of fun...
linux:~ # uname -a
Linux linux 3.16.0-rc6 #12 SMP Thu Jul 24 11:48:10 CEST 2014 armv7l armv7l armv7l GNU/Linux
linux:~ # cat /proc/cpuinfo
processor	: 0
model name	: ARMv7 Processor rev 4 (v7l)
Features	: swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm 
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xc07
CPU revision	: 4

Hardware	: ARM-Versatile Express
Revision	: 0000
Serial		: 0000000000000000

 

Conclusiones

Aunque la virtualización sobre ARM es todavía una tecnología emergente, hemos podido ver en esta guía que el software ya está preparado para recibir la nueva generación de procesadores de esta arquitectura orientados a entornos de servidor.

¿Conseguirá hacerse un hueco en un mundo dominado por Intel/AMD?