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:

/* Abre id: Se o id não for atualizado em timeoutMs ms a partir da chamada, o watchdog de hardware não será servido. */
void wdOpen(enum eWdClient id, uint32_t timeoutMs);

/* Serve o watchdog de software id */
void wdRefresh(enum eWdClient id);

/* Fecha o watchdog de software id, agora não é mais necessário servir este watchdog */
void wdClose(enum eWdClient id);

 

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.

 

  ...
  wdOpen(wdMain, 5000);
  while (1)
  {
    wdRefresh(wdMain);
    ifPeriodicHook(); /* Call UI processor */
    ...
  }

 

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.

 

void ethernetTcpCallback(void) //Called by the Ethernet ISR
{
  wdOpen(wdEthernet, 1000);
  ... /* Do Tcp Stuff within 1 secound */
  wdClose(wdEthernet);
}

 

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.

 

void wdPeriodicHook(void) /* Must be called frequent */
{
  if (!gWdClientStatus.wdEnabled)
    return;
  if (gWdClientStatus.wdEthernetEnabled && 
      timeoutCheck_ms(gWdClientStatus.wdEthernetToutMs, 
                      gWdClientStatus.wdEthernetToutSeed))
    return;
  if (gWdClientStatus.wdMainEnabled && 
      timeoutCheck_ms(gWdClientStatus.wdMainToutMs, 
                      gWdClientStatus.wdMainToutSeed))
    return;

    _WdHwToggle(); /* Feed the real watchdog */
}

 

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

 

#ifndef WATCHDOG_H
#define WATCHDOG_H

enum eWdClient {
	wdGlobal=1,
	wdMain,
	wdEthernet
};

void wdOpen(enum eWdClient id, uint32_t timeoutMs);
void wdRefresh(enum eWdClient id);
void wdClose(enum eWdClient id);
void wdPeriodicHook(void);
void wdInit(void);

#endif //WATCHDOG_H

 

watchdog.c

 

static void _WdHwInit(void)
{
	...
}
static void _WdHwToggle(void)
{
	...
}

struct sWdClientStatus
{
	uint8_t wdMainEnabled;
	uint32_t wdMainToutSeed;
	uint32_t wdMainToutMs;

	uint8_t wdEthernetEnabled;
	uint32_t wdEthernetToutSeed;
	uint32_t wdEthernetToutMs;

	uint8_t wdEnabled;
};

static struct sWdClientStatus gWdClientStatus;

void wdOpen(enum eWdClient id, uint32_t timeoutMs)
{
	switch (id)
	{
	case wdGlobal:
		gWdClientStatus.wdEnabled = 1;
		break;
	case wdMain:
		gWdClientStatus.wdMainEnabled = 1;
		gWdClientStatus.wdMainToutSeed = timeoutInit();
		gWdClientStatus.wdMainToutMs = timeoutMs;
		break;
	case wdEthernet:
		gWdClientStatus.wdEthernetEnabled = 1;
		gWdClientStatus.wdEthernetToutSeed = timeoutInit();
		gWdClientStatus.wdEthernetToutMs = timeoutMs;
		break;
	}
}

void wdRefresh(enum eWdClient id)
{
	switch (id)
	{
	case wdMain:
		gWdClientStatus.wdMainToutSeed = timeoutInit();
		break;
	case wdEthernet:
		gWdClientStatus.wdEthernetToutSeed = timeoutInit();
		break;
	}
}

void wdClose(enum eWdClient id)
{
	switch (id)
	{
	case wdGlobal:
		gWdClientStatus.wdEnabled = 0;
		break;
	case wdMain:
		gWdClientStatus.wdMainEnabled = 0;
		gWdClientStatus.wdMainToutSeed = 0;
		gWdClientStatus.wdMainToutMs = 0;
		break;
	case wdEthernet:
		gWdClientStatus.wdEthernetEnabled = 0;
		gWdClientStatus.wdEthernetToutSeed = 0;
		gWdClientStatus.wdEthernetToutMs = 0;
		break;
	}
}

void wdPeriodicHook(void)
{
	if (!gWdClientStatus.wdEnabled)
		return;
	if (gWdClientStatus.wdEthernetEnabled && 
            timeoutCheck_ms(gWdClientStatus.wdEthernetToutMs, 
                            gWdClientStatus.wdEthernetToutSeed))
		return;
	if (gWdClientStatus.wdMainEnabled && 
            timeoutCheck_ms(gWdClientStatus.wdMainToutMs, 
            gWdClientStatus.wdMainToutSeed))
		return;

	_WdHwToggle();
}

void wdInit(void) {
	gWdClientStatus.wdEnabled = 1;

	gWdClientStatus.wdEthernetEnabled = 0;
	gWdClientStatus.wdMainEnabled = 0;

	_WdHwInit();
}

 

 

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.

 

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
1 Comentário
recentes
antigos mais votados
Inline Feedbacks
View all comments
Tiago Possato
Tiago Possato
05/12/2014 10:31

Olá Felipe, essa sua biblioteca de Watchdog é ótima, está salvando meu projeto! Porém estou com algumas dúvidas em relação as funções “timeoutInit()” e “timeoutCheck_ms(…,…)”. Como não entendi a forma de implementá-las, fiz umas alterações e gostaria que você me desse sua opinião a respeito. Estou usando o TI-RTOS, da Texas em um placa Tiva C Connected Launchpad. A princípio está tudo funcionando, mas fiquei na dúvida se é ‘sorte’ ou a bliblioteca está trabalhando como a sua original. As alterações que fiz são essas: void wdOpen(enum eWdClient id, uint32_t timeoutMs) { switch (id) { case wdGlobal: gWdClientStatus.wdEnabled = 1;… Leia mais »

Home » Software » Firmware » Servindo Watchdog adequadamente

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste:
Nenhum resultado encontrado.