La ODROID-U3 es de 2013. Quad-core Exynos4412 a 1,7GHz, 2GB de RAM y almacenamiento en eMMC. Hace más de una década que Hardkernel la discontinuó, pero sigue funcionando. El problema es que el kernel oficial que traía —un 3.8 parcheado— se quedó fósil. Si tienes una corriendo Ubuntu 22.04 con un kernel 4.16 compilado a mano hace años, tarde o temprano te planteas actualizarlo. Este servidor es uno de los que más uso, pero siempre estoy dudando entre dejarlo ya hasta que la placa falle o tener algo más nuevo.
Cuando descubrí la existencia de VelvetOS, pensé en actualizar todo, pero me daba pereza y además había dudas de que funcionase en la memoria interna eMMC que es mucho más rápida que usar una microSD. Así que hice un intento con un kernel 6.8 genérico de Ubuntu, compilado con ayuda de Claude Code, que no arrancó. Pantalla negra, sin SSH, sin nada. Lo que sigue es lo que aprendí en el proceso y la solución que finalmente funcionó.

El U-Boot es el punto de partida
Antes de pensar en el kernel hay que saber qué versión de U-Boot tienes instalada. El U-Boot original que venía con la ODROID-U3 era de 2010 y solo entiende el comando bootm, que requiere imágenes en formato uImage con cabecera mkimage y tiene un límite de tamaño que un kernel moderno supera sin despeinarse.
Si en algún momento actualizaste el U-Boot desde el foro de Hardkernel y esta web que aún sigue viva —como es mi caso—, o si instalaste una imagen de Velvet OS o similar, lo más probable es que tengas algo mucho más reciente. Puedes comprobarlo desde el sistema arrancado:
sudo strings /dev/mmcblk1boot0 | grep -iE "U-Boot 20[0-9]{2}"
En mi caso el resultado fue U-Boot 2020.10. Con eso ya puedes arrancar kernels modernos usando bootz y DTB separado. Si tienes algo anterior a 2015, necesitarás actualizar el U-Boot antes de continuar. Eso es otra historia que no cubro aquí, pero que se puede intentar con esta web: Odroid U3 Kernel Upgrade + Docker
El dispositivo eMMC puede aparecer como mmcblk0 o mmcblk1 según la placa. Comprueba cuál es el tuyo con lsblk antes de ejecutar el comando. En mi caso tengo memoria eMMC (mmcblk0), una microSD (mmcblk2) y un pendrive por USB (sda).

Por qué no funcionó el kernel genérico de Ubuntu
El kernel 6.8 genérico de Ubuntu es enorme y está compilado para todo el hardware ARM posible. Eso ya es un problema de tamaño. Pero el problema real son los patches. El Exynos4412 necesita al menos tres correcciones que no están en el árbol genérico:
Primero, un parche que arregla el USB en el Exynos4412 (fix-odroid-x2-usb.patch). Segundo, un parche para el HDMI (exynos-hdmi-infoframes-patch). Tercero, soporte para MAC fija en la interfaz de red, que en esta placa va por USB interno con el chip smsc95xx. Sin ese módulo en el initrd, el sistema arranca pero sin red. Sin red, no hay SSH y parece que no ha arrancado cuando en realidad sí lo ha hecho.
De hecho, el primer síntoma que ves es ese: pantalla negra —normal, el driver Mali no funciona en mainline sin más trabajo— y sin SSH. Lo segundo tiene arreglo fácil si sabes que es el módulo de red. Lo primero requiere un kernel compilado con los patches correctos.
La solución: el kernel precompilado de Velvet OS
hexdump0815 es el desarrollador detrás de Velvet OS, un framework para construir imágenes Debian arrancables en hardware ARM antiguo. Entre sus repositorios hay uno con kernels precompilados para el Exynos4412, probados en la ODROID-U3. El sufijo stb-exy+ identifica estos builds.
En febrero de 2025 publicó el 6.12.12-stb-exy+, probado en ODROID-U3+. Incluye los tres patches mencionados, un DTB correcto y un initrd propio de unos 18MB —frente a los 56MB del genérico de Ubuntu. Es exactamente lo que necesitamos.
Descarga el paquete en tu PC:
wget https://github.com/hexdump0815/linux-mainline-and-mali-generic-stable-kernel/releases/download/6.12.12-stb-exy%2B/6.12.12-stb-exy+.tar.gz
Súbelo a la ODROID por SCP si estás conectado por SSH desde un PC con Linux y extráelo luego en la odroid:
scp 6.12.12-stb-exy+.tar.gz usuario@ip-odroid:~/
ssh usuario@ip-odroid
tar xzf 6.12.12-stb-exy+.tar.gz
Instalar el kernel
Esto es como un transplante de cerebro. Copia el kernel y el DTB a la partición de arranque. La partición FAT está montada en /media/boot en las instalaciones habituales de la ODROID-U3:
sudo cp ~/boot/zImage-6.12.12-stb-exy+ /media/boot/zImage_6.12
sudo cp ~/boot/dtb-6.12.12-stb-exy+/exynos4412-odroidu3.dtb /media/boot/exynos4412-odroidu3_6.12.dtb
Instala los módulos en el sistema:
sudo cp -r ~/lib/modules/6.12.12-stb-exy+ /lib/modules/
Genera el uInitrd que necesita el U-Boot a partir del initrd incluido en el paquete:
sudo mkimage -A arm -O linux -T ramdisk -a 0x0 -e 0x0 \
-n "initrd-6.12" \
-d ~/boot/initrd.img-6.12.12-stb-exy+ \
/media/boot/uInitrd-6.12.12-stb-exy+

Configurar el arranque
Con U-Boot 2020.x el arranque se puede configurar con boot.ini y boot.scr. El U-Boot lee primero el boot.scr, así que hay que generar ambos. Primero el boot.ini:
sudo tee /media/boot/boot.ini.6.12 << 'EOF'
ODROID4412-UBOOT-CONFIG
setenv initrd_high "0xffffffff"
setenv fdt_high "0xffffffff"
setenv bootcmd "fatload mmc 0:1 0x40008000 zImage_6.12; fatload mmc 0:1 0x48000000 exynos4412-odroidu3_6.12.dtb; fatload mmc 0:1 0x49000000 uInitrd-6.12.12-stb-exy+; bootz 0x40008000 0x49000000 0x48000000"
setenv bootargs "console=tty1 console=ttySAC1,115200n8 root=UUID=TU-UUID-AQUI rootwait ro mem=2047M"
boot
EOF
Sustituye TU-UUID-AQUI por el UUID real de tu partición raíz, que puedes obtener con sudo blkid. Ahora el boot.scr:
cat << 'EOF' > /tmp/boot.txt.6.12
setenv initrd_high "0xffffffff"
setenv fdt_high "0xffffffff"
setenv bootcmd "fatload mmc 0:1 0x40008000 zImage_6.12; fatload mmc 0:1 0x48000000 exynos4412-odroidu3_6.12.dtb; fatload mmc 0:1 0x49000000 uInitrd-6.12.12-stb-exy+; bootz 0x40008000 0x49000000 0x48000000"
setenv bootargs "console=tty1 console=ttySAC1,115200n8 root=UUID=TU-UUID-AQUI rootwait ro mem=2047M"
boot
EOF
sudo mkimage -C none -A arm -T script -d /tmp/boot.txt.6.12 /media/boot/boot.scr.6.12
Antes de activar el nuevo arranque, guarda una copia de seguridad de los ficheros actuales:
sudo cp /media/boot/boot.ini /media/boot/boot.ini.anterior
sudo cp /media/boot/boot.scr /media/boot/boot.scr.anterior
Ahora activa el nuevo arranque y reinicia:
sudo cp /media/boot/boot.ini.6.12 /media/boot/boot.ini
sudo cp /media/boot/boot.scr.6.12 /media/boot/boot.scr
sudo reboot
Verificar que funciona
Espera dos o tres minutos y conéctate por SSH. Si todo ha ido bien:
uname -a
# Linux odroid 6.12.12-stb-exy+ #1 SMP PREEMPT ... armv7l armv7l armv7l GNU/Linux
Si no responde SSH, monta la eMMC en otro equipo y restaura los ficheros de arranque anteriores desde las copias de seguridad.

Un apunte sobre los kernels genéricos de apt
Si tienes Ubuntu instalado, apt intentará instalar y activar el kernel genérico en cada actualización. Ese kernel no arranca en la ODROID-U3. Conviene bloquearlo para evitar sorpresas:
sudo apt-mark hold linux-image-generic linux-headers-generic
El kernel de hexdump0815 hay que gestionarlo a mano: descargar la nueva versión cuando salga, repetir los pasos de instalación y actualizar los ficheros de arranque. No es cómodo, pero es lo que hay con hardware tan específico. Lo mismo hago el salto al nuevo kernel 7 por tener un número más redondo en algún momento.
El problema con flash-kernel
Si en algún momento instalaste un kernel genérico de Ubuntu —aunque fuera para probarlo— puede que también se instalara flash-kernel, un paquete que automatiza la copia del kernel y el DTB a la partición de arranque en hardware ARM. En la ODROID-U3 con el kernel de hexdump0815 ese paquete es completamente innecesario. Peor aún: puede entrar en un estado roto difícil de salir.
El error concreto que aparece es este:
Couldn't find DTB exynos4412-odroidu3.dtb on the following paths:
/etc/flash-kernel/dtbs /usr/lib/linux-image- /lib/firmware//device-tree/
La causa es que flash-kernel intenta buscar el DTB usando la versión del kernel genérico instalado por apt. Si ese kernel ya no está o la variable de versión queda vacía en el trigger, genera rutas inválidas. El resultado es un ciclo vicioso: falla al instalar y también al intentar purgar el kernel genérico.
La solución pasa por tres pasos. Primero, purgar el kernel genérico si aún está instalado:
sudo apt purge linux-image-6.8.0-107-generic linux-modules-6.8.0-107-generic
Si el error persiste y flash-kernel sigue roto, forzar su eliminación saltándose el script de postinst:
sudo dpkg --remove --force-remove-reinstreq flash-kernel
Por último, limpiar los paquetes huérfanos que arrastra:
sudo apt purge devio libiniparser1 mtd-utils
Eliminar flash-kernel no afecta al arranque. El kernel de hexdump0815 gestiona su propio boot mediante los ficheros de la partición FAT, sin necesitar ningún automatismo de apt.
Sobre las velocidades de acceso al almacenamiento en la Odroid-U3
Ya comenté que tenía conectadas a la Odroid-U3 la memoria eMMC (mmcblk0), una microSD (mmcblk2) y un pendrive por USB (sda). Para probar la velocidad he usado este comando:
sudo hdparm -t --direct /dev/el-disco-a-probar-según-su-nombre-visto-en-lsblk
Mide la velocidad de lectura del disco /dev/sda directamente desde el hardware, saltándose la caché del sistema operativo.
-t— mide el rendimiento de lectura secuencial en MB/s--direct— usa O_DIRECT para evitar el buffer de caché del kernel, midiendo la velocidad real del disco sin que el sistema «ayude» con datos ya cacheados
El resultado típico tiene esta pinta:
Timing O_DIRECT disk reads: 234 MB in 3.01 seconds = 77.72 MB/s
Es una medida rápida y orientativa, no un benchmark exhaustivo. En mi caso sirve para ver que la memoria eMMC es mucho más rápida que la microSD o el pendrive por USB 2.0.

La ODROID-U3 tiene más de diez años y sigue siendo una máquina capaz. Que alguien como hexdump0815 mantenga kernels actualizados y probados para ella es un trabajo que merece reconocimiento. Sin ese esfuerzo, esta placa llevaría años en el cajón.




