LibGpiodDriver.cs 16.2 KB
Newer Older
1 2 3 4 5 6
// 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;
7
using System.Collections.Concurrent;
8
using System.Diagnostics;
L
Laurent Ellerbach 已提交
9
using System.Net.NetworkInformation;
10

11 12 13 14 15 16 17 18
namespace System.Device.Gpio.Drivers;

/// <summary>
/// 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".
/// </summary>
public class LibGpiodDriver : UnixDriver
19
{
20 21 22 23 24
    private static string s_consumerName = Process.GetCurrentProcess().ProcessName;
    private readonly object _pinNumberLock;
    private readonly ConcurrentDictionary<int, SafeLineHandle> _pinNumberToSafeLineHandle;
    private readonly ConcurrentDictionary<int, LibGpiodDriverEventHandler> _pinNumberToEventHandler;
    private readonly int _pinCount;
L
Laurent Ellerbach 已提交
25
    private readonly ConcurrentDictionary<int, PinValue> _pinValue;
26 27 28 29 30 31 32 33
    private SafeChipHandle _chip;

    /// <inheritdoc />
    protected internal override int PinCount => _pinCount;

    // for use the bias flags we need libgpiod version 1.5 or later
    private static bool IsLibgpiodVersion1_5orHigher()
    {
34
        IntPtr libgpiodVersionPtr = Interop.Libgpiod.gpiod_version_string();
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
        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)
    }

58
    /// <summary>
59
    /// Construct an instance
60
    /// </summary>
61 62
    /// <param name="gpioChip">Number of the gpio Chip. Default 0</param>
    public LibGpiodDriver(int gpioChip = 0)
63
    {
64
        if (Environment.OSVersion.Platform != PlatformID.Unix)
65
        {
66 67
            throw new PlatformNotSupportedException($"{GetType().Name} is only supported on Linux/Unix.");
        }
68

69 70 71
        try
        {
            _pinNumberLock = new object();
72
            _chip = Interop.Libgpiod.gpiod_chip_open_by_number(gpioChip);
73
            if (_chip == null)
74
            {
75
                throw ExceptionHelper.GetIOException(ExceptionResource.NoChipFound, Marshal.GetLastWin32Error());
76 77
            }

78
            _pinCount = Interop.Libgpiod.gpiod_chip_num_lines(_chip);
79 80
            _pinNumberToEventHandler = new ConcurrentDictionary<int, LibGpiodDriverEventHandler>();
            _pinNumberToSafeLineHandle = new ConcurrentDictionary<int, SafeLineHandle>();
L
Laurent Ellerbach 已提交
81
            _pinValue = new ConcurrentDictionary<int, PinValue>();
82
        }
83
        catch (DllNotFoundException)
84
        {
85
            throw ExceptionHelper.GetPlatformNotSupportedException(ExceptionResource.LibGpiodNotInstalled);
86
        }
87
    }
88

89 90 91 92
    /// <inheritdoc/>
    protected internal override void AddCallbackForPinValueChangedEvent(int pinNumber, PinEventTypes eventTypes, PinChangeEventHandler callback)
    {
        if ((eventTypes & PinEventTypes.Rising) != 0 || (eventTypes & PinEventTypes.Falling) != 0)
93
        {
94
            LibGpiodDriverEventHandler eventHandler = _pinNumberToEventHandler.GetOrAdd(pinNumber, PopulateEventHandler);
95

96
            if ((eventTypes & PinEventTypes.Rising) != 0)
97
            {
98
                eventHandler.ValueRising += callback;
99
            }
100 101

            if ((eventTypes & PinEventTypes.Falling) != 0)
102
            {
103
                eventHandler.ValueFalling += callback;
104
            }
105
        }
106
        else
107
        {
108 109 110
            throw ExceptionHelper.GetArgumentException(ExceptionResource.InvalidEventType);
        }
    }
111

112 113 114 115 116
    private LibGpiodDriverEventHandler PopulateEventHandler(int pinNumber)
    {
        lock (_pinNumberLock)
        {
            _pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle);
117

118
            if (pinHandle is null || (pinHandle is object && !Interop.Libgpiod.gpiod_line_is_free(pinHandle)))
119
            {
120
                pinHandle?.Dispose();
121
                pinHandle = Interop.Libgpiod.gpiod_chip_get_line(_chip, pinNumber);
122
                _pinNumberToSafeLineHandle[pinNumber] = pinHandle;
123
            }
124 125

            return new LibGpiodDriverEventHandler(pinNumber, pinHandle!);
126
        }
127
    }
128

129 130 131 132
    /// <inheritdoc/>
    protected internal override void ClosePin(int pinNumber)
    {
        lock (_pinNumberLock)
133
        {
134 135
            if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle) &&
                !IsListeningEvent(pinNumber))
136
            {
137 138 139
                pinHandle?.Dispose();
                // We know this works
                _pinNumberToSafeLineHandle.TryRemove(pinNumber, out _);
L
Laurent Ellerbach 已提交
140
                _pinValue.TryRemove(pinNumber, out _);
P
Patrick Grawehr 已提交
141
            }
142
        }
143 144 145 146 147 148 149 150 151 152
    }

    private bool IsListeningEvent(int pinNumber)
    {
        return _pinNumberToEventHandler.ContainsKey(pinNumber);
    }

    /// <inheritdoc/>
    protected internal override int ConvertPinNumberToLogicalNumberingScheme(int pinNumber) =>
        throw ExceptionHelper.GetPlatformNotSupportedException(ExceptionResource.ConvertPinNumberingSchemaError);
153

154 155 156 157
    /// <inheritdoc/>
    protected internal override PinMode GetPinMode(int pinNumber)
    {
        lock (_pinNumberLock)
158
        {
159
            if (!_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle))
160
            {
161 162
                throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.PinNotOpenedError,
                    pin: pinNumber);
163 164
            }

165
            return pinHandle.PinMode;
166
        }
167
    }
168

169 170 171 172 173 174 175
    /// <inheritdoc/>
    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,
    };
176

177 178 179 180
    /// <inheritdoc/>
    protected internal override void OpenPin(int pinNumber)
    {
        lock (_pinNumberLock)
181
        {
182
            if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out _))
183
            {
184
                return;
185
            }
186

187
            SafeLineHandle pinHandle = Interop.Libgpiod.gpiod_chip_get_line(_chip, pinNumber);
188
            if (pinHandle == null)
189
            {
190 191
                throw ExceptionHelper.GetIOException(ExceptionResource.OpenPinError, Marshal.GetLastWin32Error());
            }
P
Patrick Grawehr 已提交
192

193
            int mode = Interop.Libgpiod.gpiod_line_direction(pinHandle);
194 195 196 197 198 199 200 201
            if (mode == 1)
            {
                pinHandle.PinMode = PinMode.Input;
            }
            else if (mode == 2)
            {
                pinHandle.PinMode = PinMode.Output;
            }
202

203 204
            if (s_isLibgpiodVersion1_5orHigher && pinHandle.PinMode == PinMode.Input)
            {
205
                int bias = Interop.Libgpiod.gpiod_line_bias(pinHandle);
206
                if (bias == (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
207
                {
208
                    pinHandle.PinMode = PinMode.InputPullDown;
209 210
                }

211
                if (bias == (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
212
                {
213
                    pinHandle.PinMode = PinMode.InputPullUp;
214
                }
P
Patrick Grawehr 已提交
215
            }
216 217

            _pinNumberToSafeLineHandle.TryAdd(pinNumber, pinHandle);
L
Laurent Ellerbach 已提交
218 219 220
            // This is setting up a default value without reading the driver as it's the default behavior.
            // If the Setmode with an initial value is used, this is going to be corrected automatically
            _pinValue.TryAdd(pinNumber, PinValue.Low);
221
        }
222
    }
223

224 225 226 227
    /// <inheritdoc/>
    protected internal override PinValue Read(int pinNumber)
    {
        if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle))
228
        {
229
            int result = Interop.Libgpiod.gpiod_line_get_value(pinHandle);
230
            if (result == -1)
231
            {
232
                throw ExceptionHelper.GetIOException(ExceptionResource.ReadPinError, Marshal.GetLastWin32Error(), pinNumber);
233
            }
234

L
Laurent Ellerbach 已提交
235
            _pinValue[pinNumber] = result;
236
            return result;
237 238
        }

239 240 241
        throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.PinNotOpenedError, pin: pinNumber);
    }

L
Laurent Ellerbach 已提交
242 243 244
    /// <inheritdoc/>
    protected internal override void Toggle(int pinNumber) => Write(pinNumber, !_pinValue[pinNumber]);

245 246 247 248
    /// <inheritdoc/>
    protected internal override void RemoveCallbackForPinValueChangedEvent(int pinNumber, PinChangeEventHandler callback)
    {
        if (_pinNumberToEventHandler.TryGetValue(pinNumber, out LibGpiodDriverEventHandler? eventHandler))
249
        {
250 251 252
            eventHandler.ValueFalling -= callback;
            eventHandler.ValueRising -= callback;
            if (eventHandler.IsCallbackListEmpty())
253
            {
254 255
                _pinNumberToEventHandler.TryRemove(pinNumber, out eventHandler);
                eventHandler?.Dispose();
256
            }
257
        }
258
        else
259
        {
260
            throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.NotListeningForEventError);
261
        }
262
    }
263

264 265 266 267
    /// <inheritdoc/>
    protected internal override void SetPinMode(int pinNumber, PinMode mode)
    {
        if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle))
268
        {
269 270 271 272
            // 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
273
            {
274 275
                PinMode.Input => Interop.Libgpiod.gpiod_line_request_input(pinHandle, s_consumerName),
                PinMode.InputPullDown => Interop.Libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName,
276
                        (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN),
277
                PinMode.InputPullUp => Interop.Libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName,
278
                        (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP),
279
                PinMode.Output => Interop.Libgpiod.gpiod_line_request_output(pinHandle, s_consumerName, 0),
280 281
                _ => -1,
            };
282

283 284 285 286
            if (requestResult == -1)
            {
                throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, Marshal.GetLastWin32Error(),
                    pinNumber);
287
            }
288

289 290
            pinHandle.PinMode = mode;
            return;
291 292
        }

293 294 295 296 297 298 299
        throw new InvalidOperationException($"Pin {pinNumber} is not open");
    }

    /// <inheritdoc />
    protected internal override void SetPinMode(int pinNumber, PinMode mode, PinValue initialValue)
    {
        if (_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle))
300
        {
301 302 303 304
            // 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
305
            {
306 307
                PinMode.Input => Interop.Libgpiod.gpiod_line_request_input(pinHandle, s_consumerName),
                PinMode.InputPullDown => Interop.Libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName,
308
                    (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN),
309
                PinMode.InputPullUp => Interop.Libgpiod.gpiod_line_request_input_flags(pinHandle, s_consumerName,
310
                    (int)RequestFlag.GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP),
311
                PinMode.Output => Interop.Libgpiod.gpiod_line_request_output(pinHandle, s_consumerName, initialValue == PinValue.High ? 1 : 0),
312 313 314 315 316 317 318 319
                _ => -1,
            };

            if (requestResult == -1)
            {
                throw ExceptionHelper.GetIOException(ExceptionResource.SetPinModeError, Marshal.GetLastWin32Error(),
                    pinNumber);
            }
320

L
Laurent Ellerbach 已提交
321
            _pinValue[pinNumber] = initialValue;
322 323 324
            pinHandle.PinMode = mode;
            return;
        }
325

326 327
        throw new InvalidOperationException($"Pin {pinNumber} is not open");
    }
328

329 330 331 332 333 334
    /// <inheritdoc/>
    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);
335

336 337 338 339
            if ((eventTypes & PinEventTypes.Rising) != 0)
            {
                eventHandler.ValueRising += Callback;
            }
340

341 342 343
            if ((eventTypes & PinEventTypes.Falling) != 0)
            {
                eventHandler.ValueFalling += Callback;
344
            }
345 346 347 348

            bool eventOccurred = false;
            PinEventTypes typeOfEventOccured = PinEventTypes.None;
            void Callback(object o, PinValueChangedEventArgs e)
349
            {
350 351
                eventOccurred = true;
                typeOfEventOccured = e.ChangeType;
352
            }
353 354 355 356 357 358 359 360 361 362 363 364 365

            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);
366
        }
367
    }
368

369 370 371
    private void WaitForEventResult(CancellationToken sourceToken, CancellationToken parentToken, ref bool eventOccurred)
    {
        while (!(sourceToken.IsCancellationRequested || parentToken.IsCancellationRequested || eventOccurred))
372
        {
373
            Thread.Sleep(1);
374
        }
375
    }
376

377 378 379 380
    /// <inheritdoc/>
    protected internal override void Write(int pinNumber, PinValue value)
    {
        if (!_pinNumberToSafeLineHandle.TryGetValue(pinNumber, out SafeLineHandle? pinHandle))
381
        {
382 383 384 385
            throw ExceptionHelper.GetInvalidOperationException(ExceptionResource.PinNotOpenedError,
                pin: pinNumber);
        }

386
        Interop.Libgpiod.gpiod_line_set_value(pinHandle, (value == PinValue.High) ? 1 : 0);
L
Laurent Ellerbach 已提交
387
        _pinValue[pinNumber] = value;
388 389
    }

390 391 392 393
    /// <inheritdoc />
    public override ComponentInformation QueryComponentInformation()
    {
        var self = new ComponentInformation(this, "LibGpiodDriver");
394
        IntPtr libgpiodVersionPtr = Interop.Libgpiod.gpiod_version_string();
395 396 397 398 399
        string libgpiodVersion = Marshal.PtrToStringAnsi(libgpiodVersionPtr) ?? string.Empty;
        self.Properties["LibGpiodVersion"] = libgpiodVersion;
        return self;
    }

400 401 402 403 404 405
    /// <inheritdoc/>
    protected override void Dispose(bool disposing)
    {
        if (_pinNumberToEventHandler != null)
        {
            foreach (KeyValuePair<int, LibGpiodDriverEventHandler> kv in _pinNumberToEventHandler)
406
            {
407 408
                LibGpiodDriverEventHandler eventHandler = kv.Value;
                eventHandler.Dispose();
409 410
            }

411
            _pinNumberToEventHandler.Clear();
412 413
        }

414
        if (_pinNumberToSafeLineHandle != null)
415
        {
416
            foreach (int pin in _pinNumberToSafeLineHandle.Keys)
417
            {
418
                if (_pinNumberToSafeLineHandle.TryGetValue(pin, out SafeLineHandle? pinHandle))
419
                {
420
                    pinHandle?.Dispose();
421 422 423
                }
            }

424
            _pinNumberToSafeLineHandle.Clear();
L
Laurent Ellerbach 已提交
425
            _pinValue.Clear();
426
        }
427

428 429
        _chip?.Dispose();
        _chip = null!;
430

431
        base.Dispose(disposing);
432 433
    }
}