Boot pela rede com U-Boot e Raspberry Pi Deixe um comentário

Hoje conheceremos um pouco mais sobre um dos bootloaders mais utilizados em Linux Embarcado, o U-boot. Vamos aprender como iniciar o U-boot na Raspberry Pi e ver alguns recursos interessantes que ele oferece, como inicializar o kernel e o rootfs pela rede, recursos esses que podem ser muito importantes no início do desenvolvimento de um produto com Linux Embarcado, pois é comum precisarmos ficar alterando o sistema de arquivos da nossa placa.

E no caso de uma Raspberry Pi, o sistema de arquivos fica armazenado em um SD Card. Então imagina se, para toda alteração, tivéssemos que tirar o SD Card da Raspberry Pi, inserir no PC, fazer a alteração e inserir novamente na placa. Não seria nada produtivo todo esse processo, não é mesmo?

No entanto, podemos melhorar esse processo, por exemplo, enviando as alterações por SSH, utilizando os comandos scp ou rsync. Já não precisaríamos tirar o SD Card da Raspberry Pi. Mas se eu falar que dá pra melhorar ainda mais? É o que mostrarei nesse post, como seria um workflow mais próximo do ideal no desenvolvimento de um produto com Linux Embarcado.

Estrutura do Linux Embarcado

Em Linux Embarcado, temos três componentes essenciais que formam a sua estrutura, são eles:

  • Bootloader – responsável por inicializar um setup básico do hardware e carregar o kernel.
  • Kernel – responsável por gerenciar CPU, memória e I/O.
  • Rotfs – toda a estrutura de arquivos da distribuição, incluindo a biblioteca C padrão que faz interface com o kernel e as bibliotecas e aplicações do usuário.

E para algumas arquiteturas como ARM, que é o caso da Raspberry Pi, também tem um componente bastante importante que é o device tree, responsável por descrever o hardware ao kernel. Quem passa o device tree para o kernel é o bootloader.

Bootloader Raspberry Pi

Bootloader U-boot

Atualmente, um dos bootloaders mais utilizados (se não for o mais utilizado) em Linux Embarcado é o Das U-Boot. Utilizaremos ele para carregar o kernel e o rootfs pela rede. Porém, a Raspberry Pi, possui um mecanismo de boot um pouco diferente de outras placas.

Além disso, algumas informações a Broadcom (fabricante do processador da Raspberry Pi) não fornece, portanto não conseguimos trocar integralmente o bootloader da Raspberry pelo U-boot. Porém conseguimos fazer com que o bootloader da Raspberry Pi carregue o U-Boot, que por sua vez, carregará o kernel.

Abaixo um comparativo do boot process de uma Raspberry Pi 3 Model B+ com uma BeagleBone Black. Perceba que na Raspberry Pi, o boot é comandado pela GPU e não pela CPU.

U-boot na raspberry pi

Rootfs

Após inicializar o kernel, precisamos passar como parâmetro o caminho do rootfs. Quando utilizamos uma distribuição pronta, como o Raspbian, o rootfs fica localizado na segunda partição do SD Card. Mas, no nosso caso, queremos carregar o rootfs pela rede. Esse processo é conhecido como NFS (Network File System), que é um protocolo que permite compartilhar arquivos e diretórios entre hosts ligados na mesma rede.

Então a ideia é na Raspberry Pi possuir apenas os bootloaders (broadcom e u-boot) e trabalhar com o sistema de arquivos diretamente na nossa máquina. Portanto, kernel, device trees e rootfs devem estar localizados no host. E para carregar o kernel e os device trees pela rede, o U-boot utiliza o protocolo TFTP (Trivial File Transfer Protocol), que é utilizado para transferir arquivos entre hosts em uma rede.

Bom, explicado como as coisas funcionam e o que queremos, então, hora de colocar a mão na massa!

Importante: Os testes abaixo foram realizados em uma máquina com Linux (Linux Mint, no meu caso), caso você não tenha um linux instalado em seu PC, pode baixar uma VM com Ubuntu  para acompanhar.

Boot pela rede com U-Boot e Raspberry Pi

Primeiro vamos instalar algumas dependências:

sudo apt-get install git nfs-kernel-server tftpd-hpa make build-essential flex bison

Vamos baixar o código fonte do Das U-Boot para compilá-lo para a Raspberry Pi.

mkdir ~/raspberry; cd ~/raspberry
git clone https://github.com/u-boot/u-boot
cd ~/raspberry/u-boot
git checkout v2020.04

Para poder compilar o U-boot para a Raspberry Pi, precisaremos de uma toolchain para cross-compilar os binários do U-boot da nossa máquina (arquitetura x86) para o target (arquitetura ARM). Vamos baixar uma toolchain da Linaro, que é uma organização que trabalha no porte de ferramentas para a arquitetura ARM:

mkdir -p ~/raspberry/tools; cd ~/raspberry/tools
wget https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
tar xfv gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz

Agora já podemos compilar o U-Boot para gerar os binários que utilizaremos na Raspberry Pi:

Importante: Aqui estou compilando para a Raspberry Pi 3 B+, por isso estou digitando make_rpi_3_b_plus_defconfig, para verificar as opções disponíveis digite: ls ~/raspberry/u-boot/configs/rpi*

cd ~/raspberry/u-boot
make rpi_3_b_plus_defconfig
make CROSS_COMPILE=~/raspberry/tools/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-

Depois de gerado, vamos copiar o U-boot para o SD Card. Insira o SD Card no PC e verifique como foi reconhecido:

Importante: No meu SD Card já possui uma imagem do Raspbian Buster Lite.

lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sdb      8:32   1  3,7G  0 disk
├─sdb2   8:34   1  3,4G  0 part /media/fernando/disk
└─sdb1   8:33   1  256M  0 part /media/fernando/boot1
sda      8:0    0  1,8T  0 disk
├─sda2   8:2    0    1K  0 part
├─sda5   8:5    0 15,8G  0 part [SWAP]
└─sda1   8:1    0  1,8T  0 part /

No meu caso, eu tenho o HD da máquina em /dev/sda e o SD Card foi reconhecido como /dev/sdb e possui duas partições, sdb1 é a partição de boot, onde devemos copiar o U-Boot e sdb2 é a partição que contém o rootfs.

Desmonte o SD Card com o comando:

sudo umount -f /dev/sdb*

Vamos criar uma pasta para montar a partição de boot do SD Card no PC:

mkdir -p ~/raspberry/sdcard/boot

Montar a partição na pasta criada:

sudo mount /dev/sdb1 ~/raspberry/sdcard/boot

Agora podemos copiar o binário do U-Boot para o SD Card:

cp ~/raspberry/u-boot/u-boot.bin ~/raspberry/sdcard/boot/

Para o bootloader da Raspberry Pi carregar o U-Boot ao invés do kernel, precisamos modificar o arquivo config.txt:

cd ~/raspberry/sdcard/boot
echo -e "kernel=u-boot.bin" | sudo tee -a config.txt

Para carregar o kernel, precisamos gerar um script que o U-Boot possa interpretar.

Importante: na terceira linha da sequência de comandos, insira a imagem do kernel de acordo com a Raspberry  Pi que estiver usando:

  • RPi 1: kernel.img
  • RPi2 e RPi3 32 bits: kernel7.img
  • RPi4 32 bits: kernel7l.img
  • RPi3 64 bits e RPi4 64 bits: kernel8.img

Na quinta linha insira o nome do seu usuário:

cd ~/raspberry
cat << "EOF" > boot.cmd
setenv serverip 192.168.0.10 
setenv ipaddr 192.168.0.11 
setenv bootargs earlyprintk console=tty1 console=ttyAMA0 root=/dev/nfs rw nfsroot=192.168.0.10:/home/fernando/raspberry/rootfs rootfstype=nfs rootwait ip=192.168.0.11
tftpboot ${kernel_addr_r} kernel7.img
bootz ${kernel_addr_r} - ${fdt_addr}
EOF
~/raspberry/u-boot/tools/mkimage -C none -A arm -T script -d ~/raspberry/boot.cmd ~/raspberry/boot.scr

Copiando o script gerado para o SD Card:

sudo cp ~/raspberry/boot.scr ~/raspberry/sdcard/boot/

Vamos criar o diretório onde ficará o kernel e os device trees:

mkdir ~/raspberry/boot

Agora vamos copiar o kernel e os device trees para o nosso diretório de boot:

sudo cp ~/raspberry/sdcard/boot/*.dtb ~/raspberry/boot/

sudo cp ~/raspberry/sdcard/boot/kernel* ~/raspberry/boot/

Vamos criar o diretório onde ficará nosso rootfs e o diretório para montar a segunda partição do SD Card:

mkdir ~/raspberry/rootfs; mkdir ~/raspberry/sdcard/rootfs

Montando o rootfs do Raspbian em nosso PC:

sudo mount /dev/sdb2 ~/raspberry/sdcard/rootfs

E copiar para o nosso diretório de rootfs:

sudo cp -ar ~/raspberry/sdcard/rootfs/* ~/raspberry/rootfs/

Para a Raspberry Pi ter acesso ao diretório que está nosso rootfs, precisamos exportá-lo com o seguinte comando:

echo -e "/home/$USER/raspberry/rootfs/ *(rw,sync,no_root_squash,no_subtree_check)" | sudo tee -a /etc/exports

Editar o arquivo /etc/default/tftpd-hpa, inserir o caminho do diretório que contém o kernel e os device trees.

Importante: Deve ser colocado o nome de usuário que você estiver logado, no meu caso, o nome de usuário é fernando

TFTP_DIRECTORY="/home/fernando/raspberry/boot"

Reiniciando os servidores NFS e TFTP:

sudo service nfs-kernel-server restart
sudo service tftpd-hpa restart

Agora podemos desmontar o SD Card e inseri-lo novamente na Raspberry Pi.

sudo umount -f /dev/sdb*

Pronto! Agora ao ligar a Raspberry Pi, o U-Boot carregará o kernel e o rootfs que estão no PC e não os que estão no SD Card, assim toda alteração que você fizer na sua máquina será refletido na Raspberry Pi.

Importante: Para esse teste, o cabo de rede deve estar conectando o PC à Raspberry Pi, como a imagem abaixo ilustra. E também, o IP do PC deve ser fixado em 192.168.0.10.

Ligação entre Raspberry e PC

Se você monitorar a serial da Raspberry Pi, vai ver as mensagens do U-Boot, carregando e inicializando o kernel:

Mensagem de log do U-boot

Conclusão

Uma das vantagens de se fazer o boot pela rede, é que podemos usar todo o processamento da nossa máquina (que geralmente é maior que o do nosso target) no desenvolvimento de aplicações, evitando ter que escrever código e/ou compilar diretamente na Raspberry Pi.

Isso facilita o deploy das aplicações e também o teste de uma infinidade de ferramentas na Raspberry Pi sem ter que se preocupar com o tamanho do SD Card (se você tiver espaço no PC, claro). Também, as aplicações ficam centralizadas no seu PC, qualquer alteração que você fizer, é fácil rastrear. E ao finalizar o desenvolvimento do produto, é só copiar o rootfs para o SD Card.

Não é uma boa prática ter ferramentas de desenvolvimento na Raspberry Pi, pois um sistema embarcado é feito para um propósito específico. Portanto, o ideal é desenvolver o projeto integralmente no PC e fazer deploy para o target. Ferramentas de desenvolvimento no target só vão inflar o sistema de arquivos e poderão causar perda de performance.

Abaixo, uma lista de artigos aqui do blog que vocês podem praticar esse método:

E você já conhecia o U-boot? E o NFS? Como você desenvolve aplicações para a sua Raspberry, desenvolve diretamente na placa ou fazendo deploy do seu PC? Deixe aqui seus comentários e compartilhe suas experiências.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *