From b0cb96a8261cf2e46920237c5416db6c70f2f3ad Mon Sep 17 00:00:00 2001 From: Felix Collins Date: Thu, 18 Sep 2025 16:59:49 +1200 Subject: [PATCH] Fixed bug where interrupt state monitoring could get out of sync if interrupts started flooding in during startup. (cherry picked from commit 11a84419fc77a38e6ce07f335beb6c2b9ae56618) --- src/devices/Tca955x/Tca955x.cs | 44 ++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/devices/Tca955x/Tca955x.cs b/src/devices/Tca955x/Tca955x.cs index 907185a9d1..bad6cb7462 100644 --- a/src/devices/Tca955x/Tca955x.cs +++ b/src/devices/Tca955x/Tca955x.cs @@ -20,8 +20,8 @@ public abstract class Tca955x : GpioDriver private readonly int _interrupt; private readonly Dictionary _pinValues = new Dictionary(); private readonly ConcurrentDictionary _eventHandlers = new ConcurrentDictionary(); - private readonly Dictionary _interruptPins = new Dictionary(); - private Dictionary _interruptLastInputValues = new Dictionary(); + private readonly Dictionary _interruptPinsSubscribedEvents = new Dictionary(); + private readonly ConcurrentDictionary _interruptLastInputValues = new ConcurrentDictionary(); private GpioController? _controller; @@ -89,7 +89,15 @@ protected Tca955x(I2cDevice device, int interrupt = -1, GpioController? gpioCont } if (_interrupt != -1) - { + { + // Initialise the interrupt handling state because ints may start coming from the INT pin + // on the expander as soon as we register the interrupt handler. + for (int i = 0; i < PinCount; i++) + { + _interruptPinsSubscribedEvents.Add(i, PinEventTypes.None); + _interruptLastInputValues.TryAdd(i, PinValue.Low); + } + _shouldDispose = shouldDispose || gpioController is null; _controller = gpioController ?? new GpioController(); if (!_controller.IsPinOpen(_interrupt)) @@ -456,7 +464,7 @@ private Task ProcessInterruptInTask() { // Take a snapshot of the current interrupt pin configuration and last known input values // so we can safely process them outside the lock in a background task. - var interruptPinsSnapshot = new Dictionary(_interruptPins); + var interruptPinsSnapshot = new Dictionary(_interruptPinsSubscribedEvents); var interruptLastInputValuesSnapshot = new Dictionary(_interruptLastInputValues); Task processingTask = new Task(() => @@ -494,9 +502,9 @@ private Task ProcessInterruptInTask() interruptLastInputValuesSnapshot[pin] = newValue; } - lock (_interruptHandlerLock) - { - _interruptLastInputValues = interruptLastInputValuesSnapshot; + foreach (var pin in interruptLastInputValuesSnapshot.Keys) + { + _interruptLastInputValues.TryUpdate(pin, interruptLastInputValuesSnapshot[pin], !interruptLastInputValuesSnapshot[pin]); } } }); @@ -557,16 +565,13 @@ protected override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEve lock (_interruptHandlerLock) { - if (_interruptPins.ContainsKey(pinNumber)) - { - throw new InvalidOperationException($"A callback is already registered for pin {pinNumber}"); - } - else + _interruptPinsSubscribedEvents[pinNumber] = eventType; + var currentValue = Read(pinNumber); + _interruptLastInputValues.TryUpdate(pinNumber, currentValue, !currentValue); + if (!_eventHandlers.TryAdd(pinNumber, callback)) { - _interruptPins.Add(pinNumber, eventType); - _interruptLastInputValues.Add(pinNumber, Read(pinNumber)); - _eventHandlers[pinNumber] = callback; - } + throw new InvalidOperationException($"An event handler is already registered for pin {pinNumber}"); + } } } @@ -575,11 +580,8 @@ protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, Pin { lock (_interruptHandlerLock) { - if (_eventHandlers.TryRemove(pinNumber, out _)) - { - _interruptPins.Remove(pinNumber); - _interruptLastInputValues.Remove(pinNumber); - } + _eventHandlers.TryRemove(pinNumber, out _); + _interruptPinsSubscribedEvents[pinNumber] = PinEventTypes.None; } }