Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 51 additions & 33 deletions src/devices/Tca955x/Tca955x.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
private readonly int _interrupt;
private readonly Dictionary<int, PinValue> _pinValues = new Dictionary<int, PinValue>();
private readonly ConcurrentDictionary<int, PinChangeEventHandler> _eventHandlers = new ConcurrentDictionary<int, PinChangeEventHandler>();
private readonly Dictionary<int, PinEventTypes> _interruptPinsSubscribedEvents = new Dictionary<int, PinEventTypes>();
private readonly ConcurrentDictionary<int, PinEventTypes> _interruptPinsSubscribedEvents = new ConcurrentDictionary<int, PinEventTypes>();
private readonly ConcurrentDictionary<int, PinValue> _interruptLastInputValues = new ConcurrentDictionary<int, PinValue>();

private GpioController? _controller;
Expand All @@ -29,6 +29,10 @@

private I2cDevice _busDevice;

// Lock protects:
// 1. I2C bus access - I2C operations must be atomic and sequential
// 2. _pinValues dictionary - tightly coupled with I2C read/write operations
// 3. Interrupt task coordination - _interruptProcessingTask and _interruptPending state
private object _interruptHandlerLock = new object();

// This task processes the i2c reading of the io expander in a background task to
Expand Down Expand Up @@ -89,13 +93,13 @@
}

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);
{
// 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.TryAdd(i, PinEventTypes.None);
_interruptLastInputValues.TryAdd(i, PinValue.Low);
}

_shouldDispose = shouldDispose || gpioController is null;
Expand Down Expand Up @@ -182,6 +186,7 @@
/// <param name="mode">The mode to be set.</param>
protected override void SetPinMode(int pinNumber, PinMode mode)
{
// Lock required: I2C bus operations must be atomic and sequential
lock (_interruptHandlerLock)
{
if (mode != PinMode.Input && mode != PinMode.Output && mode != PinMode.InputPullUp)
Expand Down Expand Up @@ -250,6 +255,7 @@
protected override PinValue Read(int pinNumber)
{
PinValue pinValue;
// Lock required: I2C bus operations must be atomic, and _pinValues is updated during read
lock (_interruptHandlerLock)
{
ValidatePin(pinNumber);
Expand All @@ -272,6 +278,7 @@
/// </summary>
protected override void Read(Span<PinValuePair> pinValuePairs)
{
// Lock required: I2C bus operations must be atomic, and _pinValues is updated during read
lock (_interruptHandlerLock)
{
byte? lowReg = null;
Expand Down Expand Up @@ -318,6 +325,7 @@
/// <param name="value">The value to be written.</param>
protected override void Write(int pinNumber, PinValue value)
{
// Lock required: I2C bus operations must be atomic, and _pinValues is updated during write
lock (_interruptHandlerLock)
{
ValidatePin(pinNumber);
Expand All @@ -338,6 +346,7 @@
bool lowChanged = false;
bool highChanged = false;

// Lock required: I2C bus operations must be atomic, and _pinValues is updated during write
lock (_interruptHandlerLock)
{
(uint mask, uint newBits) = new PinVector32(pinValuePairs);
Expand Down Expand Up @@ -445,8 +454,11 @@
// i2c and detect a change in the returned input register data, not to mention run the
// event handlers that the consumer of the library has signed up, we are likely
// to miss edges while we are doing that anyway. Dropping interrupts in this
// case is the best we can do and prevents flooding the consumer with events
// case is the best we can do and prevents flooding the consumer with events
// that could queue up in the INT gpio pin driver.

Check failure on line 459 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L459

src/devices/Tca955x/Tca955x.cs(459,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 459 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L459

src/devices/Tca955x/Tca955x.cs(459,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 459 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L459

src/devices/Tca955x/Tca955x.cs(459,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 459 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L459

src/devices/Tca955x/Tca955x.cs(459,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 459 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build MacOS Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L459

src/devices/Tca955x/Tca955x.cs(459,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 459 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build MacOS Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L459

src/devices/Tca955x/Tca955x.cs(459,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)
// Lock required for task coordination: atomically check/start _interruptProcessingTask
// or set _interruptPending flag to ensure proper interrupt queueing
lock (_interruptHandlerLock)
{
if (_interruptProcessingTask == null)
Expand All @@ -461,10 +473,11 @@
}

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<int, PinEventTypes>(_interruptPinsSubscribedEvents);
{
// Take a snapshot of the current interrupt pin configuration and last known input values
// so we can safely process them in a background task. ConcurrentDictionary enumeration
// is thread-safe and provides a consistent snapshot.
var interruptPinsSnapshot = new Dictionary<int, PinEventTypes>(_interruptPinsSubscribedEvents);
var interruptLastInputValuesSnapshot = new Dictionary<int, PinValue>(_interruptLastInputValues);

Task processingTask = new Task(() =>
Expand Down Expand Up @@ -511,14 +524,16 @@

processingTask.ContinueWith(t =>
{
lock (_interruptHandlerLock)
{
_interruptProcessingTask = null;
if (_interruptPending)
{
_interruptPending = false;
_interruptProcessingTask = ProcessInterruptInTask();
}
// Lock required for task coordination: atomically check/update _interruptProcessingTask
// and _interruptPending to ensure only one processing task runs at a time
lock (_interruptHandlerLock)
{
_interruptProcessingTask = null;
if (_interruptPending)
{
_interruptPending = false;
_interruptProcessingTask = ProcessInterruptInTask();
}
}
});

Expand Down Expand Up @@ -563,26 +578,29 @@
throw new InvalidOperationException("No interrupt pin configured");
}

// Update subscription state using thread-safe ConcurrentDictionary operations
_interruptPinsSubscribedEvents[pinNumber] = eventType;

Check failure on line 583 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L583

src/devices/Tca955x/Tca955x.cs(583,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 583 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L583

src/devices/Tca955x/Tca955x.cs(583,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 583 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L583

src/devices/Tca955x/Tca955x.cs(583,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 583 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L583

src/devices/Tca955x/Tca955x.cs(583,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 583 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build MacOS Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L583

src/devices/Tca955x/Tca955x.cs(583,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 583 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build MacOS Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L583

src/devices/Tca955x/Tca955x.cs(583,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)
// Read current value needs lock because it accesses I2C bus
PinValue currentValue;
lock (_interruptHandlerLock)
{
_interruptPinsSubscribedEvents[pinNumber] = eventType;
var currentValue = Read(pinNumber);
_interruptLastInputValues.TryUpdate(pinNumber, currentValue, !currentValue);
if (!_eventHandlers.TryAdd(pinNumber, callback))
{
throw new InvalidOperationException($"An event handler is already registered for pin {pinNumber}");
}
currentValue = Read(pinNumber);
}

Check failure on line 590 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L590

src/devices/Tca955x/Tca955x.cs(590,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 590 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build Linux Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L590

src/devices/Tca955x/Tca955x.cs(590,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 590 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build MacOS Build Build_Release)

src/devices/Tca955x/Tca955x.cs#L590

src/devices/Tca955x/Tca955x.cs(590,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)

Check failure on line 590 in src/devices/Tca955x/Tca955x.cs

View check run for this annotation

Azure Pipelines / dotnet.iot (Build MacOS Build Build_Debug)

src/devices/Tca955x/Tca955x.cs#L590

src/devices/Tca955x/Tca955x.cs(590,1): error SA1028: (NETCORE_ENGINEERING_TELEMETRY=Build) Code should not contain trailing whitespace (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1028.md)
_interruptLastInputValues.TryUpdate(pinNumber, currentValue, !currentValue);
if (!_eventHandlers.TryAdd(pinNumber, callback))
{
throw new InvalidOperationException($"An event handler is already registered for pin {pinNumber}");
}
}

/// <inheritdoc/>
protected override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback)
{
lock (_interruptHandlerLock)
{
_eventHandlers.TryRemove(pinNumber, out _);
_interruptPinsSubscribedEvents[pinNumber] = PinEventTypes.None;
}
// Use thread-safe ConcurrentDictionary operations - no lock needed
_eventHandlers.TryRemove(pinNumber, out _);
_interruptPinsSubscribedEvents[pinNumber] = PinEventTypes.None;
}

/// <inheritdoc/>
Expand Down
Loading