The irony is that the chipset on these motherboards has a watchdog! But because the manufacturer won't share the chipset datasheet, no driver exists. What I did was easier than reversing the BIOS (but I might still do that):
After a night of tinkering:
#include "VirtualSerial.h"
static CDC_LineEncoding_t LineEncoding = { .BaudRateBPS = 0,
.CharFormat = CDC_LINEENCODING_OneStopBit,
.ParityType = CDC_PARITY_None,
.DataBits = 8
};
uint8_t led_pwm = 255;
int8_t led_pwm_delta = 0;
uint16_t led_pwm_delta_delay = 0;
uint8_t reset_countdown = 6;
enum {
watchdog_off = 0,
watchdog_armed = 1,
watchdog_cleared = 2,
watchdog_timeout = 3,
watchdog_reset_wait = 4,
};
uint8_t watchdog_state = watchdog_off;
char * report = 0;
int main(void)
{
MCUSR &= ~(1 << WDRF);
wdt_disable();
clock_prescale_set(clock_div_1);
USB_Init();
sei();
DDRC = 0;
PORTC = 0;
DDRD = 255;
PORTD = 0;
uint16_t count = 63;
for (;;) {
count += 64; // 64 controls the update rate of the LED by speeding or slowing the overflow to 0
// square led_pwm: fast linearization of the nonlinear response of the LED
PORTD = (count > ((uint16_t) led_pwm*led_pwm)) ?
0 : (1 << PORTD6);
if (watchdog_state == watchdog_timeout) {
led_pwm_delta_delay += 2;
if (!led_pwm_delta_delay) {
if (!reset_countdown) {
led_pwm = 64;
DDRC = 1 << PORTC7;
reset_countdown = 7;
} else if (reset_countdown == 7) {
led_pwm = 10;
reset_countdown = 6;
watchdog_state = watchdog_reset_wait;
} else {
led_pwm ^= 255;
reset_countdown--;
DDRC = 0;
}
}
} else {
if (watchdog_state == watchdog_cleared) watchdog_state = watchdog_armed;
led_pwm_delta_delay += 64;
if (!led_pwm_delta_delay && led_pwm_delta) {
int16_t next_pwm = led_pwm;
next_pwm += led_pwm_delta;
if (next_pwm > 255) {
led_pwm_delta = -1;
led_pwm = 255;
} else if (next_pwm < 0) {
led_pwm_delta = 1;
led_pwm = 0;
if (watchdog_state == watchdog_armed) {
watchdog_state = watchdog_timeout;
led_pwm_delta = 0;
led_pwm = 255;
}
} else {
led_pwm = next_pwm;
}
}
}
CDC_Task();
USB_USBTask();
}
}
void EVENT_USB_Device_Connect(void)
{
led_pwm = 0;
led_pwm_delta = 0;
}
void EVENT_USB_Device_Disconnect(void)
{
led_pwm = 255;
led_pwm_delta = 0;
watchdog_state = watchdog_off;
}
void EVENT_USB_Device_ConfigurationChanged(void)
{
if (Endpoint_ConfigureEndpoint(CDC_NOTIFICATION_EPADDR, EP_TYPE_INTERRUPT, CDC_NOTIFICATION_EPSIZE, 1) &&
Endpoint_ConfigureEndpoint(CDC_TX_EPADDR, EP_TYPE_BULK, CDC_TXRX_EPSIZE, 1) &&
Endpoint_ConfigureEndpoint(CDC_RX_EPADDR, EP_TYPE_BULK, CDC_TXRX_EPSIZE, 1))
{
led_pwm = 0;
led_pwm_delta = 0;
} else {
led_pwm = 128;
led_pwm_delta = 0;
}
LineEncoding.BaudRateBPS = 0;
}
void EVENT_USB_Device_ControlRequest(void)
{
switch (USB_ControlRequest.bRequest)
{
case CDC_REQ_GetLineEncoding:
if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
{
Endpoint_ClearSETUP();
Endpoint_Write_Control_Stream_LE(&LineEncoding, sizeof(CDC_LineEncoding_t));
Endpoint_ClearOUT();
}
break;
case CDC_REQ_SetLineEncoding:
if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
{
Endpoint_ClearSETUP();
Endpoint_Read_Control_Stream_LE(&LineEncoding, sizeof(CDC_LineEncoding_t));
Endpoint_ClearIN();
}
break;
case CDC_REQ_SetControlLineState:
if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
{
Endpoint_ClearSETUP();
Endpoint_ClearStatusStage();
}
break;
}
}
void CDC_Task(void)
{
static char * last_report = 0;
if (USB_DeviceState != DEVICE_STATE_Configured)
return;
report = (watchdog_state == watchdog_timeout) ? "watchdog timeout\r\n" : "watchdog armed\r\n";
if ((report != 0) && (last_report != report) && LineEncoding.BaudRateBPS)
{
last_report = report;
if (watchdog_state == watchdog_off) {
watchdog_state = watchdog_armed;
led_pwm_delta = 1;
}
Endpoint_SelectEndpoint(CDC_TX_EPADDR);
Endpoint_Write_Stream_LE(report, strlen(report), NULL);
bool IsFull = (Endpoint_BytesInEndpoint() == CDC_TXRX_EPSIZE);
Endpoint_ClearIN();
if (IsFull)
{
Endpoint_WaitUntilReady();
Endpoint_ClearIN();
}
}
Endpoint_SelectEndpoint(CDC_RX_EPADDR);
if (Endpoint_IsOUTReceived())
{
uint8_t Buffer[Endpoint_BytesInEndpoint()];
uint16_t DataLength = Endpoint_BytesInEndpoint();
Endpoint_Read_Stream_LE(&Buffer, DataLength, NULL);
Endpoint_ClearOUT();
Endpoint_SelectEndpoint(CDC_TX_EPADDR);
Endpoint_Write_Stream_LE(&Buffer, DataLength, NULL);
Endpoint_ClearIN();
Endpoint_WaitUntilReady();
Endpoint_ClearIN();
watchdog_state = watchdog_cleared;
led_pwm = 0;
led_pwm_delta = 1;
led_pwm_delta_delay = 0;
}
}
The code uses 1 LED for status, and one GPIO connected to the reset switch on your motherboard. The pins are correct for a Teensy 2 ($16). The reset line can be connected to both the GPIO and your manual reset button: the line is open drain with a pullup so any number of reset drivers can be wired to it.
The AVR emulates a USB serial adapter, and must receive 1 byte every 4 seconds (if your AVR crystal runs at 16 MHz), or it will hit the reset button. I have Windows and Linux code that does this for me - it's quite simple. To reduce the number of moving parts for reliability, I recommend against a simple echo "" >/dev/ttyACM0 because it will open and close the port, causing unnecessary USB traffic. A slightly better way is to leave the fd (handle) open:
exec 5>/dev/ttyACM0
while true; do
echo "" >&5
sleep 4
done
The status LED stays off until the serial port is configured. For Windows this happens when the serial port is opened. For Linux, this happens when the cdc_acm module is loaded. Then the status LED indicates the countdown, starting over each time a byte is received. During the reset process, the LED shows a "warning pattern," asserts reset with a different intensity, and then waits for the system to reset the serial port.
There's still lots that needs work! A watchdog should be as reliable as possible, and the serial port code is more complicated than necessary. It does help when debugging things. Please donate if you want me to improve on this. I also want to port Coin Control to Bitcoin-Qt head - send me a PM if that would be useful to you.
Here are some older threads on watchdogs:
- Hardware watchdogs start at €25 on ebay: https://bitcointalksearch.org/topic/harware-watchdog-via-usbserial-for-debian-31417
- A software watchdog: https://bitcointalksearch.org/topic/guidehow-to-setup-an-automated-headless-linuxcoin-mining-rig-watchdog-29021 (software watchdogs would not have saved last night)
- A remote-control power switch: https://bitcointalksearch.org/topic/remote-power-switch-rebooting-computer-via-hard-reset-remotely-7150 (but cycling power is overkill and shortens the life of your rig)
Please donate if you find this useful: 1KBMjP645vz9HGyUru22V5RgSyacdJHsfh