diff --git a/src/System.Device.Gpio.Tests/LibGpiodDriverTests.cs b/src/System.Device.Gpio.Tests/LibGpiodDriverTests.cs deleted file mode 100644 index f4c4eafe89..0000000000 --- a/src/System.Device.Gpio.Tests/LibGpiodDriverTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Device.Gpio.Drivers; -using System.Threading; -using Xunit; -using Xunit.Abstractions; - -namespace System.Device.Gpio.Tests -{ - [Trait("feature", "gpio")] - [Trait("feature", "gpio-libgpiod")] - [Trait("SkipOnTestRun", "Windows_NT")] - public class LibGpiodDriverTests : GpioControllerTestBase - { - public LibGpiodDriverTests(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - protected override GpioDriver GetTestDriver() => new LibGpiodDriver(); - - protected override PinNumberingScheme GetTestNumberingScheme() => PinNumberingScheme.Logical; - - [Fact] - public void SetPinModeSetsDefaultValue() - { - using (GpioController controller = new GpioController(GetTestNumberingScheme(), GetTestDriver())) - { - int testPin = OutputPin; - // Set value to low prior to test, so that we have a defined start situation - controller.OpenPin(testPin, PinMode.Output); - controller.Write(testPin, PinValue.Low); - controller.ClosePin(testPin); - // For this test, we use the input pin as an external pull-up - controller.OpenPin(InputPin, PinMode.Output); - controller.Write(InputPin, PinValue.High); - Thread.Sleep(2); - - controller.OpenPin(testPin, PinMode.Input); - Thread.Sleep(50); - // It's not possible to change the direction while listening to events (causes an error). Therefore the real behavior of the driver - // can only be tested with a scope (or if we had a third pin connected in the lab hardware) - - // We do another test here and make sure the - // pin is really high now - controller.Write(testPin, PinValue.High); - controller.SetPinMode(testPin, PinMode.Output); - controller.SetPinMode(InputPin, PinMode.Input); - - Assert.True(controller.Read(InputPin) == PinValue.High); - - controller.ClosePin(OutputPin); - controller.ClosePin(InputPin); - } - } - - [Fact] - public void UnregisterPinValueChangedShallNotThrow() - { - using var gc = new GpioController(GetTestNumberingScheme(), GetTestDriver()); - gc.OpenPin(InputPin, PinMode.Input); - - static void PinChanged(object sender, PinValueChangedEventArgs args) - { - } - - for (var i = 0; i < 1000; i++) - { - gc.RegisterCallbackForPinValueChangedEvent(InputPin, PinEventTypes.Rising | PinEventTypes.Falling, PinChanged); - gc.UnregisterCallbackForPinValueChangedEvent(InputPin, PinChanged); - } - } - } - - /// - /// Ensure leaking instances of the driver doesn't cause a segfault - /// Regression test for https://github.com/dotnet/iot/issues/1849 - /// - [Fact] - public void LeakingDriverDoesNotCrash() - { - GpioController controller1 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); - controller1.OpenPin(10, PinMode.Output); - GpioController controller2 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); - controller2.OpenPin(11, PinMode.Output); - GpioController controller3 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); - controller3.OpenPin(12, PinMode.Output); - GpioController controller4 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); - controller4.OpenPin(13, PinMode.Output); - GpioController controller5 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); - controller5.OpenPin(14, PinMode.Output); - - for (int i = 0; i < 10; i++) - { - GC.Collect(); - GpioController controller6 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); - controller6.OpenPin(15, PinMode.Output); - controller6.ClosePin(15); - controller6.Dispose(); - GC.Collect(); - Thread.Sleep(20); - } - - GC.WaitForPendingFinalizers(); - } -} diff --git a/src/System.Device.Gpio.Tests/LibGpiodV1DriverTests.cs b/src/System.Device.Gpio.Tests/LibGpiodV1DriverTests.cs index 0cd8f4332f..a0d69c0076 100644 --- a/src/System.Device.Gpio.Tests/LibGpiodV1DriverTests.cs +++ b/src/System.Device.Gpio.Tests/LibGpiodV1DriverTests.cs @@ -72,4 +72,36 @@ static void PinChanged(object sender, PinValueChangedEventArgs args) gc.UnregisterCallbackForPinValueChangedEvent(InputPin, PinChanged); } } -} + + /// + /// Ensure leaking instances of the driver doesn't cause a segfault + /// Regression test for https://github.com/dotnet/iot/issues/1849 + /// + [Fact] + public void LeakingDriverDoesNotCrash() + { + GpioController controller1 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); + controller1.OpenPin(10, PinMode.Output); + GpioController controller2 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); + controller2.OpenPin(11, PinMode.Output); + GpioController controller3 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); + controller3.OpenPin(12, PinMode.Output); + GpioController controller4 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); + controller4.OpenPin(13, PinMode.Output); + GpioController controller5 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); + controller5.OpenPin(14, PinMode.Output); + + for (int i = 0; i < 10; i++) + { + GC.Collect(); + GpioController controller6 = new GpioController(PinNumberingScheme.Logical, new LibGpiodDriver()); + controller6.OpenPin(15, PinMode.Output); + controller6.ClosePin(15); + controller6.Dispose(); + GC.Collect(); + Thread.Sleep(20); + } + + GC.WaitForPendingFinalizers(); + } +} \ No newline at end of file diff --git a/src/System.Device.Gpio/Interop/Unix/SafeLineHandle.cs b/src/System.Device.Gpio/Interop/Unix/SafeLineHandle.cs deleted file mode 100644 index ef5333338d..0000000000 --- a/src/System.Device.Gpio/Interop/Unix/SafeLineHandle.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; - -namespace System.Device.Gpio; - -/// -/// Pointer to a pin (Not a real SafeLineHandle, because we need to align its finalization with the owning object) -/// -internal sealed class SafeLineHandle : IDisposable -{ - private IntPtr _handle; - public SafeLineHandle() - { - _handle = IntPtr.Zero; - } - - public SafeLineHandle(IntPtr handle) - { - _handle = handle; - PinMode = PinMode.Input; - } - - public PinMode PinMode { get; set; } - - public IntPtr Handle - { - get - { - return _handle; - } - set - { - _handle = value; - } - } - - /// - /// Release the lock on the line handle. - /// - public void ReleaseLock() - { - // Contrary to intuition, this does not invalidate the handle (see comment on declaration) - Interop.libgpiod.gpiod_line_release(_handle); - } - - public bool IsInvalid => _handle == IntPtr.Zero || _handle == Interop.libgpiod.InvalidHandleValue; - - public void Dispose() - { - if (_handle != IntPtr.Zero) - { - Interop.libgpiod.gpiod_line_release(_handle); - _handle = IntPtr.Zero; - } - } - - public static implicit operator IntPtr(SafeLineHandle self) - { - return self.Handle; - } -} diff --git a/src/System.Device.Gpio/Interop/Unix/libgpiod/V1/SafeLineHandle.cs b/src/System.Device.Gpio/Interop/Unix/libgpiod/V1/SafeLineHandle.cs index be7172fa31..5e71c2eeec 100644 --- a/src/System.Device.Gpio/Interop/Unix/libgpiod/V1/SafeLineHandle.cs +++ b/src/System.Device.Gpio/Interop/Unix/libgpiod/V1/SafeLineHandle.cs @@ -7,31 +7,53 @@ namespace System.Device.Gpio.Libgpiod.V1; /// -/// Pointer to a pin. +/// Pointer to a pin (Not a real SafeLineHandle, because we need to align its finalization with the owning object) /// -internal class SafeLineHandle : SafeHandle +internal sealed class SafeLineHandle : IDisposable { - public PinMode PinMode { get; set; } - + private IntPtr _handle; public SafeLineHandle() - : base(IntPtr.Zero, true) { + _handle = IntPtr.Zero; } - protected override bool ReleaseHandle() + public SafeLineHandle(IntPtr handle) { - // Contrary to intuition, this does not invalidate the handle (see comment on declaration) - LibgpiodV1.gpiod_line_release(handle); - return true; + _handle = handle; + PinMode = PinMode.Input; + } + + public PinMode PinMode { get; set; } + + public IntPtr Handle + { + get + { + return _handle; + } + set + { + _handle = value; + } } /// - /// Release the lock on the line handle. + /// Release the lock on the line handle. /// public void ReleaseLock() { - ReleaseHandle(); + // Contrary to intuition, this does not invalidate the handle (see comment on declaration) + Interop.libgpiod.gpiod_line_release(_handle); } - public override bool IsInvalid => handle == IntPtr.Zero || handle == LibgpiodV1.InvalidHandleValue; + public bool IsInvalid => _handle == IntPtr.Zero || _handle == Interop.libgpiod.InvalidHandleValue; + + public void Dispose() + { + if (_handle != IntPtr.Zero) + { + Interop.libgpiod.gpiod_line_release(_handle); + _handle = IntPtr.Zero; + } + } } diff --git a/src/System.Device.Gpio/System/Device/Gpio/Drivers/LibGpiodDriver.cs b/src/System.Device.Gpio/System/Device/Gpio/Drivers/LibGpiodDriver.cs deleted file mode 100644 index 003f21a6d4..0000000000 --- a/src/System.Device.Gpio/System/Device/Gpio/Drivers/LibGpiodDriver.cs +++ /dev/null @@ -1,410 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Threading; -using System.Runtime.InteropServices; -using System.Collections.Concurrent; -using System.Diagnostics; - -namespace System.Device.Gpio.Drivers -{ - /// - /// This driver uses the Libgpiod library to get user-level access to the gpio ports. - /// It superseeds the SysFsDriver, but requires that libgpiod is installed. To do so, run - /// "sudo apt install -y libgpiod-dev". - /// - public class LibGpiodDriver : UnixDriver - { - private static string s_consumerName = Process.GetCurrentProcess().ProcessName; - private readonly object _pinNumberLock; - private readonly ConcurrentDictionary _pinNumberToSafeLineHandle; - private readonly ConcurrentDictionary _pinNumberToEventHandler; - private readonly int _pinCount; - private SafeChipHandle _chip; - - /// - protected internal override int PinCount => _pinCount; - - // for use the bias flags we need libgpiod version 1.5 or later - private static bool IsLibgpiodVersion1_5orHigher() - { - IntPtr libgpiodVersionPtr = Interop.libgpiod.gpiod_version_string(); - string? libgpiodVersionMatch = Marshal.PtrToStringAnsi(libgpiodVersionPtr); - - if (libgpiodVersionMatch is object) - { - Version libgpiodVersion = new Version(libgpiodVersionMatch); - return (libgpiodVersion.Major >= 1 && libgpiodVersion.Minor >= 5); - } - - return false; - } - - private static bool s_isLibgpiodVersion1_5orHigher = IsLibgpiodVersion1_5orHigher(); - - private enum RequestFlag : ulong - { - GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN = (1UL << 0), - GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE = (1UL << 1), - GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW = (1UL << 2), - GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE = (1UL << 3), - GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN = (1UL << 4), - GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP = (1UL << 5) - } - - /// - /// Construct an instance - /// - /// Number of the gpio Chip. Default 0 - public LibGpiodDriver(int gpioChip = 0) - { - if (Environment.OSVersion.Platform != PlatformID.Unix) - { - throw new PlatformNotSupportedException($"{GetType().Name} is only supported on Linux/Unix."); - } - - try - { - _pinNumberLock = new object(); - _chip = Interop.libgpiod.gpiod_chip_open_by_number(gpioChip); - if (_chip == null) - { - throw ExceptionHelper.GetIOException(ExceptionResource.NoChipFound, Marshal.GetLastWin32Error()); - } - - _pinCount = Interop.libgpiod.gpiod_chip_num_lines(_chip); - _pinNumberToEventHandler = new ConcurrentDictionary(); - _pinNumberToSafeLineHandle = new ConcurrentDictionary(); - } - catch (DllNotFoundException) - { - throw ExceptionHelper.GetPlatformNotSupportedException(ExceptionResource.LibGpiodNotInstalled); - } - } - - /// - protected internal override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback) - { - if ((eventTypes & PinEventTypes.Rising) != 0 || (eventTypes & PinEventTypes.Falling) != 0) - { - LibGpiodDriverEventHandler eventHandler = _pinNumberToEventHandler.GetOrAdd(pinNumber, PopulateEventHandler); - - if ((eventTypes & PinEventTypes.Rising) != 0) - { - eventHandler.ValueRising += callback; - } - - if ((eventTypes & PinEventTypes.Falling) != 0) - { - eventHandler.ValueFalling += callback; - } - } - else - { - throw ExceptionHelper.GetArgumentException(ExceptionResource.InvalidEventType); - } - } - - private LibGpiodDriverEventHandler PopulateEventHandler(int pinNumber) - { - lock (_pinNumberLock) - { - _pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle); - - if (pinHandle is null || (pinHandle is object && !Interop.libgpiod.gpiod_line_is_free(pinHandle))) - { - pinHandle?.Dispose(); - pinHandle = new SafeLineHandle(Interop.libgpiod.gpiod_chip_get_line(_chip, pinNumber)); - _pinNumberToSafeLineHandle[pinNumber] = pinHandle; - } - - return new LibGpiodDriverEventHandler(pinNumber, pinHandle!); - } - } - - /// - protected internal override void ClosePin(int pinNumber) - { - lock (_pinNumberLock) - { - if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle) && - !IsListeningEvent(pinNumber)) - { - pinHandle?.Dispose(); - // We know this works - _pinNumberToSafeLineHandle.TryRemove(pinNumber, out _); - } - } - } - - private bool IsListeningEvent(int pinNumber) - { - return _pinNumberToEventHandler.ContainsKey(pinNumber); - } - - /// - protected internal override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) => - throw ExceptionHelper.GetPlatformNotSupportedException(ExceptionResource.ConvertPinNumberingSchemaError); - - /// - protected internal override PinMode GetPinMode(int pinNumber) - { - lock (_pinNumberLock) - { - if (!_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle)) - { - throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.PinNotOpenedError, - pin: pinNumber); - } - - return pinHandle.PinMode; - } - } - - /// - protected internal override bool IsPinModeSupported(int pinNumber, PinMode mode) => mode switch - { - PinMode.Input or PinMode.Output => true, - PinMode.InputPullDown or PinMode.InputPullUp => s_isLibgpiodVersion1_5orHigher, - _ => false, - }; - - /// - protected internal override void OpenPin(int pinNumber) - { - lock (_pinNumberLock) - { - if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out _)) - { - return; - } - - SafeLineHandle pinHandle = new SafeLineHandle(Interop.libgpiod.gpiod_chip_get_line(_chip, pinNumber)); - if (pinHandle == null) - { - throw ExceptionHelper.GetIOException(ExceptionResource.OpenPinError, Marshal.GetLastWin32Error()); - } - - int mode = Interop.libgpiod.gpiod_line_direction(pinHandle); - if (mode == 1) - { - pinHandle.PinMode = PinMode.Input; - } - else if (mode == 2) - { - pinHandle.PinMode = PinMode.Output; - } - - if (s_isLibgpiodVersion1_5orHigher && pinHandle.PinMode == PinMode.Input) - { - int bias = Interop.libgpiod.gpiod_line_bias(pinHandle); - if (bias == (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN) - { - pinHandle.PinMode = PinMode.InputPullDown; - } - - if (bias == (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP) - { - pinHandle.PinMode = PinMode.InputPullUp; - } - } - - _pinNumberToSafeLineHandle.TryAdd(pinNumber, pinHandle); - } - } - - /// - protected internal override PinValue Read(int pinNumber) - { - if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle)) - { - int result = Interop.libgpiod.gpiod_line_get_value(pinHandle); - if (result == -1) - { - throw ExceptionHelper.GetIOException(ExceptionResource.ReadPinError, Marshal.GetLastWin32Error(), pinNumber); - } - - return result; - } - - throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.PinNotOpenedError, pin: pinNumber); - } - - /// - protected internal override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback) - { - if (_pinNumberToEventHandler.TryGetValue(pinNumber, out LibGpiodDriverEventHandler? eventHandler)) - { - eventHandler.ValueFalling -= callback; - eventHandler.ValueRising -= callback; - if (eventHandler.IsCallbackListEmpty()) - { - _pinNumberToEventHandler.TryRemove(pinNumber, out eventHandler); - eventHandler?.Dispose(); - } - } - else - { - throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.NotListeningForEventError); - } - } - - /// - protected internal override void SetPinMode(int pinNumber, PinMode mode) - { - if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle)) - { - // This call does not release the handle. It only releases the lock on the handle. Without this, changing the direction of a line is not possible. - // Line handles cannot be freed and are cached until the chip is closed. - pinHandle.ReleaseLock(); - int requestResult = mode switch - { - PinMode.Input => Interop.libgpiod.gpiod_line_request_input(pinHandle, s_consumerName), - PinMode.InputPullDown => Interop.libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName, - (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN), - PinMode.InputPullUp => Interop.libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName, - (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP), - PinMode.Output => Interop.libgpiod.gpiod_line_request_output(pinHandle, s_consumerName, 0), - _ => -1, - }; - - if (requestResult == -1) - { - throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, Marshal.GetLastWin32Error(), - pinNumber); - } - - pinHandle.PinMode = mode; - return; - } - - throw new InvalidOperationException($"Pin {pinNumber} is not open"); - } - - /// - protected internal override void SetPinMode(int pinNumber, PinMode mode, PinValue initialValue) - { - if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle)) - { - // This call does not release the handle. It only releases the lock on the handle. Without this, changing the direction of a line is not possible. - // Line handles cannot be freed and are cached until the chip is closed. - pinHandle.ReleaseLock(); - int requestResult = mode switch - { - PinMode.Input => Interop.libgpiod.gpiod_line_request_input(pinHandle, s_consumerName), - PinMode.InputPullDown => Interop.libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName, - (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN), - PinMode.InputPullUp => Interop.libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName, - (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP), - PinMode.Output => Interop.libgpiod.gpiod_line_request_output(pinHandle, s_consumerName, initialValue == PinValue.High ? 1 : 0), - _ => -1, - }; - - if (requestResult == -1) - { - throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, Marshal.GetLastWin32Error(), - pinNumber); - } - - pinHandle.PinMode = mode; - return; - } - - throw new InvalidOperationException($"Pin {pinNumber} is not open"); - } - - /// - protected internal override WaitForEventResult WaitForEvent(int pinNumber, PinEventTypes eventTypes, CancellationToken cancellationToken) - { - if ((eventTypes & PinEventTypes.Rising) != 0 || (eventTypes & PinEventTypes.Falling) != 0) - { - LibGpiodDriverEventHandler eventHandler = _pinNumberToEventHandler.GetOrAdd(pinNumber, PopulateEventHandler); - - if ((eventTypes & PinEventTypes.Rising) != 0) - { - eventHandler.ValueRising += Callback; - } - - if ((eventTypes & PinEventTypes.Falling) != 0) - { - eventHandler.ValueFalling += Callback; - } - - bool eventOccurred = false; - PinEventTypes typeOfEventOccured = PinEventTypes.None; - void Callback(object o, PinValueChangedEventArgs e) - { - eventOccurred = true; - typeOfEventOccured = e.ChangeType; - } - - WaitForEventResult(cancellationToken, eventHandler.CancellationToken, ref eventOccurred); - RemoveCallbackForPinValueChangedEvent(pinNumber, Callback); - - return new WaitForEventResult - { - TimedOut = !eventOccurred, - EventTypes = eventOccurred ? typeOfEventOccured : PinEventTypes.None, - }; - } - else - { - throw ExceptionHelper.GetArgumentException(ExceptionResource.InvalidEventType); - } - } - - private void WaitForEventResult(CancellationToken sourceToken, CancellationToken parentToken, ref bool eventOccurred) - { - while (!(sourceToken.IsCancellationRequested || parentToken.IsCancellationRequested || eventOccurred)) - { - Thread.Sleep(1); - } - } - - /// - protected internal override void Write(int pinNumber, PinValue value) - { - if (!_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle)) - { - throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.PinNotOpenedError, - pin: pinNumber); - } - - Interop.libgpiod.gpiod_line_set_value(pinHandle, (value == PinValue.High) ? 1 : 0); - } - - /// - protected override void Dispose(bool disposing) - { - if (_pinNumberToEventHandler != null) - { - foreach (KeyValuePair kv in _pinNumberToEventHandler) - { - LibGpiodDriverEventHandler eventHandler = kv.Value; - eventHandler.Dispose(); - } - - _pinNumberToEventHandler.Clear(); - } - - if (_pinNumberToSafeLineHandle != null) - { - foreach (int pin in _pinNumberToSafeLineHandle.Keys) - { - if (_pinNumberToSafeLineHandle.TryGetValue(pin, out SafeLineHandle? pinHandle)) - { - pinHandle?.Dispose(); - } - } - - _pinNumberToSafeLineHandle.Clear(); - } - - _chip?.Dispose(); - _chip = null!; - - base.Dispose(disposing); - } - } -}