gimco's tech disorder


Jun 02 2020
12 minutos de lectura

U-Boot

Hace ya casi ¡10 años! que me compré un NAS para la casa. En su momento quería que tuviera dos bahías de discos duros poder configurar RAID y poder utilizar Linux, por lo que me decidí por un Iomega StorCenter ix2-200.

Despues de estar algunas semanas con el firmware original, decidí tomar el control completo del servidor e instalar Debian 6. Ha estado desempeñando sus múltiples labores a la perfección, pero debido a las obvias limitaciones y riesgos de seguridad por utilizar una versión tan antigua decidí actualizar el sistema. Comencé el proceso de actualizar entre las distintas versiones de debian, realizando varias veces los pasos de modificar el nombre de la release (squeeze → wheezy → jessie → stretch → buster) e ir actualizando:

# Modificar /etc/apt/sources.list 
apt-get update
apt-get upgrade
apt-get dist-upgrade

El problema es que en algún punto el sistema se rompió y tras uno de los reinicios ya no era capaz de conectar por SSH al servidor, y por lo tanto perdí el acceso al dispositivo (y parece que no soy el único al que le pasan estas cosas).

Conexión TTL UART

En estos casos, la mayoría de los dispositivos y microcontroladores proporcionan un mecanismo para acceder a una especie de consola o terminal mediante una conexión a un puerto serie. Necesitaremos hacernos con un dispositivo USB to TTL como el siguiente:

Una vez desmontado el dispositivo buscaremos en la placa las indicaciones para conectar los cables a los pines de transmisión, recepción, voltaje y tierra. Hay que informarse si trabaja a 3 o 5 voltios (y conectar en nuestro USBTTL el pin correspondiente), y recordar invertir el orden de los cables de transmisión y recepción.

También deberemos saber las opciones de transmisión que debamos configurar. Una vez enchufado nuestro USBTTL, consultamos los mensajes del kernel con dmesg para saber que nuevo dispositivo en /dev se ha creado. Una vez conectados todos los pines nos podremos conectar a nuestro dispositivo, usando alguna aplicación como Serial, o símplemente con el siguiente comando:

screen /dev/tty.SLAB_USBtoUART 11520

Al encender el NAS comenzaremos a ver mensajes del proceso de inicio interno!

         __  __                      _ _
        |  \/  | __ _ _ ____   _____| | |
        | |\/| |/ _` | '__\ \ / / _ \ | |
        | |  | | (_| | |   \ V /  __/ | |
        |_|  |_|\__,_|_|    \_/ \___|_|_|
 _   _     ____              _
| | | |   | __ )  ___   ___ | |_
| | | |___|  _ \ / _ \ / _ \| __|
| |_| |___| |_) | (_) | (_) | |_
 \___/    |____/ \___/ \___/ \__|
 ** MARVELL BOARD: RD-88F6281A LE

U-Boot 1.1.4 (Sep  8 2009 - 09:31:54) Marvell version: 3.4.14

Mapower version: 2.0 (32MB) (2009/09/08)

U-Boot code: 00600000 -> 0067FFF0  BSS: -> 006CEE60

Soc: 88F6281 A0 (DDR2)
CPU running @ 1000Mhz L2 running @ 333Mhz
SysClock = 333Mhz , TClock = 200Mhz

DRAM CAS Latency = 5 tRP = 5 tRAS = 18 tRCD=6
DRAM CS[0] base 0x00000000   size 256MB
DRAM Total size 256MB  16bit width
Flash:  0 kB
Addresses 8M - 0M are saved for the U-Boot usage.
Mem malloc Initialization (8M - 7M): Done
NAND:32 MB

CPU : Marvell Feroceon (Rev 1)

Streaming disabled
Write allocate disabled

Module 0 is RGMII
Module 1 is TDM

USB 0: host mode
PEX 0: interface detected no Link.
Net:   egiga0, egiga1 [PRIME]
Fan lookup table initialized.

Current remote temperature: 47
Current fan speed: 0

Hit any key to stop autoboot:  0

Das U-Boot

Los mensajes que nos aparecen inicialmente son del bootloader utilizado por la placa de nuestro NAS. El bootloader es un mini programa ofrece algunas utilidades que nos permiten configurar y realizar algunas acciones para poder iniciar un sistema mas complejo, en nuestro caso, u-boot se encarga de cargar en la memoria el kernel de linux y proceder a su arranque.

Si pulsamos intro durante el arranque, podremos interactuar con la líneas de comandos de u-boot. Se ejecutamos help podremos ver todos los comandos que tenemos disponibles:

Marvell>> help
?       - alias for 'help'
base    - print or set address offset
boot    - boot default, i.e., run 'bootcmd'
bootd   - boot default, i.e., run 'bootcmd'
bootext2    dev:boot_part1,boot_part2 addr boot_image linux_dev_name 
bootm   - boot application image from memory
bootp   - boot image via network using BootP/TFTP protocol
bubt    - Burn an image on the Boot Nand Flash.
chpart  - change active partition
cmp     - memory compare
cmpm    - Compare Memory
cp      - memory copy
cpumap - Display CPU memory mapping settings.
crc32   - checksum calculation
date    - get/set/reset date & time
dclk    - Display the MV device CLKs.
dhcp    - invoke DHCP client to obtain IP/boot params
diskboot- boot from IDE device
echo    - echo args to console
eeprom  - EEPROM sub-system
erase   - erase FLASH memory
ext2load- load binary file from a Ext2 filesystem
ext2ls  - list files in a directory (default /)
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls   - list files in a directory (default /)
fi      - Find value in the memory.
flinfo  - print FLASH memory information
fsinfo  - print information about filesystems
fsload  - load binary file from a filesystem image
g       - start application at cached address 'addr'(default addr 0x40000)
go      - start application at address 'addr'
help    - print online help
icrc32  - checksum calculation
ide     - IDE sub-system
iloop   - infinite loop on address range
imd     - i2c memory display
imm[.b, .s, .w, .l]     - i2c memory modify (auto-incrementing)
imw     - memory write (fill)
inm     - memory modify (constant address)
iprobe  - probe to discover valid I2C chip addresses
ir      - reading and changing MV internal register values.
loop    - infinite loop on address range
ls      - list files in a directory (default /)
map     - Diasplay address decode windows
md      - memory display
me      - PCI master enable
mm      - memory modify (auto-incrementing)
mp      - map PCI BAR
mtdparts- define flash/nand partitions
mtest   - simple RAM test
mw      - memory write (fill)
nand                   - NAND sub-system
nboot   - boot from NAND device
nbubt   - Burn a boot loader image on the Boot Nand Flash.
nm      - memory modify (constant address)
pci     - list and access PCI Configuration Space
phyRead - Read PCI-E Phy register
pciePhyWrite    - Write PCI-E Phy register
phyRead - Read Phy register
phyWrite        - Write Phy register
ping    - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
protect - enable or disable FLASH write protection
rarpboot- boot image via network using RARP/TFTP protocol
rcvr    - Satrt recovery process (Distress Beacon with TFTP server)
reset   - Perform RESET of the CPU
resetenv        - Return all environment variable to default.
run     - run commands in an environment variable
saveenv - save environment variables to persistent storage
se      - PCI Slave enable
setenv  - set environment variables
sflash  - read, write or erase the external SPI Flash.
sg      - scanning the PHYs status
sp      - Scan PCI bus.
tftpboot- boot image via network using TFTP protocol
usb     - USB sub-system
usbboot - boot from USB device
version - print monitor version
Marvell>> 

Con el mando printenv podremos ver cuáles son las opciones configuradas en u-boot para realizar el inicio, y podremos ajustar la variables de entorno que precisemos (como por ejemplo agregar nuevos parámetros de inicio al kernel) y podremos guardar los cambios.

A continuación podemos ver la configuración actual de este dispositivo:

Marvell>> printenv
baudrate=115200
loads_echo=0
rootpath=/mnt/ARM_FS/
console=console=ttyS0,115200 mtdparts=nand_mtd:0xc0000@0(uboot)ro,0x1f00000@0x100000(root)
CASset=min
MALLOC_len=1
bootargs_root=root=/dev/nfs rw
bootargs_end=:::DB88FXX81:eth0:none
image_name=uImage
standalone=fsload 0x2000000 $(image_name);setenv bootargs $(console) root=/dev/mtdblock0 rw ip=$(ipaddr):$(serverip)$(bootargs_end) $(mvPhoneConfig); bootm 0x2000000;
ethmtu=1500
eth1mtu=1500
mvPhoneConfig=mv_phone_config=dev0:fxs,dev1:fxs
mvNetConfig=mv_net_config=(00:11:88:0f:62:81,0:1:2:3),mtu=1500
usb0Mode=host
yuk_ethaddr=00:00:00:EE:51:81
nandEcc=1bit
netretry=no
rcvrip=169.254.100.100
loadaddr=0x02000000
autoload=no
FanHysteresis=2
FanTempStart=58
ethprime=egiga1
eth1addr=00:D0:B8:16:51:14
uboot_start=0x0
uboot_size=0xc0000
env_start=0xa0000
env_size=0x20000
kernel_start=0x100000
kernel_size=0x300000
initrd_start=0x540000
flash_load=run make_boot_args load1 load2 boot
load1=nand read.e 0x2000000 $(kernel_start) $(kernel_size)
load2=nand read.e 0x4500000 $(initrd_start) $(initrd_size)
boot=bootm 0x2000000 0x4500000
make_boot_args=setenv bootargs console=ttyS0,115200 mtdparts=nand_mtd:;setenv bootargs $(bootargs)$(uboot_size)@$(uboot_start)(uboot),;setenv bootargs $(bootargs)$(env_size)@$(env_start)(env),;setenv bootargs $(bootargs)$(kernel_size)@$(kernel_start)(zImage),;setenv bootargs $(bootargs)$(initrd_size)@$(initrd_start)(initrd),;setenv bootargs $(bootargs)128m@0x0(flash);
iomega=123
ethaddr=AA:00:00:00:00:01
arcNumber=1682
filesize=69603a
fileaddr=5400000
netmask=255.255.255.0
ipaddr=192.168.1.11
serverip=192.168.1.10
bootcmd=run load1 load2 boot
bootargs=console=ttyS0,115200 mtdparts=orion_nand:0x300000@0x100000(uImage),0x1000000@0x540000(uInitrd) root=/dev/mapper/secure-root rootdelay=10
initrd_size=0x1000000
bootargs_console=console=ttyS0,115200 mtdparts=orion_nand:0x300000@0x100000(uImage),0x1000000@0x540000(uInitrd) root=/dev/mapper/secure-root rootdelay=10
stdin=serial
stdout=serial
stderr=serial
mainlineLinux=yes
enaMonExt=no
enaCpuStream=no
enaWrAllo=no
pexMode=RC
disL2Cache=no
setL2CacheWT=yes
disL2Prefetch=yes
enaICPref=yes
enaDCPref=yes
sata_dma_mode=yes
netbsd_en=no
vxworks_en=no
bootdelay=3
disaMvPnp=no
hddPowerCtrl=no
enaAutoRecovery=yes
ethact=egiga1

Environment size: 2264/16380 bytes
Marvell>> 

Enviar el nuevo kernel por tftp

De los mensajes de inicio, vemos que el dispositivo dispone de 256 megas de RAM y 32 megas de NAND. Al cargar el bootloader se hace una espera de unos segundos para comenzar a ejecutar los pasos que vemos en la variable bootcmd=run load1 load2 boot. El dispositivo leerá de la NAND y copiará el kernel y el initramfs a la memoria RAM para finalmente iniciar.

Si tenemos problemas porque hemos corrompido la NAND, tendremos que hacer llegar el nuevo kernel (e initramfs) que queremos grabar. Para esto podemos hacer uso de tftp. Los dos ficheros del kernel y del initramfs necesitan estar en un formato compatible con uboot. Si lo que queremos hacer es una instalación desde cero, en el caso de debian, podemos buscar la versión del instalador compatible con nuestro dispositivo.

En caso contrario, deberemos convertir los ficheros para poder ser procesados por uboot:

mkimage -A arm -O linux -T kernel -C none -a 0x00008000 -e 0x00008000 -n "2.6.32.5-kirkwood" -d vmlinuz uImage
mkimage -A arm -O linux -T ramdisk -C none -a 0x01100000 -e 0x01100000 -n "2.6.32.5-kirkwood" -d initrd.img uInitrd

Colocamos la imagen del kernel y el initramfs en el directorio de nuestro servidor tftp y procedemos a recibir en el dispositivo los datos y cargarlos en las direcciones de memoria correspondientes:

dhcp
set serverip=192.168.x.y
tftpboot 0x2000000 uImage
tftpboot 0x5400000 uInitrd
bootm 0x2000000 0x5400000

Una vez hayamos probado que inicia bien con el kernel y el initramfs, lo podremos hacer permanente. Para ello, despues de reiniciar la máquina y volver a cargar en memoria los ficheros mediante tftp, procederemos a borrar la NAND y copiar de memoria RAM a NAND:

nand erase 0x100000 0x300000
nand erase 0x540000 0x300000
nand write.e 0x2000000 0x100000 0x300000
nand write.e 0x5400000 0x540000 0x300000

De esta forma, la próxima vez que iniciemos se procederá al inicio normal.

Buscando el kernel

Lo siguiente que hice fue buscar el kernel disponible en debian para mi arquitectura para extraer el kernel y el initramfs. Para ello instalé u-boot-tools para poder realizar la conversión con mkimage, descargué directamente el paquete y extraje el contenido:

apt install u-boot-tools
wget "http://ftp.us.debian.org/debian/pool/main/l/linux/linux-image-5.6.0-1-marvell_5.6.7-1_armel.deb"
ar x linux-image-5.6.0-1-marvell_5.6.7-1_armel.deb
tar xvf data.tar.xz

Una vez convertido el kernel y el initramfs, lo envié por tftp a la memoria del dispositivo. Pero al intentar iniciar el kernel me encontré con la desagradable sorpresa de que no iniciaba, ya que las opciones de compilación del kernel de debian no era compatible con mi dispositivo:

Error: unrecognized/unsupported machine ID (r1 = 0x00000692).

Compilar un kernel ARM

La única opción en este punto es comprobar si la arquitectura seguía estando disponbile de forma oficial y compilar un kernel con las opciones necesarias. Para ello hay que informarse bien del hardware del que disponemos, al menos CPU y tarjetas de red, para poder seleccionar en la configuración las opciones correctas. Para que sirva de referencia para el futuro, esta es parte de la información devuelta por el comando hwinfo sobre la cpu y la tarjeta de red del dispositivo:

----- /proc/cpuinfo -----
  Processor	: Feroceon 88FR131 rev 1 (v5l)
  BogoMIPS	: 990.41
  Features	: swp half thumb fastmult edsp
  CPU implementer	: 0x56
  CPU architecture: 5TE
  CPU variant	: 0x2
  CPU part	: 0x131
  CPU revision	: 1

  Hardware	: Marvell RD-88F6281 Reference Board
  Revision	: 0000
  Serial		: 0000000000000000
----- /proc/cpuinfo end -----

05: None 00.0: 0200 Ethernet controller
  [Created at pci.912]
  Unique ID: XveX.iOgNgvnpcfD
  SysFS ID: /devices/platform/mv643xx_eth_port.0
  SysFS BusID: mv643xx_eth_port.0
  Hardware Class: network
  Model: "Marvell MV64360/64361/64362 System Controller"
  Vendor: pci 0x11ab "Marvell Technology Group Ltd."
  Device: pci 0x6460 "MV64360/64361/64362 System Controller"
  Device File: eth0
  HW Address: aa:00:00:00:00:01
  Link detected: no
  Module Alias: "platform:mv643xx_eth_port"
  Driver Info #0:
    Driver Status: mv643xx_eth is active
    Driver Activation Cmd: "modprobe mv643xx_eth"
  Config Status: cfg=new, avail=yes, need=no, active=unknown

El proyecto debian tiene alguna información útil para ayudarnos a compilar un kernel para otra arquitectura o lo que es lo mismo, hacer crossbuild. Aunque no fue suficiente la simple compilación, ya que al parecer a partir de cierta versión del kernel, se comenzó a usar Device Tree, que son una especie de metadatos binarios que informan al kernel de distintas características hardware de la máquina.

Afortunádamente, encontré un post de un desarrollador que se encontraba en la misma tesitura que yo. Rebuscando dentro de los ficheros Makefile del kernel, conseguí seguir la pista para averiguar las opciones de compilación necesarias:

dpkg --add-architecture armel
apt-get update
apt-get install crossbuild-essential-armel
apt install sbuild
apt source linux-image-5.6.0-1-marvell
cd linux-5.6.7/
export ARCH=armel
export (dpkg-architecture -a$ARCH)
make menuconfig
make mvebu_v5_defconfig
make zImage
make dtbs
cat arch/arm/boot/zImage arch/arm/boot/dts/kirkwood-iomega_ix2_200.dtb > zImage-kirkwood-iomega
./scripts/mkuboot.sh -A arm -O linux -C none -T kernel -a 0x00008000 -e 0x00008000 -n 'Linux-5.6.7' -d ./zImage-kirkwood-iomega uImage-kirkwood-iomega

Finalmente conseguí iniciar un kernel mas moderno en este antiguo NAS (aunque se me olvidó meterle mano al tema de los leds), y de paso aprendí algunas cosillas por el camino. 🎉🎉

Multientidad con Hibernate 3