Skip to content

Commit

Permalink
Add libgpiodv2 support (#2184)
Browse files Browse the repository at this point in the history
* libgpiodv2: introduce namespace for libgpiod v1 for differentation to v2

* libgpiodv2: add binding and proxy classes for libgpiod v2

* libgpiodv2: add libgpiodv2 driver and event observer

* libgpiodv2: add libgpiod driver factory to select driver based on installed libgpiod version

* libgpiodv2: add tests

* libgpiodv2: add documentation on how to use libgpiodv2 driver

* libgpiodv2: documentation improvement

* libgpiodv2: name namespaces consistent

* libgpiodv2: keep LibGpiodDriver name to not break client code, make namespaces shorter, improve libgpiod version detection

* libgpiodv2: do not block operations when waiting for edge events

* libgpiodv2: introduce factory for libgpiod proxy objects

* libgpiodv2: locking reads is not necessary

* libgpiodv2: add GetAvailableVersions

* libgpiodv2: let GpioException derive from IOException

* libgpiodv2: apply disposable pattern to all proxies, assign enum members explicitly

* libgpiodv2: free all filedescriptors

* libgpiodv2: make LibGpiodDriverFactory a singleton and use Lazy

* libgpiodv2: use alternative init in LibGpiodDriverFactory

* Add required suppressions

They're rather strange and look like false positives

* Disable LibGpiod2 for Helix until runners are updated

* libgpiodv2: recursively search for libgpiod

* Fix exclusions

* libgpiodv2: improve XML doc

---------

Co-authored-by: Patrick Grawehr <[email protected]>
  • Loading branch information
huesla and pgrawehr authored Jan 27, 2024
1 parent 9383fd5 commit 3fb06eb
Show file tree
Hide file tree
Showing 67 changed files with 4,724 additions and 69 deletions.
8 changes: 8 additions & 0 deletions Documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ While contributing, you should read the [coding guidelines section](https://gith

* [PINE A64-LTS](https://www.pine64.org/?page_id=46823)

## Platforms

### Linux

#### GPIO

* [Use libgpiod to control GPIOs on Linux](./gpio-linux-libgpiod.md)

## Maker Resources

### Prototyping
Expand Down
109 changes: 109 additions & 0 deletions Documentation/gpio-linux-libgpiod.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Using libgpiod to control GPIOs

## Quick usage: blink LED example

This example targets a RaspberryPi 3/4, see comments for more information:

```c#
// side note: on the Raspberry Pi the GPIO chip line offsets are the same numbers as the usual BCM GPIO numbering, which is convenient
const int ledGpio = 15;

// on the Pi3,4 you most likely want 0, on the Pi5 number 4, see 'gpioinfo' tool
const int chipNumber = 0;
// 'using' will dispose the controller when it falls out of scope, which will un-claim lines
// alternatively be more explicit: 'new GpioController(chipNumber, new LibGpiodDriver())'
using var gpioController = new GpioController(chipNumber);

gpioController.OpenPin(ledGpio);

for (int i = 0; i < 5; i++)
{
controller.Write(ledGpio, PinValue.High);
await Task.Delay(1000);
controller.Write(ledGpio, PinValue.Low);
await Task.Delay(1000);
}
```

## libgpiod versions

**Note**: The documented version of libgpiod is not the same as the library so name, see the following table:

| Documented version | Library so name | Comment |
| ------------------ | ----------------- | -------------------------------------------------------- |
| 1.0.2 | libgpiod.so.1.0.2 | last .so.1.x library |
| 1.1 | libgpiod.so.2.0.0 | first occurrence of inconsistency, first .so.2.x library |
| 1.6.4 | libgpiod.so.2.2.2 | last .so.2.x library |
| 2.0 | libgpiod.so.3.0.0 | first .so.3.x library |
| 2.1 | libgpiod.so.3.1.0 | latest .so.3.x library (currently) |

## libgpiod version support

Currently (12/23) dotnet-iot supports v0, v1 and v2 of libgpiod.

The following table shows which driver supports which library version

| LibGpiodDriverVersion | Libgpiod version (documented) |
| --------------------- | ----------------------------- |
| V1 | 0.x to 1.x |
| V2 | 2.x |

## Choose LibGpiodDriver Version

If you want to explicitly select the version of the libgpiod driver, to target a specific library version, there are following options:

1. constructor of LibGpiodDriver:

```c#
new LibGpiodDriver(chipNumber, LibGpiodDriverVersion.V1)
```

2. Environment variable:

```shell
export DOTNET_IOT_LIBGPIOD_DRIVER_VERSION=V1 // or V2...
```

When not explicitly specified, dotnet iot automatically tries to find a driver compatible to what library version is installed.

## Install libgpiod

If you want to control GPIOs using libgpiod, the library must be installed.

Many package managers provide a libgpiod package, for example:

```shell
apt install libgpiod2
```

## Install libgpiod manually

The installation should be the same on all Pi's, or boards whose distro uses the APT package manager.

1. Install build dependencies

```shell
sudo apt update && sudo apt install -y autogen autoconf autoconf-archive libtool libtool-bin pkg-config build-essential
```

2. Download the tarball and unpack it, see [releases](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/refs/), e.g.
```shell
wget https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/snapshot/libgpiod-2.1.tar.gz
tar -xzf libgpiod-2.1.tar.gz
```

3. Compile and install (see [docs](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/))
```shell
cd libgpiod-2.1/
./autogen.sh
make
sudo make install
sudo ldconfig
```

This will install the library .so files to `/usr/lib/local`

If you want to also build command line utilities `gpioinfo, gpiodetect` etc., specify `./autogen.sh --enable-tools=yes`
2 changes: 1 addition & 1 deletion eng/sendToHelix.proj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<HelixBuild Condition="'$(HelixBuild)' == ''">123460.01</HelixBuild>
<HelixConfiguration>$(Configuration)</HelixConfiguration>
<XUnitArguments>$(XUnitArguments) -nocolor -verbose -serialize -maxthreads 1</XUnitArguments>
<XUnitArguments>$(XUnitArguments) -notrait feature=pwm</XUnitArguments>
<XUnitArguments>$(XUnitArguments) -notrait feature=pwm -notrait feature=gpio-libgpiod2</XUnitArguments>

<IncludeDotNetCli>true</IncludeDotNetCli>
<DotNetCliPackageType>sdk</DotNetCliPackageType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Device.Gpio.Drivers;
using System.Device.Gpio.Drivers.Libgpiod.V1;
using System.Threading;
using Xunit;
using Xunit.Abstractions;
Expand All @@ -11,14 +12,14 @@ namespace System.Device.Gpio.Tests;
[Trait("feature", "gpio")]
[Trait("feature", "gpio-libgpiod")]
[Trait("SkipOnTestRun", "Windows_NT")]
public class LibGpiodDriverTests : GpioControllerTestBase
public class LibGpiodV1DriverTests : GpioControllerTestBase
{
public LibGpiodDriverTests(ITestOutputHelper testOutputHelper)
public LibGpiodV1DriverTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}

protected override GpioDriver GetTestDriver() => new LibGpiodDriver();
protected override GpioDriver GetTestDriver() => new LibGpiodDriver(0, LibGpiodDriverVersion.V1);

protected override PinNumberingScheme GetTestNumberingScheme() => PinNumberingScheme.Logical;

Expand Down
27 changes: 27 additions & 0 deletions src/System.Device.Gpio.Tests/LibGpiodV2DriverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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.Diagnostics;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace System.Device.Gpio.Tests;

[Trait("feature", "gpio")]
[Trait("feature", "gpio-libgpiod2")]
[Trait("SkipOnTestRun", "Windows_NT")]
public class LibGpiodV2DriverTests : GpioControllerTestBase
{
private const int ChipNumber = 0;

public LibGpiodV2DriverTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}

protected override GpioDriver GetTestDriver() => new LibGpiodDriver(ChipNumber, LibGpiodDriverVersion.V2);

protected override PinNumberingScheme GetTestNumberingScheme() => PinNumberingScheme.Logical;
}
1 change: 1 addition & 0 deletions src/System.Device.Gpio.Tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Currently the full test suite requires following components, if you don't have o
| BME280 (I2C) | `-notrait feature=i2c` |
| Pins 5 and 6 connected | `-notrait feature=gpio` |
| libgpiod | `-notrait feature=gpio-libgpiod` |
| libgpiodV2 | `-notrait feature=gpio-libgpiod2` |
| sysfs access (i.e. sudo or permissions) | `-notrait feature=gpio-sysfs` |
| configured PWM (overlaps with MCP3008 setting) | `-notrait feature=pwm` |
| root access (overlaps, assumes you use default permissions) | `-notrait requirement=root` |
Expand Down
18 changes: 18 additions & 0 deletions src/System.Device.Gpio/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.Device.Gpio.Drivers.LibGpiodDriverVersion.#ctor</Target>
<Left>lib/netstandard2.0/System.Device.Gpio.dll</Left>
<Right>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.Device.Gpio.Drivers.RaspberryPi3Driver.AltMode.#ctor</Target>
Expand Down Expand Up @@ -55,6 +61,18 @@
<Left>lib/netstandard2.0/System.Device.Gpio.dll</Left>
<Right>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:System.Device.Gpio.Drivers.GpiodException</Target>
<Left>lib/netstandard2.0/System.Device.Gpio.dll</Left>
<Right>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:System.Device.Gpio.Drivers.LibGpiodDriverVersion</Target>
<Left>lib/netstandard2.0/System.Device.Gpio.dll</Left>
<Right>lib/net6.0-windows10.0.17763/System.Device.Gpio.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0008</DiagnosticId>
<Target>T:System.Device.Gpio.Drivers.RaspberryPi3Driver.AltMode</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
internal partial class Interop
{
[DllImport(LibcLibrary)]
internal static extern int epoll_ctl(int epfd, PollOperations op, int fd, ref epoll_event events);
internal static extern int epoll_ctl(int epfd, PollOperations op, int fd, ref epoll_event @event);
}

internal enum PollOperations
Expand All @@ -26,10 +26,12 @@ internal struct epoll_event
public epoll_data data;
}

[Flags]
internal enum PollEvents : uint
{
EPOLLIN = 0x01,
EPOLLPRI = 0x02,
EPOLLERR = 0x08,
EPOLLET = 0x80000000
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
// Disable these StyleCop rules for this file, as we are using native names here.
#pragma warning disable SA1300 // Element should begin with upper-case letter

using System;
using System.Runtime.InteropServices;

internal partial class Interop
{
[DllImport(LibcLibrary, SetLastError = true)]
internal static extern int epoll_wait(int epfd, out epoll_event events, int maxevents, int timeout);
internal static extern int epoll_wait(int epfd, IntPtr events, int maxevents, int timeout);
}
13 changes: 13 additions & 0 deletions src/System.Device.Gpio/Interop/Unix/Libc/Interop.pipe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// Disable these StyleCop rules for this file, as we are using native names here.
#pragma warning disable SA1300 // Element should begin with upper-case letter

using System.Runtime.InteropServices;

internal partial class Interop
{
[DllImport(LibcLibrary, SetLastError = true)]
internal static extern int pipe(int[] pipefd);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using System;
using System.Device.Gpio;
using System.Device.Gpio.Libgpiod.V1;
using System.Runtime.InteropServices;
#if NET6_0_OR_GREATER
using System.Runtime.Loader;
Expand All @@ -17,7 +18,7 @@

internal partial class Interop
{
internal static partial class Libgpiod
internal static partial class LibgpiodV1
{
#if NET6_0_OR_GREATER
private const string LibgpiodLibrary = "libgpiod.so.2";
Expand All @@ -26,11 +27,11 @@ internal static partial class Libgpiod
#endif
internal static IntPtr InvalidHandleValue;

static Libgpiod()
static LibgpiodV1()
{
InvalidHandleValue = new IntPtr(-1);
#if NET6_0_OR_GREATER
Assembly currentAssembly = typeof(Libgpiod).Assembly;
Assembly currentAssembly = typeof(LibgpiodV1).Assembly;

AssemblyLoadContext.GetLoadContext(currentAssembly)!.ResolvingUnmanagedDll += (assembly, libgpiodName) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using LibgpiodV1 = Interop.LibgpiodV1;

namespace System.Device.Gpio;
namespace System.Device.Gpio.Libgpiod.V1;

/// <summary>
/// Pointer to a general-purpose I/O (GPIO) chip.
Expand All @@ -17,9 +18,9 @@ public SafeChipHandle()

protected override bool ReleaseHandle()
{
Interop.Libgpiod.gpiod_chip_close(handle);
LibgpiodV1.gpiod_chip_close(handle);
return true;
}

public override bool IsInvalid => handle == IntPtr.Zero || handle == Interop.Libgpiod.InvalidHandleValue;
public override bool IsInvalid => handle == IntPtr.Zero || handle == LibgpiodV1.InvalidHandleValue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using LibgpiodV1 = Interop.LibgpiodV1;

namespace System.Device.Gpio;
namespace System.Device.Gpio.Libgpiod.V1;

/// <summary>
/// Pointer to an iterator of all GPIO chips available on the device.
Expand All @@ -17,9 +18,9 @@ public SafeChipIteratorHandle()

protected override bool ReleaseHandle()
{
Interop.Libgpiod.gpiod_chip_iter_free(handle);
LibgpiodV1.gpiod_chip_iter_free(handle);
return true;
}

public override bool IsInvalid => handle == IntPtr.Zero || handle == Interop.Libgpiod.InvalidHandleValue;
public override bool IsInvalid => handle == IntPtr.Zero || handle == LibgpiodV1.InvalidHandleValue;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using LibgpiodV1 = Interop.LibgpiodV1;

namespace System.Device.Gpio;
namespace System.Device.Gpio.Libgpiod.V1;

/// <summary>
/// Pointer to a pin.
Expand All @@ -20,17 +21,17 @@ public SafeLineHandle()
protected override bool ReleaseHandle()
{
// Contrary to intuition, this does not invalidate the handle (see comment on declaration)
Interop.Libgpiod.gpiod_line_release(handle);
LibgpiodV1.gpiod_line_release(handle);
return true;
}

/// <summary>
/// Release the lock on the line handle. <see cref="Interop.Libgpiod.gpiod_line_release"/>
/// Release the lock on the line handle. <see cref="LibgpiodV1.gpiod_line_release"/>
/// </summary>
public void ReleaseLock()
{
ReleaseHandle();
}

public override bool IsInvalid => handle == IntPtr.Zero || handle == Interop.Libgpiod.InvalidHandleValue;
public override bool IsInvalid => handle == IntPtr.Zero || handle == LibgpiodV1.InvalidHandleValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Device.Gpio.Libgpiod.V2;

/// <seealso href="https://libgpiod.readthedocs.io/en/latest/group__edge__event.html"/>
internal enum GpiodEdgeEventType
{
RisingEdge = 1,
FallingEdge = 2
}
Loading

0 comments on commit 3fb06eb

Please sign in to comment.