Firmware: Fixing Interrupt Concurrency Bugs (Spinlock)#
Interrupts Are Special#
For example: 7 (“sensor burning”) beats 42 (timer)
⟶ Best to treat ISRs as “parallel”
⟶ Trust nobody
Low Hanging Fruit#
In FSM::start():
case State::INIT:
_interval_timer.start(Why::TimeSpec(0,1), _interval);
Why::IRQ::connect(_sensor_burning_irq, [this](int irqnum){_sensor_burning();});
Why::IRQ::connect(_operator_button_irq, [this](int irqnum){_operator_button_pressed();});
_sensor.turn_on();
_state = State::RUNNING;
break;
What if timer expires before state is set to
RUNNING?What if we measure before sensor has been turned on?
⟶ Look in
FSM::_timer_tick()
Subtle: Interrupt Nesting (7 Beats Timer)#
Sensor blows up right at the same time a “soft overheat” is detected
Timer tick interrupted by higher priority IRQ
State change to
SENSORBURNINGTimer tick continues
⟶ Overwrites
SENSORBURNINGwithOVERTEMP“State Change Overlap”
ISR: Timer tick |
ISR: Sensor burning |
|---|---|
case State::RUNNING: {
double temp = _sensor.get_temperature();
if (temp > _soft_critical_temp) {
_cooling.set_state(true);
.
.
.
.
.
.
_soft_alarm.set_state(true);
_state = State::OVERTEMP;
}
...
break;
|
.
.
.
.
.
case State::RUNNING:
_sensor.turn_off();
_cooling.set_state(false);
_state = State::SENSORBURNING;
_hard_alarm.set_state(true);
break;
.
.
.
.
.
|
Effects Of Unprotected Interrupt Nesting#
Protecting Against Unwanted Interrupt Nesting#
Turn off interrupts before critical section is entered
But: there is a better too that does more
… and also works on a multicore CPU
⟶ Spinlock
Critical Section?#
Look in state machine
First:
FSM::_timer_tick()What is critical? Remember problem: in
RUNNING, we got interrupted.Here’s the relevant code from Finite State Machine (fsm.h, fsm.cpp)
switch (_state) {
...
case State::RUNNING: {
double temp = _sensor.get_temperature();
if (temp > _soft_critical_temp) {
_cooling.set_state(true);
_soft_alarm.set_state(true);
_state = State::OVERTEMP;
}
else if (temp > _high_temp)
_cooling.set_state(true);
else if (temp < _low_temp)
_cooling.set_state(false);
break;
}
...
}
Updated FSM: Spinlock#
#pragma once
#include "sensor.h"
#include "switch.h"
#include <why-timer.h>
#include <why-spinlock.h> // <-- pull in spinlock
class FSM
{
public:
enum class State {
INIT,
RUNNING,
OVERTEMP,
SENSORBURNING
};
public:
FSM(Why::TimeSpec interval,
int sensor_burning_irq, Switch& hard_alarm,
long soft_critical_temp, Switch& soft_alarm,
int operator_button_irq,
Sensor& sensor, Switch& cooling,
long low_temp, long high_temp);
void start();
private:
void _timer_tick();
void _sensor_burning();
void _operator_button_pressed();
private:
Why::Spinlock _lock; // <-- essential class member!
Why::TimeSpec _interval;
Why::Timer _interval_timer;
int _sensor_burning_irq;
Switch& _hard_alarm;
long _soft_critical_temp;
Switch& _soft_alarm;
int _operator_button_irq;
Sensor& _sensor;
Switch& _cooling;
long _low_temp;
long _high_temp;
State _state;
};
#include "fsm.h"
#include <why-irq.h>
#include <cassert>
FSM::FSM(
Why::TimeSpec interval,
int sensor_burning_irq, Switch& hard_alarm,
long soft_critical_temp, Switch& soft_alarm,
int operator_button_irq,
Sensor& sensor, Switch& cooling,
long low_temp, long high_temp)
: _interval(interval),
_interval_timer([this](){_timer_tick();}),
_sensor_burning_irq(sensor_burning_irq),
_hard_alarm(hard_alarm),
_soft_critical_temp(soft_critical_temp),
_soft_alarm(soft_alarm),
_operator_button_irq(operator_button_irq),
_sensor(sensor),
_cooling(cooling),
_low_temp(low_temp),
_high_temp(high_temp),
_state(State::INIT)
{}
void FSM::start()
{
_lock.lock(); // <-- acquire
switch (_state) {
case State::INIT:
_interval_timer.start(Why::TimeSpec(0,1), _interval);
Why::IRQ::connect(_sensor_burning_irq, [this](int irqnum){_sensor_burning();});
Why::IRQ::connect(_operator_button_irq, [this](int irqnum){_operator_button_pressed();});
_sensor.turn_on();
_state = State::RUNNING;
break;
case State::RUNNING:
assert(!"RUNNING");
break;
case State::OVERTEMP:
assert(!"OVERTEMP");
break;
case State::SENSORBURNING:
assert(!"SENSORBURNING");
break;
}
_lock.unlock(); // <-- release
}
void FSM::_timer_tick()
{
_lock.lock(); // <-- acquire
switch (_state) {
case State::INIT:
assert(!"INIT");
break;
case State::RUNNING: {
double temp = _sensor.get_temperature();
if (temp > _soft_critical_temp) {
_cooling.set_state(true);
_soft_alarm.set_state(true);
_state = State::OVERTEMP;
}
else if (temp > _high_temp)
_cooling.set_state(true);
else if (temp < _low_temp)
_cooling.set_state(false);
break;
}
case State::OVERTEMP: {
double temp = _sensor.get_temperature();
if (temp > _high_temp)
_cooling.set_state(true);
else if (temp < _low_temp)
_cooling.set_state(false);
break;
}
case State::SENSORBURNING:
break;
}
_lock.unlock(); // <-- release
}
void FSM::_sensor_burning()
{
_lock.lock(); // <-- acquire
switch (_state) {
case State::INIT:
assert(!"INIT");
break;
case State::RUNNING:
_sensor.turn_off();
_cooling.set_state(false);
_hard_alarm.set_state(true);
_state = State::SENSORBURNING;
break;
case State::OVERTEMP:
_sensor.turn_off();
_cooling.set_state(false);
_soft_alarm.set_state(false);
_hard_alarm.set_state(true);
_state = State::SENSORBURNING;
break;
case State::SENSORBURNING:
break;
}
_lock.unlock(); // <-- release
}
void FSM::_operator_button_pressed()
{
_lock.lock(); // <-- acquire
switch (_state) {
case State::INIT:
assert(!"INIT");
break;
case State::RUNNING:
break;
case State::OVERTEMP:
_soft_alarm.set_state(false);
_state = State::RUNNING;
break;
case State::SENSORBURNING:
break;
}
_lock.unlock(); // <-- release
}