Introdução
O Yocto Project é uma plataforma de desenvolvimento de software de código aberto que oferece um ambiente altamente configurável para a criação de distribuições de Linux. Ele fornece um conjunto de ferramentas para criação de imagens Linux personalizadas para vários dispositivos.Embora possua uma curva de aprendizado notoriamente alta, o Yocto é, atualmente, o sistema padrão da indústria para geração de distribuições Linux para sistemas embarcados, devido à sua alta capacidade de customização e robustez.
Neste artigo, faremos uma build de uma Distribuição (Distro) Linux simples, em modo headless (sem componentes gráficos, apenas com o terminal padrão para interface com o usuário). Para tal, usaremos o Single-Board Computer (SBC) Rock 4 SE, da fabricante chinesa Radxa.
Hardware Necessário
A placa Rock 4 SE utiliza um System-on-Chip (SoC) Rockchip RK3399-T, que possui CPU com 2 núcleos ARM Cortex-A72 e mais 4 núcleos ARM-Cortex-A53, além de uma GPU Mali-T860MP4. A placa possui também várias interfaces de conectividade, como USB 3.0, Ethernet 1000 Base-T (Gigabit), Wifi 802.11 ac + BLE 5.0, saída HDMI, e suporta até 4GB de memória RAM do tipo LPDDR4.Também usaremos um cabo USB-C para alimentação da placa, um cabo USB tipo A Macho-Macho para gravação na EMMC, e um conversor USB-Serial para o interface com o terminal padrão do kernel.
Fundamentos do Yocto Project e seus principais componentes
-
OpenEmbedded: Um conjunto de ferramentas e scripts que usados
para a criação de distribuições Linux para sistemas embarcados. Este
sistema utiliza o conceito de receitas (recipes) e camadas (layers).
Estes componentes definem como compilar os diversos pacotes de
software que compõe um sistema Linux, incluindo onde obter o
código-fonte, como compilá-lo e quais dependências são
necessárias.
-
Bitbake: Executor de tasks usado pelo sistema de build da
OpenEmbedded. É responsável por interpretar os meta-dados das
receitas, gerar a lista de tarefas a serem executadas na build e
executá-las. Pode ser usado também para compilar components da
imagem individualmente.
-
Poky: Distro Linux padrão do sistema OpenEmbedded. É
usada como referência para gerar as outras distros e imagens do
sistema. O processo de adaptação de uma nova placa ao sistema
OpenEmbedded geralmente começa com a clonagem do repositório
poky.
-
Receitas: Conjuntos de instruções para compilação de pacotes
de software. Incluem instruções sobre onde baixar o código-fonte,
como compilá-lo, como gerar um pacote a partir do código compilado,
etc. São organizadas em arquivos com extensão *.bb.
-
Camadas (meta-layers): Um conjunto de receitas e
configurações que são usadas para personalizar as imagens de
sistema. Geralmente possuem o prefixo "meta" e são organizadas em
"camadas" que podem ser facilmente inseridas ou removidas da build,
através de um arquivo de configuração (bblayers.conf).
Layers são, essencialmente, são conjuntos de receitas, geralmente organizadas em coleções logicamente correlatas. Neste artigo, faremos uso das layers poky, meta-rockchip (que contém definições para o SoC RK3399-T utilizado na Rock 4 SE) e meta-openembedded.
Configuração do ambiente de build
╭─guilhermes@guilhermes ~╰─$ sudo apt install gawk wget git diffstat unzip texinfo gcc build-essential chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev python3-subunit mesa-common-dev zstd liblz4-tool file locales && sudo locale-gen en_US.UTF-8
╭─guilhermes@guilhermes ~╰─$ mkdir yocto && cd yocto
╭─guilhermes@guilhermes ~/yocto╰─$ git clone git://git.yoctoproject.org/poky -b kirkstone
╭─guilhermes@guilhermes ~/yocto╰─$ git clone git://git.openembedded.org/meta-openembedded.git -b kirkstone
╭─guilhermes@guilhermes ~/yocto╰─$ git clone https://github.com/radxa/meta-rockchip.git -b kirkstone-radxa
╭─guilhermes@guilhermes ~/yocto╰─$ source poky/oe-init-build-env### Shell environment set up for builds. ###You can now run 'bitbake <target>'Common targets are:core-image-minimalcore-image-full-cmdlinecore-image-satocore-image-westonmeta-toolchainmeta-ide-supportYou can also run generated qemu images with a command like 'runqemu qemux86'Other commonly useful commands are:- 'devtool' and 'recipetool' handle common recipe tasks- 'bitbake-layers' handles common layer tasks- 'oe-pkgdata-util' handles common target package tasks
Adaptações para gerar uma build sem componentes gráficos
Por exemplo, observe este snippet do arquivo rockchip-rk3399-rock-4se.conf, que define o MACHINE da Rock 4 SE:
Build da imagem
-
update.img: Arquivo crucial para atualização da imagem.
Geralmente, contém uma imagem completa que pode ser gravada na
placa-alvo, contendo kernel, módulos, e rootfs. Em sistemas que
realizam deploys e atualizações Over-the-Air com SoC's da Rockchip,
este arquivo é de suma importância, já que é o arquivo que geralmente
é enviado para regravar as placas com novas versões de software.
-
loader.bin: Arquivo do bootloader inicial, também conhecido
como preloader para SoC's da Rockchip. Antes do bootloader principal
(U-boot, neste caso) assumir o controle, este programa roda
diretamente após o código de inicialização de fábrica da ROM do SoC, e
é responsável por inicializar a memória principal e carregar os demais
componentes da cadeia de boot. Este arquivo não precisa ser regravado
todas as vezes - geralmente, apenas se for necessária a recuperação de
erros fatais na placa.
-
boot.img: Contém a imagem do kernel, necessária para o boot de
um sistema Linux.
-
rootfs.img (e demais arquivos com rootfs no nome): Contém o
sistema de arquivos raíz (rootfs) da placa. Esta é a partição primária
que contém o sistema operacional, suas aplicações e seus serviços.
-
módulos (modules--*.tgz): Arquivo comprimido de módulos do
kernel. São drivers que podem ser carregados / descarregados
dinamicamente pelo kernel.
-
u-boot.img e u-boot.bin: Imagens do bootloader principal
(U-boot).
- rk3399-rock-4se.dtb: Arquivo do tipo Device Tree Blob (dtb), que contém a device tree compilada a partir dos arquivos *.dts e *.dtsi na pasta do kernel. Se a placa-alvo for customizada, muito provavelmente serão arquivos distintos, com o hardware mapeado para tal.
Terminal serial para logs
Gravação na EMMC
╭─guilhermes@guilhermes ~/yocto╰─$ sudo apt-get install libudev-dev libusb-1.0-0-dev
╭─guilhermes@guilhermes ~/yocto╰─$ git clone https://github.com/rockchip-linux/rkbin.gitcd rkbin/tools/sudo chmod a+x upgrade_tool
╭─guilhermes@guilhermes ~/yocto╰─$ lsusbBus 002 Device 003: ID 0bda:0411 Realtek Semiconductor Corp. HubBus 002 Device 002: ID 0bda:0411 Realtek Semiconductor Corp. HubBus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hubBus 001 Device 002: ID 0b05:1939 ASUSTek Computer, Inc. AURA LED ControllerBus 001 Device 005: ID 046d:c083 Logitech, Inc. G403 Prodigy Gaming MouseBus 001 Device 007: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
╭─guilhermes@guilhermes ~/yocto╰─$ cd rkbin/tools/╭─guilhermes@guilhermes ~/yocto/rkbin/tools ‹master●›╰─$ sudo ./upgrade_toolProgram Data in /root/.config/upgrade_toolList of rockusb connectedDevNo=1 Vid=0x2207,Pid=0x330c,LocationID=1112 Mode=MaskromFound 1 rockusb,Select input DevNo,Rescan press <R>,Quit press <Q>:
╭─guilhermes@guilhermes ~/yocto/rkbin/tools ‹master●›╰─$ cd ~/yocto/rkbin/tools╭─guilhermes@guilhermes ~/yocto/rkbin/tools ‹master●›╰─$ sudo ./upgrade_tool db ../../build/tmp/deploy/images/rockchip-rk3399-rock-4se/loader.binProgram Data in /root/.config/upgrade_toolDownload boot ok.
Após gravar loader.bin com sucesso, você poderá ver o log de boot no terminal serial da placa:
Boot1 Release Time: Jun 2 2020 15:02:17, version: 1.26
CPUId = 0x0
SdmmcInit=2 0
BootCapSize=100000
UserCapSize=29820MB
FwPartOffset=2000 , 100000
UsbBoot ...72661
powerOn 84942
╭─guilhermes@guilhermes ~/yocto╰─$ sudo ./upgrade_tool uf ../../build/tmp/deploy/images/rockchip-rk3399-rock-4se/core-image-minimal-rockchip-rk3399-rock-4se.update.img
Teste de boot: A placa irá reiniciar após a gravação e iniciar o boot do sistema. Após subir o bootloader e inicializar o kernel, a tela de login deve estar acessível pelo terminal serial:
udhcpc: broadcasting discoverudhcpc: broadcasting discover[ 2943.951641] rk_gmac-dwmac fe300000.ethernet eth0: Link is Up - 1Gbps/Full - flow control rx/txudhcpc: broadcasting discoverudhcpc: broadcasting select for 192.168.0.103, server 192.168.0.1udhcpc: lease of 192.168.0.103 obtained from 192.168.0.1, lease time 7200/etc/udhcpc.d/50default: Adding DNS 8.8.8.8/etc/udhcpc.d/50default: Adding DNS 4.4.4.4done.Starting syslogd/klogd: donePoky (Yocto Project Reference Distro) 4.0.12 rockchip-rk3399-rock-4se ttyFIQ0rockchip-rk3399-rock-4se login:
root@rockchip-rk3399-rock-4se:~# uname -aLinux rockchip-rk3399-rock-4se 5.10.160-rockchip-standard #1 SMP Fri Aug 4 02:10:04 UTC 2023 aarch64 GNU/Linux
Obs.: Apesar de termos removido os componentes do servidor gráfico, a device tree permanece inalterada - logo, espera-se que os nós responsáveis pelo controle do módulo HDMI permaneçam funcionais, como demonstrado aqui. Se for necessário remover esta interface, o mais correto é desativar / remover estes nós das device trees que o kernel usa para esta placa.
Re-gravando e apagando a EMMC
PartType: EFINo misc partitionboot mode: NoneCLK: (uboot. arml: enter 400000 KHz, init 816000 KHz, kernel 0N/A)CLK: (uboot. armb: enter 24000 KHz, init 816000 KHz, kernel 0N/A)aplll 816000 KHzapllb 816000 KHzdpll 666000 KHzcpll 24000 KHzgpll 800000 KHznpll 600000 KHzvpll 24000 KHzaclk_perihp 133333 KHzhclk_perihp 66666 KHzpclk_perihp 33333 KHzaclk_perilp0 266666 KHzhclk_perilp0 88888 KHzpclk_perilp0 44444 KHzhclk_perilp1 100000 KHzpclk_perilp1 50000 KHzNet: eth0: ethernet@fe300000Hit key to stop autoboot('CTRL+C'): 0
=> download
RKUSB: LUN 0, dev 0, hwpart 0, sector 0x0, count 0x3a3e000
Para facilitar este processo, pode-se também soldar um push-button em SW1, mostrado na foto abaixo. Deste modo, apenas segure SW1 e, sem soltá-lo, resete a placa com o push-button abaixo do conector USB-C da alimentação.
╭─guilhermes@guilhermes ~/yocto/rkbin/tools ‹master●›╰─$ sudo ./upgrade_tool ef ../../build/tmp/deploy/images/rockchip-rk3399-rock-
Inserindo pacotes à imagem
Neste exemplo, adicionaremos o pacote util-linux à imagem para utilizar o programa lsblk. Ao tentar executá-lo no terminal da placa-alvo, observamos que não está inserido na imagem:
root@rockchip-rk3399-rock-4se:~# lsblk-sh: lsblk: not foundroot@rockchip-rk3399-rock-4se:~#
Adicione este pacote à local.conf:
```Python
DISTRO_FEATURES:remove = "x11 wayland directfb pulseaudio"
IMAGE_FEATURES:remove = "x11"
IMAGE_INSTALL:append = " util-linux"
```
e recompile a imagem com o comando bitbake core-image-minimal. Observe que, como a build é incremental, o tempo de recompilação é consideravelmente menor, uma vez que há apenas a necessidade de realizar o fetch / compilação de util-linux e re-gerar o rootfs (com a task core-image-minimal-1.0-r0 do_rootfs).
╭─guilhermes@guilhermes ~/yocto/build╰─$ bitbake core-image-minimalLoading cache: 100% |########################################################################################################################| Time: 0:00:00Loaded 2820 entries from dependency cache.NOTE: Resolving any missing task queue dependenciesBuild Configuration:BB_VERSION = "2.0.0"BUILD_SYS = "x86_64-linux"NATIVELSBSTRING = "universal"TARGET_SYS = "aarch64-poky-linux"MACHINE = "rockchip-rk3399-rock-4se"DISTRO = "poky"DISTRO_VERSION = "4.0.12"TUNE_FEATURES = "aarch64 armv8a crc"TARGET_FPU = ""meta-rockchip = "kirkstone-radxa:ef027f4455f705da7b4c24a63570ad9f0c512ff9"metameta-pokymeta-yocto-bsp = "kirkstone:5d822b31316663c838c5864ab68b28fb3ca41351"meta-oe = "kirkstone:a88cb922f91fda95e8a584cee3092083d5ad3e98"Initialising tasks: 100% |###################################################################################################################| Time: 0:00:01Sstate summary: Wanted 8 Local 6 Mirrors 0 Missed 2 Current 1125 (75% match, 99% complete)Removing 2 stale sstate objects for arch rockchip_rk3399_rock_4se: 100% |####################################################################| Time: 0:00:00NOTE: Executing TasksNOTE: Tasks Summary: Attempted 2984 tasks of which 2971 didn't need to be rerun and all succeeded.
root@rockchip-rk3399-rock-4se:~# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTSmmcblk0 179:0 0 29.1G 0 disk|-mmcblk0p1 179:1 0 4M 0 part|-mmcblk0p2 179:2 0 4M 0 part|-mmcblk0p3 179:3 0 29.9M 0 part`-mmcblk0p4 179:4 0 29.1G 0 part /mmcblk0boot0 179:32 0 4M 1 diskmmcblk0boot1 179:64 0 4M 1 diskzram0 254:0 0 0B 0 disk
Modificando o kernel com o devtool
Este comando cria uma layer denominada workspace, para a qual extrai o código-fonte das receitas. Isto permite que este código seja modificado em um ambiente isolado da pasta de build, e facilita o processo de criação de patches, mantendo o ambiente de build organizado.
O comando devtool modify linux-rockchip pode ser usado para extrair os arquivos-fonte do kernel na pasta workspace, como visto a seguir. A partir da pasta ~/yocto/build:
╭─guilhermes@guilhermes ~/yocto/build╰─$ devtool modify linux-rockchipNOTE: Starting bitbake server...NOTE: Reconnecting to bitbake server...NOTE: Retrying server connection (#1)...Loading cache: 100% |########################################################################################################################| Time: 0:00:00Loaded 2820 entries from dependency cache.NOTE: Resolving any missing task queue dependenciesBuild Configuration:BB_VERSION = "2.0.0"BUILD_SYS = "x86_64-linux"NATIVELSBSTRING = "universal"TARGET_SYS = "aarch64-poky-linux"MACHINE = "rockchip-rk3399-rock-4se"DISTRO = "poky"DISTRO_VERSION = "4.0.12"TUNE_FEATURES = "aarch64 armv8a crc"TARGET_FPU = ""meta-rockchip = "kirkstone-radxa:ef027f4455f705da7b4c24a63570ad9f0c512ff9"metameta-pokymeta-yocto-bsp = "kirkstone:5d822b31316663c838c5864ab68b28fb3ca41351"meta-oe = "kirkstone:a88cb922f91fda95e8a584cee3092083d5ad3e98"workspace = "<unknown>:<unknown>"Initialising tasks: 100% |###################################################################################################################| Time: 0:00:00Sstate summary: Wanted 1 Local 0 Mirrors 0 Missed 1 Current 105 (0% match, 99% complete)NOTE: Executing TasksNOTE: Tasks Summary: Attempted 475 tasks of which 464 didn't need to be rerun and all succeeded.INFO: Copying kernel config to srctreeINFO: Source tree extracted to /home/guilhermes/yocto/build/workspace/sources/linux-rockchipINFO: Recipe linux-rockchip now set up to build from /home/guilhermes/yocto/build/workspace/sources/linux-rockchip
Receitas extraídas com o devtool estão localizadas na pasta sources:
╭─guilhermes@guilhermes ~/yocto/build/workspace╰─$ cd sources╭─guilhermes@guilhermes ~/yocto/build/workspace/sources╰─$ lslinux-rockchip
Como exemplo, faremos uma pequena modificação na device tree da placa, e testaremos na placa-alvo. Observe o conteúdo do campo "Machine Model" no log do kernel da placa:
root@rockchip-rk3399-rock-4se:~# dmesg | grep model[ 2.853312] Machine model: Radxa ROCK 4SE[ 5.125233] rockchip-dmc dmc: could not find power_model node[ 5.357372] midgard ff9a0000.gpu: Using configured power model mali-simple-power-model, and fallback mali-simple-power-model[ 5.357563] I : [File] : drivers/gpu/arm/midgard/backend/gpu/mali_kbase_devfreq.c; [Line] : 417; [Func] : midgard_kbase_devfreq_init(); success initing power_model_simple.
De volta à pasta de build, recompile o kernel com o comando devtool build linux-rockchip. Recompile agora a imagem completa com o comando bitbake core-image-minimal. Grave novamente o arquivo re-gerado update.img na placa.
root@rockchip-rk3399-rock-4se:~# dmesg | grep model[ 2.870466] Machine model: Custom dts Yocto demo MP[ 5.161135] rockchip-dmc dmc: could not find power_model node[ 5.385885] midgard ff9a0000.gpu: Using configured power model mali-simple-power-model, and fallback mali-simple-power-model[ 5.386079] I : [File] : drivers/gpu/arm/midgard/backend/gpu/mali_kbase_devfreq.c; [Line] : 417; [Func] : midgard_kbase_devfreq_init(); success initing power_model_simple.