Servindo Watchdog adequadamente

Watchdog

Watchdogs são fundamentais em produtos, são eles que devem reiniciar o sistema a um estado conhecido na ocorrência de um comportamento anômalo. Entretanto, existe uma dificuldade considerável em fazer com que o sistema sempre seja reiniciado na ocorrência de travamentos e, no final das contas, os watchdogs são difíceis de usar e frequentemente vemos produtos congelando com seus watchdogs não atuando. Neste post eu vou apresentar a maneira correta de se programar um bom watchdog e, inclusive, vou fornecer o código da minha biblioteca de watchdog. Basta copiar e usar!

 

 

Como não fazer com Watchdog

 

  1. Servir o watchdog numa interrupção de timer: se a execução do código entrar em loop ou deadlock o watchdog continuará a ser alimentado, pois as interrupções continuarão ocorrendo;
  2. Servir várias vezes o watchdog dentro do código: quanto mais vezes a função que serve o watchdog for chamada no código, maior a probabilidade de se ter uma delas na fila de execução quando o código trancar em loop;
  3. Colocar as rotinas de delay para servir o watchdog: pelo mesmo motivo do item 2, essas rotinas são usadas largamente e provavelmente serão usadas por um software rodando em loop também.

 

 

O jeito certo

 

Primeiro a biblioteca de watchdog deve ser desenvolvida de maneira a fornecer um watchdog de software a cada uma das diferentes tarefas do firmware (execução da main é uma tarefa, execução em interrupção de timer é outra tarefa, assim como execução em interrupção de ethernet, e etc.), então cada tarefa terá seu próprio watchdog simulado em software.

 

Para implementar a solução de diversos watchdogs em software, a biblioteca do watchdog fornece as seguintes funções:

 

Com essas três funções fica fácil usar o watchdog em diferentes tarefas. Na main, configura-se o timeout para o tempo suficiente (5 segundos, no caso) e chama-se apenas uma vez a função de refresh, que fica no início do loop infinito e, preferencialmente, logo antes da interface do usuário, pois um dos sintomas de um código congelado é a interface travada. Tendo o watchdog da main servido junto à interface garante que se a interface não rodar, o watchdog também não será servido e o sistema será reiniciado.

 

 

A main não deve usar a função wdClose, pois nunca para de executar. Ao contrário das demais tarefas que interrompem a execução, fazendo suas funções e depois se cessando, ou seja, rodam em interrupções. Para essas fica necessário usar a função wdClose logo antes da interrupção retornar, como mostra o snippet abaixo. No início da execução da interrupção wdOpen é usado para ativar o watchdog wdEthernet a um timeout de 1 segundo. Com isso, dentro de 1 segundo a função wdClose ou wdRefresh deve ser chamada para que o watchdog não dispare o sinal de reiniciar o sistema. No caso ilustrado o watchdog wdEthernet não é servido, apenas aberto e fechado.

 

 

Na verdade, apenas o watchdog virtual da função main seria o suficiente para garantir a eficácia da atuação do watchdog. Entretanto, frequentemente precisamos colocar timeouts na ordem de diversos segundos na main, e usando watchdogs distribuídos por tarefas atinge-se menor tempo de resposta na ocorrência de travamentos.

 

Quando se desenvolve uma biblioteca bem estruturada ela fica simples de usar, como os snippets mostram. Por trás dessas funções a biblioteca executa o seu algoritmo no seu próprio escopo eliminando complexidades das camadas superiores do software. Inserir esses níveis de abstração no código é sempre uma excelente prática de programação de sistemas embarcados.

 

Por fim, vimos que as funções wdOpen, wdRefresh e wdClose são usadas para controlar o watchdog simulado em software. Entretanto, o verdadeiro watchdog é um só, o de hardware, e que geralmente precisa ser servido a uma periodicidade na ordem de centenas de milésimos de segundos.

 

Para isso torna-se necessário usar uma função que verifica se algum dos watchdogs simulados em software extrapolou o tempo configurado e, por seguinte, sirva o watchdog de hardware. Essa função precisa garantir latência e deve ser chamada por uma interrupção de alta prioridade de timer. Abaixo o algoritmo da função.

 

 

Como eu havia prometido, abaixo segue o código completo da biblioteca. Sintam-se convidados para usá-la da maneira que lhes convir.

 

watchdog.h

 

 

watchdog.c

 

 

 

Conclusão

 

Neste post foi apresentado a maneira adequada de se utilizar watchdogs. Como muitas vezes pudemos descobrir, geralmente da pior maneira, que apenas o watchdog real, o de hardware, não é suficiente para que o sistema sempre seja reiniciado quando trancar e, assim, torna-se necessário uma solução criativa e efetiva como a apresentada.