Zephyr RTOS – Adicionando Bootloader e Update de Firmware

Neste artigo veremos os passos para adicionar o MCUBoot em um projeto do Zephyr RTOS e habilitar os serviços do MCUmgr para realizar o Update de Firmware.
Este post faz parte da série Zephyr RTOS + Nordic. Leia também os outros posts da série:

Se formos pensar no ciclo de maturidade de um projeto de IoT, a possibilidade de se realizar um upgrade de firmware em campo pode ser um requisito importante para muitos projetos e vital por questões de segurança e correção de eventuais bugs após lançamento do produto ou adicionar novas funcionalidades.

O elemento básico que permite o update de firmware em campo do seu projeto é um bootloader. O bootloader é o primeiro código a ser rodado em seu dispositivo e é responsável por carregar a imagem principal de sua aplicação.

Além disso, ele pode ter recursos como a possibilidade de se ter múltiplas imagens e permitindo a escolha de qual será a ativa.. Além disso, é fundamental o bootloader contar com uma camada de segurança que não permita o carregamento de um firmware não autorizado em seu dispositivo.

O MCUBoot

O Zephyr RTOS utiliza o MCUBoot como bootloader. Assim como o próprio Zephy, o MCUBoot é uma solução OpenSource e nasceu dentro do projeto do RTOS Apache Mynewt.

Aqui no Embarcados existe um artigo excelente explicando os detalhes desse bootloader (Primeiros passos com ESP32 utilizando MCUboot como bootloader), portanto não entrarei em tantos detalhes teóricos e sugiro a leitura deste para um maior aprofundamento.

Em termos práticos, para podermos realizar o Device Firmware Update (DFU), precisaremos ter um conjunto mínimo de partições em nosso dispositivo compostas por:

  • boot_partition: para o próprio MCUBoot
  • slot0_partitiion: para o primeiro slot de imagem
  • slot1_partition: para o segundo slot de imagem
  • scratch_partition: área de trabalho do bootloader

Se abrirmos o arquivo de devicetree de nossa placa (/zephyr/boards/arm/nrf52833dk_nrf52833/nrf52833dk_nrf52833.dts), observaremos justamente essa definição dentro do node da flash interna:

Caso você não utilize bootloader, todo o seu código residirá a partir desse endereço 0x000000000. Caso utilize o bootloader, nessa primeira partição residirá o MCUboot e na slot0_partition residirá o seu código principal. Essa definição é feita através dessa linha no início do dts, na seção “chosen”:

É claro que em algumas arquiteturas, você poderá inclusive ter um empilhamento de bootloaders. Se pegarmos por exemplo a placa Arduino BLE Sense, ela vem com um bootloader baseado em mbed posicionado 0x000000000 que permite a gravação da mesma através da interface serial CDC montada na USB.

Você poderia manter esse bootloader original, adicionar o MCUboot e sua aplicação, de maneira a não precisar de um programador externo. Ou então utilizar um programador J-Link e substituir totalmente o bootloader do Arduino pelo MCUboot permitindo futuros updates por serial ou Bluetooth.

Dedicarei um artigo futuro para as peculiaridades desta placa.

Aplicação sem Bootloader

Primeiramente, analisemos o exemplo de nosso artigo anterior (Primeiro Projeto BLE – Monitor Cardíaco – Embarcados), que originalmente é executado sem bootloader.

Ao final do build podemos recebemos a seguinte informação:

Neste report, podemos ver que o tamanho da FLASH informado (“Region Size”) é de 512 KB, que é o total do SoC nRF52833.

Se formos olhar os arquivos binários gerados na subpasta zephyr do diretório de build, você verá que existe apenas um arquivo .hex:

Este arquivo contém o Zephyr e nossa aplicação e é utilizado quando damos o comando:

Perceba também que na saída de terminal de nosso exemplo, nosso output inicia diretamente com o carregamento do Zephyr RTOS:

Adicionando o MCUBoot

O próximo passo é habilitarmos o MCUBoot em nossa aplicação.

Isso é feito adicionando a seguinte linha de comando ao nosso proj.conf:

Vamos aproveitar e configurar também o subsistema de log e eventuais mensagens de debug do MCUBoot:

Importante: O método acima é válido para microcontroladores ARM sem TrustZone, como o Cortex-M4F na família nRF52 da Nordic. Caso esteja utilizando um microcontrolador com TrustZone, como o Cortex-M33 do nRF5340 ou nRF9160, você necessitará de alguns passos adicionais devido a definições do que deve (ou não) rodar em ambiente seguro. Abordarei isso em um artigo futuro desta série.

Faremos agora o rebuild de nosso RTOS e aplicação. Como foram feitas alterações grandes devido ao bootloader (apesar de poucas linhas de configuração), sugiro utilizar a opção “pristine build”, que apagará todo o seu build e gerará tudo de novo do zero. Isso evitará dor de cabeça de algum eventual erro de mudança não propagada corretamente. 

Observe a opção -p ao final da linha de comando abaixo:

Observe que o primeiro bloco referencia uma partição de 48KB , que é onde reside o MCUBoot. Já o segundo referência um partição de 237056 B, que é o tamanho da partição slot0_partition definida em nosso dts.

É importante ressaltar que, uma vez que dividimos a nossa flash interna em múltiplas regiões e suportamos slots para múltiplas imagens, o tamanho máximo de nossa aplicação terá que ser inevitavelmente menor. Portanto, a necessidade ou não de atualização de firmware deve ser considerada no momento da decisão do footprint de memória do microcontrolador para seu projeto.

Analisando os arquivos gerados

Continuando, se olharmos no diretório zephyr de nosso build, veremos que existem agora uma série de arquivos .hex:

Aqui vale comentar que o zephyr.hex continua sendo o nosso Zephyr RTOS com nossa aplicação principal, porém agora ao invés de ter a base em 0x000000000, ele terá como base a definição do endereço da slot0_partition .

E também que foi criado o arquivo merged.hex, que contém tanto o MCUboot quanto nossa aplicação. Se você carregar o merged.hex no Programmer do nRF Connect, você verá justamente essas duas regiões:

Update de Firmware

No caso de existência do MCUboot, será o merged.hex o arquivo utilizado no comando west flash.

Criptografia da Imagem

Por último, se você subir um pouco no report de seu build, observará a seguinte mensagem:

Isto significa que você não especificou nenhum método de criptografia ou chave específica para assinar o seu firmware. O west utilizou então a sua chave default para assinar e gerar os arquivos app_signed.hex (e sua versão binária app_signed.bin).

São justamente esses arquivos app_signed que serão utilizados para fazer update de firmware seguro com o MCUboot. Se futuras versões de imagem não forem geradas com a mesma chave que foi especificada ao se gerar o bootloader, estas imagens serão consideradas inválidas e não carregadas pelo bootloader.

Para trocar a autenticação para RSA e especificar uma chave específica para seu projeto, utilize as seguintes linhas abaixo no proj.conf. E lembre-se de tomar medidas de segurança com sua chave, como não incluí-la no commit em gits públicos.

Analisando as partições

Podemos também entrar no diretório do build e utilizar o comando ninja rom_report para observar como ficou nosso particionamento:

Verificando a execução do MCUboot

Agora vamos baixar nosso código para a placa e observar o que aparecerá no terminal abaixo:

Perceba que agora é iniciado o bootloader e depois ele passa a execução para a imagem que está presente no slot0 em 0xc000.

MCUmgr e SMP

De nada nos adianta utilizarmos o MCUboot sem termos um mecanismo para poder baixar e manusear múltiplas imagens em nosso dispositivo.

No Zephyr existe um subsistema chamado MCUmgr, que permite:

  • Manusear múltiplas imagens (IMG_MGMT)
  • Coletar estatísticas internas do seu dispositivo (STAT_MGMT)
  • Ter acesso ao filesystem para receber/enviar arquivos (FS_MGMT)
  • Enviar comandos através de um shell (SHELL_MGMT)
  • Recursos OS básicos como resetar remotamente seu dispositivo. (OS_MGMT)

Neste artigo focaremos  no IMG_MGMT e OS_MGMT. Para os demais serviços, sugiro olhar o SMP Server Sample do Zephyr.

Para todos esses serviços é utilizado um protocolo chamado SMP (Simple Management Protocol) e que pode ser acessado tanto por Bluetooth (fazendo o legítimo FOTA – Firmware Over The Air) ou por uma interface serial. Esta interface serial pode ser tanto uma interface UART padrão quanto uma porta USB rodando uma interface CDC.

Update de Firmware por Bluetooth

Modificando Configuração e Fontes

Primeiramente, adicionaremos algumas linhas ao nosso proj.conf para habilitarmos o MCUmgr e o SMP via Bluetooth:

Importante ressaltar que CONFIG_MCUMGR_SMP_BT_AUTHEN=n deve ser usado caso você esteja utilizando Bluetooth sem autenticação, como caso desse exemplo. Caso utilize alguma técnica de autenticação como chave de pareamento, remova esta opção. 

Além disso, teremos que fazer algumas alterações em nossos fontes para iniciar os serviços do SMP.

Para isto, precisaremos adicionar alguns headers dos serviços utilizados:

E também modificar nossa rotina de inicialização do Bluetooth no main(), adicionando as funções os_mgmt_register_group(), img_mgt_register_group() antes da inicialização do Bluetooth e a função smp_bt_register() após a mesma.

Executando a Atualização pelo Smartphone

Agora podemos fazer o rebuild, gravar essa nova versão em nossa placa e utilizar o aplicativo nRF Connect no smartphone para verificarmos a presença do novo serviço SMP.

Update de Firmware

Perceba que, como não alteramos nosso pacote de advertising adicionando o UUID do serviço de SMP, esse não aparece antes de conectarmos ao dispositivo. Mas, após solicitarmos a conexão, podemos confirmar a presença do mesmo.

Considero essa estratégia importante pois impossibilita de scanners detectarem a disponibilidade do serviço para eventuais intrusões, em especial quando se está utilizando algum mecanismo de autenticação de conexão.

Podemos então desconectar do dispositivo no nRF Connect e utilizar o Nordic Device Manager (App Android ou IphoneLib Android). Esse aplicativo permite utilizar todos os serviços do MCUMGR através do seu smartphone.

Após conectar pelo Device Manager, selecione a opção “Image” e depois “Advanced”. Você então poderá clicar em “Read” para realizar a leitura das imagens que estão disponíveis no seu dispositivo.

Update de Firmware

Observe que nesse momento aparece apenas a imagem do Slot 0, com a informação de Versão que especificamos em CONFIG_MCUBOOT_IMAGE_VERSION="0.0.1"

O próximo passo é termos uma nova versão de imagem para atualizarmos nosso dispositivo. Para isso, vamos simplesmente modificar a string de versão para CONFIG_MP BOOT_IMAGE VERSION=”0.1.0", e fazermos novamente o build.

Feita essa nova versão, vamos precisar do arquivo app_signed.bin que se encontra no subdiretório /zephyr do seu diretório de build. Vou renomear ele para app_v0.1.0.bin para identificação mais fácil.

Agora temos que acessar esse arquivo .bin a partir da aplicação no smartphone. A maneira que tenho utilizado e que considero bem prática é copiá-lo para algum serviço de nuvem como o Onedrive ou Google Drive e acessá-lo a partir de lá. 


Selecione Select File, aponte para o arquivo .bin e depois selecione Upload para enviar o mesmo para o dispositivo e em seguida selecione Read novamente para ver a nova imagem no Slot 1:   

Update de Firmware

Observe que existem alguns flags indicando qual é a imagem ativa e também, se ambas são bootáveis e se existe algum comando pendente a ser realizado.

A partir desse ponto temos algumas opções:

  • Confirmar (Confirm) que a nova versão. Isso copiará esta imagem para o Slot 0 e tornará a mesma ativa no próximo reset. Clique em Send Reset Command para resetar a placa nessa nova configuração.
  • Testar (Test) a nova versão. Essa opção marcará a nova imagem como ativa para o próximo reboot, mas sem copiá-la para a posição do Slot 0. Após o reboot, você pode testar funcionalidades básicas. Em caso de algum erro, você reseta o dispositivo e ele voltará para a imagem anterior. Caso deseje manter a imagem atual, você confirma  a mesma.

Conclusão

Neste artigo vimos os passos básicos para adicionar o MCUBoot em um projeto do Zephyr RTOS e habilitar os serviços do MCUmgr para realizar o DFU.

Além de realizar esse processo através dos aplicativos mostrados, você pode também utilizar a cli do mcumgr (apache/mynewt-mcumgr-cli) para através de linha de comando enviar os comandos SMP para comunicação do MCUmgr através de Bluetooth (somente Linux), USB ou Serial. Mais detalhes desses métodos se encontram em SMP Server Sample — Zephyr Project Documentation.

Outros artigos da série

<< Zephyr RTOS – Primeiro Projeto BLE: Monitor Cardíaco

João Dullius é Engenheiro de Aplicações na BP&M desde 2013, atuando atualmente com foco em em aplicações IoT, Machine Learning e RF. Engenheiro eletricista de formação, trabalhou em diversos projetos embarcados na área de telecomunicações e automação industrial e agrícola.

Notificações
Notificar
guest
1 Comentário
recentes
antigos mais votados
Inline Feedbacks
View all comments
Luiz Sampaio
luiz sampaio
13/09/2021 11:04

Olá,
Vi em outros sites que é possível usar python no Zephyr.
Vale a pena?

WEBINAR

Imagens de Ultrassom: Princípios e Aplicações

DATA: 26/10 ÀS 19:30 H