ÍNDICE DE CONTEÚDO
- Primeiros passos com ESP32 utilizando MCUboot como bootloader
- Firmware update utilizando MCUboot no ESP32
Continuando a série MCUboot no ESP32, onde anteriormente explicamos as características, o funcionamento geral deste bootloader e também realizamos algumas etapas práticas para a preparação de ambiente de desenvolvimento, compilação e execução no ESP32.
Este presente artigo abordará o processo de atualização de firmware realizado pelo MCUboot e também incluirá a realização de um exemplo OTA (Over-the-air) deste processo no ESP32.
Para ilustrar as etapas do processo de atualização o exemplo prático utiliza o RTOS NuttX. Para quem não tem conhecimento sobre este RTOS, sugiro a leitura do artigo Primeiros Passos com o ESP32 e o NuttX. A placa ESP32-DevKitC será novamente utilizada e assume-se como premissa de que os passos para a preparação de ambiente do artigo anterior, Primeiros passos com ESP32 utilizando MCUboot como bootloader, também já foram realizados.
O processo de atualização de firmware
O processo de atualização padrão no MCUboot está diretamente ligado a organização da flash do dispositivo, pois ocorre através da permuta (“swap”) de regiões e para cada imagem de firmware executável existem dois “slots”: o Primário, onde reside a imagem a ser executada e o Secundário, onde residirá sua imagem de atualização (ou de rollback no momento após a atualização). Há também a região de “scratch”, que auxilia no swap dos slots.
Deste modo o MCUboot consegue integrar importantes características ao processo:
- Segurança:
- 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/decriptação) durante o transporte e/ou durante o armazenamento da mesma em flash externa. O MCUboot possui suporte para encriptação/decriptação on-the-fly durante o processo de atualização e utiliza algoritmos AES ou ECIES para isso.
Nota: o slot primário sempre é armazenado sem encriptação, a premissa original do MCUboot é de que o slot primário da imagem principal resida em flash interna, o que resguarda seu acesso por via física. Contudo, no port MCUboot da Espressif é possível ativar a criptografia de flash nativa de suas placas ESP32-XX e manter a confidencialidade do processo. Mais informações na documentação do port.
- 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.
As etapas gerais do processo de atualização, considerando um dispositivo já executando um firmware normalmente, são:
- Sinalização de atualização: agente atualizador (update agent) presente no firmware atual recebe ou busca a sinalização de que há uma atualização. Esta etapa não diz respeito ao MCUboot, sendo responsabilidade do firmware principal conter e executar este agente.
Nota: Há frameworks/plataformas como, por exemplo, Golioth, Hawkbit ou UpdateHub que são compatíveis com a estrutura e organização do MCUboot.
- Download do novo firmware: o agente atualizador faz o download do novo firmware e o grava no slot secundário referente à imagem a ser atualizada.
- Reset do dispositivo: a consolidação do novo firmware só será efetivada no próximo boot, sendo assim, é necessário o reiniciar o dispositivo.
- Verificação do novo firmware: o MCUboot neste novo ciclo de boot realizará a checagem de hash e assinatura (se houver) da nova imagem.
- Permuta do slot secundário para o slot primário.
- Boot do novo firmware que agora reside no slot primário.
- O firmware atualizado se verificará, pois é necessário que ele indique ao MCUboot que a imagem está ok (a bliblioteca utilizada é a
bootutil
do MCUboot, que deve ser integrada ao firmware).
Veja o resumo no diagrama de sequência:
Update OTA utilizando MCUboot no NuttX
Vamos agora demonstrar o processo, explicado na prática. Como já dito no início do artigo, para executar todos os passos deste exemplo é necessário ter os ambientes do NuttX e do MCUboot preparados.
Primeiramente é necessário compilar e gravar o bootloader, para isso siga os passos do artigo anterior . Ainda estamos lidando com as features mais básicas do bootloader, então caso já tenha o bootloader gravado, não será necessário realizar essa etapa novamente.
Compilando o agente atualizador no NuttX
Vamos configurar a aplicação de exemplo do agente atualizador existente no NuttX apps.
Primeiro vamos ao diretório raiz do NuttX (por exemplo “~/nuttxspace/nuttx”) e fazer a pré-configuração indicando a placa (target/de desenvolvimento) e as aplicações que farão parte do firmware. Neste caso a pré-configuração inicial que faremos será da aplicação Wireless API para utilizarmos os comandos para a conexão Wi-Fi necessária para a atualização OTA.
1 2 |
cd <NUTTX_DIR> ./tools/configure.sh esp32-devkitc:wapi |
Agora vamos terminar a configuração adicionando a aplicação do agente atualizador compatível com o MCUboot.
1 |
make menuconfig |
No menu gráfico vamos realizar as seguintes configurações:
– Habilitar [Build Setup → Prompt for development and/or incomplete code/drivers]:
– Alterar [System Type → Application Image Configuration → Application Image Format] para MCUboot-bootable format
:
Uma coisa interessante a se observar neste menu, é que estamos compilando a “primeira versão” do firmware principal, então naturalmente ela deve ser colocada no slot primário:
– Verificar e alterar se necessário os endereços e tamanhos dos slots de acordo com o que foi configurado no MCUboot [System Type → SPI Flash configuration]:
– Desabilitar a persistência de parâmetros do Wi-Fi [System Type → Wi-Fi configuration → Save Wi-Fi Parameters]:
– Habilitar o comando de reset [Board Selection → Enable reset interfaces]:
– Habilitar o exemplo de agente de atualização do MCUboot [Application Configuration → Bootloader Utilities → MCUboot] e [Application Configuration → Bootloader Utilities → MCUboot → MCUboot update agent example]:
– Alterar [Application Configuration → Bootloader Utilities → MCUboot → Download buffer size in bytes] para 4096
.
– Habilitar o web client que será necessário para fazer o download da atualização via HTTP [Application Configuration → Network Utilities → uIP web client]:
– Para facilitar a diferenciação deste firmware principal de sua atualização, vamos colocar uma mensagem de inicialização no NuttX shell [Application Configuration → NSH Library → Message of the Day (MOTD)], Welcome to NuttX from MCUboot, this is the FIRST firmware!
:
Salve as configurações e saia do menu. Vamos agora compilar:
1 |
make -j8 |
Note que o sistema de build já fez a adição do cabeçalho MCUboot à imagem e indicou que ela está confirmada:
Finalmente faremos o flash desta imagem no dispositivo. Ajuste <PORT> de acordo com a porta em que o dispositivo está conectado (exemplo: /dev/ttyUSB0
), <BAUD> de acordo com o baud rate compatível e <FLASH_SIZE> de acordo com o tamanho da flash do dispositivo:
1 2 |
esptool.py -p <PORT> -b <BAUD> --after no_reset --chip esp32 write_flash --flash_mode dio --flash_size <FLASH_SIZE> --flash_freq 40m 0x10000 nuttx.bin picocom -b <BAUD> <PORT> |
Reinicie o dispositivo. Observe a nossa mensagem de inicialização configurada anteriormente:
Compilando o firmware de atualização
Já temos nosso dispositivo rodando o NuttX, que também contém a aplicação do agente atualizador. Agora vamos compilar a nossa atualização.
Primeiro limpe o ambiente:
1 |
make distclean |
Para o nosso exemplo podemos pré-configurar uma aplicação mais simples, contendo apenas o shell e a aplicação de confirmação de imagem do MCUboot, já que faremos a atualização apenas uma vez:
1 |
./tools/configure.sh esp32-devkitc:nsh |
Novamente vamos terminar de configurar no menu, algumas configurações serão as mesmas que as anteriores:
1 |
make menuconfig |
– Habilitar [Build Setup → Prompt for development and/or incomplete code/drivers].
– Alterar [System Type → Application Image Configuration → Application Image Format] para MCUboot-bootable format
.
– Agora vamos alterar aquela configuração do slot em que será colocada a nova imagem [System Type → Application Image Configuration → Target slot for image flashing]:
– Habilitar o driver de SPI Flash [System Type → ESP32 Peripheral Selection]:
– Habilitar o comando de reset [Board Selection → Enable reset interfaces]:
– Habilitar a aplicação de confirmação de imagem do MCUboot [Application Configuration → Bootloader Utilities → MCUboot] e [Application Configuration → Bootloader Utilities → MCUboot → MCUboot slot confirm example]:
– Alterar a mensagem de inicialização do NuttX shell [Application Configuration → NSH Library → Message of the Day (MOTD)] para Welcome to NuttX from MCUboot, this is the UPDATED firmware!
.
1 |
make -j8 |
Observe que, como selecionamos a opção de que a imagem seria gravada no secondary slot, o sistema de build não adicionou a confirmação de imagem ao cabeçalho da imagem:
Execução da atualização OTA
Neste exemplo será necessário um servidor HTTP para disponibilizar nossa imagem de atualização. Abra outro terminal ou em background no mesmo diretório em que se encontra o binário da imagem:
1 |
sudo python -m http.server 8080 |
Chegou a hora de executar nossa atualização. Abra novamente o monitoramento do dispositivo e o reinicie:
1 |
picocom -b <BAUD> <PORT> |
O próximo passo é conectar o dispositivo à sua rede Wi-Fi para que ele consiga buscar a atualização no servidor que está local em sua máquina:
1 2 3 4 5 |
ifup wlan0 wapi mode wlan0 2 wapi psk wlan0 <PSK_KEY_WIFI> 3 wapi essid wlan0 <SSID_WIFI> 1 renew wlan0 |
Você pode testar a sua conexão através do ping:
1 |
ping 8.8.8.8 |
Finalmente vamos utilizar o agente de atualização para fazer o download da nova imagem:
1 |
mcuboot_agent http://<SERVER_IP>:8080/nuttx.bin |
Note que após o download, o agente de atualização reiniciou o dispositivo para que o MCUboot faça a permuta entre a imagem nova, que está no slot secundário, e a imagem antiga que está no slot primário. Após a permuta, o bootloader iniciou a imagem atualizada e exibiu a mensagem colocada para indicar que a nova imagem está sendo executada como esperado.
Ainda falta um passo para completarmos a atualização, que é a confirmação desta imagem que foi atualizada. Caso essa confirmação não seja feita, ocorrerá um rollback no próximo boot, ou seja, ocorrerá uma permuta e a imagem antiga voltará ao slot primário. Caso queira (não é necessário, claro), você pode testar esse rollback reiniciando o dispositivo:
Caso tenha feito o rollback, repita os passos de conexão à rede Wi-Fi e download da imagem através do agente de atualização.
Para confirmar a imagem, vamos executar a aplicação de confirmação:
1 |
mcuboot_confirm |
Pronto! Como a imagem foi confirmada, ela fica consolidada e não sofrerá rollback nos próximos boots.
Conclusão
O processo de atualização via permuta do MCUboot contribui na robustez dos projetos de firmwares, já que provê mecanismos de proteção ao longo de suas diversas etapas, como a verificação de corrupção dos dados da nova imagem, tolerância de falhas enquanto ocorre a permuta de imagens e rollback caso a imagem não se consolide.
Na perspectiva da camada de aplicação, há menor complexidade para a integração desse processo de atualização contendo todas as vantagens citadas. Sendo a imagem apenas responsável pelo recebimento (download) e correto posicionamento do novo firmware no Slot secundário. Também, caso desejável, logo após a permuta a imagem atualizada pode realizar seus próprios testes antes de se marcar como permanente – o rollback é realizado pelo próprio MCUboot no próximo boot caso haja algum problema.
Por outro lado, essa arquitetura demanda uma quantidade maior de memória e espaço em memória flash devido às áreas auxiliares da imagem como os trailers e a área de Scratch. Além disso, com o aumento de escritas na flash por causa da permuta, também acelera o desgaste da região dessa área.
Agora que passamos pelas etapas da atualização de firmware OTA e vimos algumas de suas vantagens e desvantagens, os próximos passos desta série sobre MCUboot + ESP32 serão relacionados à segurança: Secure boot, autenticação de imagem e criptografia de flash.
Referências
- https://docs.mcuboot.com/design.html
- https://docs.mcuboot.com/readme-espressif.html
- MCUboot bootloader on NuttX – NuttX Online Workshop – Gustavo Nihei https://www.youtube.com/watch?v=KxhrnO4qz0w&t=32660s