Primeiros passos com ESP32 utilizando MCUboot como bootloader

Este guia tem como objetivo instruir os primeiros passos com MCUboot no seu projeto baseado em ESP32 e apresentar a configuração de ambiente.

No mundo embarcado, atualização de firmware é um essencial e extenso tema que nos últimos anos vem ganhando mais e mais importância. Eficiência e segurança nunca foram mais discutidas, ao passo que soluções IoT crescem exponencialmente em números e complexidade, assim como a preocupação em fazê-las seguras, atualizáveis (em campo!) e ambas as condições através de formas eficientes.

Há pouco tempo atrás, o MCUboot surgiu como um bootloader de código aberto para sistemas pequenos e de baixo custo, que se propôs a simplificar e padronizar soluções para os problemas mencionados. Ele começou como um subprojeto do RTOS Apache Mynewt quando seus desenvolvedores decidiram separar os desenvolvimentos do bootloader e do OS. Mais tarde foi portado para o Zephyr RTOS e se tornou seu bootloader de uso padrão.

A Espressif Systems vem expandindo seu suporte a outros RTOSes, como Zephyr e NuttX como uma opção atrativa para uso em seus SoCs, e agora esse interessante bootloader alternativo está sendo desenvolvido. Recentemente um porte para os SoCs da Espressif foi adicionado ao projeto do MCUboot e o suporte básico para o amplamente usado ESP32 está disponível.

Este guia tem como objetivo instruir os primeiros passos com MCUboot no seu projeto baseado em ESP32 e apresentar a configuração de ambiente, configurações adicionais requeridas no lado da aplicação, o processo de build e flash do dispositivo. A placa ESP32 DevKitC foi utilizada para preparar este guia.

O que é MCUboot?

Como dito anteriormente, o MCUboot é um bootloader seguro e open source para microcontroladores de 32 bits. O projeto define uma infra-estrutura comum para o boot seguro e sua arquitetura contempla:

  • Layout de flash do sistema: a organização da memória flash é bem definida, utilizando o conceito de “slots” para conter a imagem principal e a imagem de atualização em diferentes regiões da flash, bem como uma área de scratch para auxiliar no processo de swap (permuta)  da atualização de firmware.
  • Atualização de firmware fácil e segura: é um processo simples em que, seja qual for o agente atualizador, é apenas necessário assinar a imagem corretamente e gravá-la no slot correto da flash. MCUboot se encarregará do swap entre a imagem atual e a mais nova, e também da segurança do processo (integridade, autenticidade e confidencialidade).
  • Segurança: MCUboot permite o boot e atualização seguras do firmware utilizando:
    • Integridade da imagem através da verificação de hash (SHA256).
    • Autenticidade da fonte através da validação de assinatura de chaves assimétricas, como RSA 2048 e 3072, ECDSA e ed25519.
    • Confidencialidade de dados da imagem (encriptação/desencriptação) durante o transporte e/ou durante o armazenamento da mesma em flash externa. O MCUboot possui suporte para encriptação/desencriptação on-the-fly durante o processo de atualização e utiliza algoritmos AES ou ECIES para isso.
  • Tolerância a falhas: o mecanismo de swap permite a recuperação/reversão de imagens caso um problema durante o processo ocorra, como um reset no meio de um swap durante uma atualização. Além disso, se por alguma razão uma nova imagem tenha sido atualizada e inicializada, mas não se sinalizou como “ok”, o MCUboot possui um mecanismo de rollback para reverter à imagem anterior, uma vez que ela é mantida após o swap.

O projeto se propõe a padronizar esses pontos de forma compreensível. 

MCUboot não está atrelado a um sistema operacional ou hardware único. Ele depende das camadas de portabilidade de hardware existentes no SO alvo.

Vamos dar uma olhada na visão geral alto-nível do processo de boot:

image 43
Figura 1 – Visão geral do processo de boot do MCUboot

Pontos importantes a serem notados:

  • O slot primário (Primary slot) é onde a imagem principal inicializável se encontra, a execução do firmware corrente sempre ocorre a partir dele.
  • O slot secundário (Secondary slot) é utilizado para atualizações. Ele armazena a imagem de atualização recebida e, após a permuta, armazena a imagem original, garantindo que a ação possa ser revertida caso ocorra algum problema.
  • A região de scratch é utilizada para auxiliar a permuta de imagens no processo de atualização.
  • Um cabeçalho e um sufixo de dados são adicionados à imagem para guardar e rastrear informações gerais, estados da permuta e estados da atualização. 

Veja mais na página oficial do MCUboot.

Preparando o ambiente

Primeiramente, precisamos preparar o ambiente de desenvolvimento. Este guia pressupõe o uso do Linux (Ubuntu 20.04.2 LTS).

Adicionalmente, assegure-se de que possui Git, Python3, pip e CMake instalados. Caso não tenha, você pode executar os seguintes comandos (este passo é opcional):

  1. Faça o clone do repositório:
  1. Instale as dependências adicionais necessárias pelo MCUboot:
  1. Atualize os submódulos necessários pelo porte da Espressif. Este passo pode levar um tempo, pois irá baixar a versão do ESP-IDF que contém a HAL (camada de abstração de hardware) para o ESP32 e a toolchain usada para compilação.  Também é baixada a biblioteca mbedtls requerida pelo MCUboot.
  1. Agora precisamos instalar as dependências do IDF e configurar as variáveis de ambiente. Este passo pode levar algum tempo:
  • Obs.: Se você já instalou o IDF alguma vez, verifique qual é a toolchain que está instalada. Você pode executar ls ~/.espressif/tools/xtensa-esp32-elf/ para checar se o diretório “esp-2020r3-8.4.0” já existe. Caso o diretório não exista, execute os comandos abaixo. Se você tem alguma outra versão (esp-2021r1-8.4.0, por exemplo), você deve mover este diretório para outro lugar para que a toolchain compatível com o porte da Espressif seja instalado na pasta citada. 

Compilação e Flash do MCUboot na ESP32

Agora que temos tudo preparado, vamos compilar nosso bootloader.

  1. Compile e gere o ELF do bootloader:
  1. Converta o ELF para o binário final do bootloader, preparada para o flash:
  1. Finalmente, faça o flash do MCUboot na sua placa:

Você deve ajustar a porta USB (-p /dev/ttyUSB0) e o baud rate (-b 2000000) de acordo com a conexão de sua placa.

Agora podemos verificar a saída serial na porta UART (a mesma que utilizamos para o flash):

Como ainda não fizemos o flash de nenhuma imagem de aplicação ainda, veremos o seguinte log:

image 44

Preparação para o flash de uma aplicação

Para qualquer imagem de aplicação que você use com o MCUboot, será necessário assiná-la e prepará-la adequadamente, e o imgtool é utilizado para isso. É uma ferramenta que adiciona o cabeçalho e sufixo necessários ao binário, assina o firmware e também pode gerar as chaves para serem utilizadas no processo. imgtool pode ser encontrado em <MCUBOOT_DIR>/scripts/imgtool.py (<MCUBOOT_DIR> é o caminho para o diretório onde o repositório foi clonado), ou você também pode instalar a ferramenta da seguinte forma (opcional):

Há algumas chaves de exemplo no repositório que são utilizadas por padrão. É crucial que você nunca use essas chaves-exemplo em produção já que a chave privada se encontra aberta no repositório. Mais informações sobre como gerar e gerenciar chaves de criptografia e assinatura em imgtool.

Assinatura e flash da aplicação

Estou deixando aqui dois exemplos de aplicação para serem testadas com o MCUboot, uma do Zephyr e outra do NuttX. Você também pode criar uma aplicação “do zero” para ambos, mas certifique-se de habilitar a configuração de compatibilidade com o MCUboot em qual RTOS escolher.

Agora precisamos assinar o binário. Para isso utilizaremos o imgtool.

Como você pode notar abaixo, estamos utilizando o imgtool provido pelo repositório do MCUboot, mas você também pode utilizar aquele instalado através do pip. Substitua <app.bin> por “nuttx.bin” ou “zephyr.bin”, dependendo do binário escolhido, sendo ele o objeto de origem e “signed.bin” o objeto assinado final:

De forma alternativa, se você instalou imgtool através do pip:

Uma explicação rápida sobre o que a ação do imgtool e seus parâmetros estão fazendo:

  • --align 4: Especifica o alinhamento da flash como 4 bytes (palavra de 32 bit).
  • -v 0: Especifica a versão da imagem, neste caso especificamos como ‘0’.
  • -H 32: Especifica o tamanho do cabeçalho do MCUboot que será adicionado ao binário.
  • --pad-header: Indica que o cabeçalho do MCUboot precisa explicitamente ser adicionado pela ferramenta (o build do Zephyr para algumas plataformas já adiciona o espaço necessário preenchido com ‘0’s e podem não necessitar do parâmetro).
  • -S 0x00100000: Indica o tamanho do slot, assim a ferramenta também conseguirá adicionar a região de sufixo adequadamente.

Não estamos lidando com atualizações ainda. Se este é o seu caso, há outros parâmetros requeridos para que isso seja feito. Eles serão abordados nas próximas partes desta série sobre MCUboot no ESP32.

O próximo passo é fazer o flash do dispositivo:

Verificando a saída serial, finalmente podemos ver:

  • Se você escolheu “zephyr.bin”:
image 45
  • Se você escolheu “nuttx.bin”:
image 46

Assim, o MCUboot carregou a aplicação exemplo do slot primário. Note que nós fizemos o flash manualmente no endereço 0x10000, o qual é o endereço esperado do slot primário pelo bootloader. Veremos um pouco mais sobre a organização da flash na próxima seção.

Organização da Flash

O MCUboot define uma organização para a flash e uma área da flash pode conter múltiplas imagens executáveis dependendo da configuração de boot e atualização. Cada área de imagem contém dois slots de imagem: um primário e um secundário, e por padrão o bootloader executa apenas a imagem do slot primário. O slot secundário é onde a imagem de atualização recebida é colocada antes de ser “instalada”, seu conteúdo é então permutado com o slot primário ou irá sobrescrevê-lo no processo de atualização. Portanto, podemos identificar quatro tipos de áreas de flash no layout:

ÁREAIDDESCRIÇÃO
FLASH_AREA_BOOTLOADER0Esta é a área para o próprio bootloader
FLASH_AREA_IMAGE_PRIMARY(0)1Slot primário para a imagem executável
FLASH_AREA_IMAGE_SECONDARY(0)2Slot secundário para a imagem recebida
FLASH_AREA_IMAGE_SCRATCH3Esta área é necessária para permitir a permuta de imagens de forma confiável e deve ter um tamanho o suficiente para armazenar pelo menos o maior setor a ser permutado.
Tabela 1: Tipos de áreas da flash

O MCUboot também suporta o uso de múltiplas imagens e podemos definir outras áreas de imagem, cada qual com seus respectivos slots primários e secundários. 

O porte da Espressif no momento suporta apenas uma área de imagem com seus dois slots, então a flash foi particionada de acordo:

ÁREAENDEREÇOTAMANHO
BOOTLOADER0x10000xF000
PRIMARY SLOT0x100000x100000
SECONDARY SLOT0x1100000x100000
SCRATCH0x2100000x40000
Tabela 2: Organização da flash para MCUboot na ESP32

A informação sobre o layout da flash é colocada no arquivo bootloader.conf do porte da Espressif para MCUboot. Os endereços e tamanhos podem ser alterados, no entanto, as seguintes regras devem ser respeitadas:

  • O endereço do bootloader deve ser mantido, pois por padrão é para onde o ESP32 realiza o jump após um reset.
  • Nenhum dos slots devem se sobrepor.
  • Os slots primário e secundário devem possuir o mesmo tamanho.
  • A área auxiliar de scratch deve possuir tamanho o suficiente para armazenar pelo menos o tamanho do maior setor que será permutado, devendo ser então o mesmo tamanho do maior setor da flash.
  • A aplicação e o agente de atualização devem ter conhecimento deste layout para que tudo possa ser posicionado adequadamente.

Em nosso exemplo, 0x10000 é o endereço para o slot primário, de onde o bootloader irá carregar e inicializar a imagem. Se quisermos atualizar o dispositivo, o agente de atualização deve estar ciente da organização da flash, assinar a imagem e colocá-la no slot secundário. Note que o processo de assinatura será um pouco diferente do exemplo deste tutorial. Mais informações sobre atualização de imagem serão abordadas no futuro.

Conclusão

MCUboot provê uma estrutura sólida e define um fluxo padrão para atualização de firmware e boot seguro. Portanto, uma vez que essas funcionalidades estão implementadas como parte do bootloader, elas podem ser facilmente habilitadas sem muitas modificações no desenvolvimento de um firmware.

Além disso, é um projeto open source, o que traz todas as vantagens de ser desenvolvido por uma comunidade comumente interessada, mas heterogênea, com respostas rápidas para solução de problemas e desenvolvimento engajado.

Vimos neste guia o processo de build do bootloader MCUboot para ESP32, vimos como assinar uma imagem e também como a flash deve ser organizada. O próximo passo é entender como as atualizações funcionam no MCUboot e como utilizar esta funcionalidade apropriadamente.

Referências

Formado em Ciências da Computação pela UFSCar. Atualmente trabalhando como Engenheiro de Software Embarcado na Espressif Systems e contribuindo para a integração de seus SoCs em projetos embarcados open source como MCUboot, Zephyr e NuttX.
Interessado em software embarcado, em aprender coisas novas e em compartilhar conhecimento!

Notificações
Notificar
guest
6 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Marc
Marc
17/08/2021 13:00

Nice work! Can you also share the zephyr configuration you used to build the hello_world example? I can run either the hello_world example or mcuboot. Booting hello_world with mcuboot fails. I guess devicetree/Kconfig settings are missing.

Marc
Marc
Reply to  Almir Kazunari Okato
26/08/2021 19:18

Hello Almir, thanks for the update. I was able to build and run different Zephyr examples with code from the PR and upstream MCUboot. Great to see progress on ESP32 and Zephyr. My next step will be trying to get actual FOTA working.

Rubens Junior
Rubens Junior
04/08/2021 09:57

Muito bom! Parabéns!

WEBINAR

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

DATA: 26/10 ÀS 19:30 H