Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add libgpiodv2 support #2184

Merged
merged 34 commits into from
Jan 27, 2024
Merged

Add libgpiodv2 support #2184

merged 34 commits into from
Jan 27, 2024

Conversation

huesla
Copy link
Contributor

@huesla huesla commented Dec 1, 2023

Fixes: #2179
Hi there,
This PR aims to integrate version 2 of libgpiod

C library and tools for interacting with the linux GPIO character device (gpiod stands for GPIO device)

Structure
Basically, my strategy was to extend as much functionality as possible while touching as little existing code as possible.
A lot has changed in the API between v1 and v2 of libgpiod. For example, the control of GPIOs is purely via so-called request objects. If you want to learn more about the concepts, see:

  1. overview https://www.youtube.com/watch?v=EcUULioruj0&t=1s
  2. docs https://libgpiod.readthedocs.io/en/latest/modules.html

As the new functions are very different from the existing ones, I have decided to differentiate between V1 and V2 in the namespace or class name. (What changes together should go together). This concerns the Interop class, as well as the driver and event handler.

The new structure from the lower to the higher level is shown as follows:

Binding
Interop.libgpiod
Maps all methods of the current libgpiod API, structured in the same way as in the docs.

Proxies
libgpiod offers so-called "opaque data classes". These are objects whose fields are not visible and can only be changed via methods. libgpiod divides the API logically according to objects (or modules) see docs. To represent these in C#, there are now so-called proxy classes. Such a class offers the same methods as the controlled gpiod object, but in a more C#-friendly way.
LibGpiodProxyBase is the base class and provides methods for thread security.
(more information on the threading contract of libgpiod) and wraps any exception from the native call in GpiodException (easier for clients to handle).
I think when you know the concept of a libgpiod module, looking at the corresponding proxy class should give a clear picutre in what it does (Basically it only forwards stuff).

Handles
Each proxy class works with a SafeHandle, the object that holds the pointer to the gpiod object. SafeHandle comes from the framework and makes sure to call ReleaseHandle() once, which frees the gpiod object.

Enums
Constants see docs

LibGpiodV2Driver
This class is the "bridge" between GpioController and the binding. Each driver instance manages one gpiochip.
It implements the usual methods and manages proxies, mainly the LineRequest to control GPIOs. The methods of the driver were implemented to have as little traffic as possible, e.g. reuse existing LineRequest objects etc.

LibGpiodV2EventObserver
Is closely coupled with LibGpiodV2Driver and manages EdgeEvent subscriptions / callbacks.

GpioController
Existing class that instantiates the driver. LibGpiodDriverFactory helps to load the correct version suitable for the system. If libgpiodv2 is available, v2 is loaded, otherwise v1 or v0.

Testing
Tested with manual tests and GpioControllerTestBase tests on Raspberry Pi 3/4/5 with latest RPI OS (lite).
I had to adapt the gpiochip number with Pi5 to 4, instead of 0 on Pi3/4. I am not sure how you treat different boards in unit tests, so I left 0 as default, because Pi5 is not yet as popular.

Testing (continued)
The current tests of GpioControllerTestBase use delays, which provokes flakyness and makes the tests slow.
I have an improvement branch that uses reset events instead of delays. Again I tested it on the Pi 3/4/5... the time got improved alot and I did not encounter flakyness anymore:

Pi3: 50.2 sec -> 32.2 sec
Pi4: 42.8s -> 19.3s
Pi5: 40.3s -> 13.2s
(measured only once)

I could make a seperate PR for this after this one, depending on how the integration of this will go.
The real question is whether other drivers can keep up... would probably have to be tested with your CI pipeline.

Commits
The commits are chronologically logically ordered.

Advanced use
It should be noted that the current GpioController/Driver architecture imposes restrictions on the libgpiodv2 functionality. For example, information such as ChipInfo, LineInfo, EventInfo, Event sequence numbers etc. are not available. Also functions like setting Debounce Period, EventBuffer size etc. are not available. (One of my use cases is, for example, the time-accurate detection of edges, and the time info is in the edge event as Ns timestamp).
In order not to change the existing architecture, I have stuck to it and made proxies etc. internal.
However, this also means that this functionality cannot be used externally.
My suggestion is therefore to make it public. This allows advanced users extended functionality if they need it. Normal users can continue to use the GpioController API.
Since it would probably be a bit tedious (even for advanced users) to manage proxy objects directly, and to use every fine grained function, it would make sense to create an abstraction that would simplify this and cover the most relevant use cases. The code could look much nicer then, for example:

var request = new GpioChip(4).PrepareRequest()
    .SetConsumer("me")
    .AddLineSettings(lineSettings => 
    {
        lineSettings.Lines = new { 12, 18 };
        lineSettings.Direction = LineDirection.Input;
        lineSettings.EdgeDetection = EdgeDetection.Both;
    })
    .DoReqeust();

Task.Run(() =>
{
    foreach (var event in request.GetNextEvent())
    {
        ...
    }
};

request.SetLine(12, High);
request.SetLine(12, Low);

(By the way, this example is inspired by the Python wrapper for libgpiodv2)

I would of course also make documentation for a few examples.

I look forward to comments and opinions :)

Microsoft Reviewers: Open in CodeFlow

EDIT (@krwq): adding link to the issue

@huesla huesla mentioned this pull request Dec 1, 2023
@huesla
Copy link
Contributor Author

huesla commented Dec 1, 2023

@dotnet-policy-service agree

@pgrawehr
Copy link
Contributor

pgrawehr commented Dec 1, 2023

Thanks for the submission!
Reviewing this will take a while, in particular because it touches the core parts of our library. So please be patient.

Can you please specify on what system you ran your tests and what systems you expect to work with the new library? Are there any systems where the old driver is no longer supported and only the new one will work?

@huesla
Copy link
Contributor Author

huesla commented Dec 2, 2023

Thanks for the submission! Reviewing this will take a while, in particular because it touches the core parts of our library. So please be patient.

Can you please specify on what system you ran your tests and what systems you expect to work with the new library? Are there any systems where the old driver is no longer supported and only the new one will work?

All right, I will be patient then :)

If you ask me then the answer is Raspberry Pi. It was also Pi's 3,4 and 5 on which I ran the tests.
Basically, the libgpiod C# drivers are not directly tied to a specific system, because wherever libgpiod is supported, the drivers are too (and that is on systems where gpiolib is supported and GPIO devices are available).

On the question of whether the V2 driver can replace the V1 driver: It depends :-)
What distinguishes V2 from V1, apart from a more sophisticated API, are of course the features, such as sequence number mapping to edge events, bias setting, kernel clock setting etc...
These features could be offered publicly for advanced users of dotnet iot. Otherwise, however, it probably depends mostly on which libgpiod the known distros deliver. It will probably take some time until v2 is delivered by default.

It's also not the goal of this PR to replace the v1 driver, rather to "prepare for the future" and have a v2 implementation in place, whose features can be used.

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work overall!

@joperezr
Copy link
Member

joperezr commented Dec 4, 2023

Thanks a bunch for the contribution @huesla, I went through it and added some feedback (might seem like a lot but most are just either NITs or questions) but this is overall looking really good. Thanks a lot again for taking the time to contribute this.

@pgrawehr
Copy link
Contributor

/azp run dotnet.iot

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@huesla
Copy link
Contributor Author

huesla commented Jan 16, 2024

Open points:

  1. Who ever has the rights, should investigate why the tests fail on this pipeline, i.e. find out why the factory implementation does not find libgpiod in the standard paths (where is libgpiod installed?)
  2. Depending on 1. find out what to do with the factory implementation. I am almost sure that the previous implementation could find the library, and it was utilizing DllImport for that. We may want to discuss using NativeLibrary only for this, but it would mean dropping netstandard (as pointed out in some comment).

@pgrawehr
Copy link
Contributor

/azp run dotnet.iot

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@pgrawehr
Copy link
Contributor

@huesla I've tried to fix the remaining issues, but the thing with the library search path is complicated. On my Rpi4 (with 64 bit Raspberry Pi OS) the correct path for libgpiod.so.2 is /lib/aarch64-linux-gnu/libgpiod.so.2. The only way to get to this path appears to be the output of ldconfig -p. This tool shows the search path of the dynamic linker. I'm not sure why this is different for you. Which operating system are you using?

@huesla
Copy link
Contributor Author

huesla commented Jan 21, 2024

@pgrawehr thanks for your investigation.
I just did a fresh install of 32/64 bit RPI OS on Pi4/Pi5. On both Pi's the result was:

  • 32 Bit: /usr/lib/arm-linux-gnueabihf/libgpiod.so.2.2.2
  • 64 Bit: /usr/lib/aarch64-linux-gnu/libgpiod.so.2.2.2

I think on my previous system setup the libraries ended up in "/usr/lib" because I may have compiled and installed them manually only. But under Debian there is multi arch support.
An easy fix would be to make the search recursive, going downwards starting from "/usr/lib", wdyt?

@pgrawehr
Copy link
Contributor

Sounds like a plan. I was first thinking about parsing the output of ldconfig, but that's quite ugly to do. Of course, recursively iterating the subfolders of /usr/lib could theoretically find the wrong library (e.g. the 32bit library for a 64 bit process), but normally you would have either both or none, I suppose.

@huesla
Copy link
Contributor Author

huesla commented Jan 22, 2024

@pgrawehr the search is now recursive. Should we enable tests again?

@pgrawehr
Copy link
Contributor

/azp run dotnet.iot

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@huesla
Copy link
Contributor Author

huesla commented Jan 23, 2024

@pgrawehr the V1 tests succeeded, can we run V2 aswell? (they are still disabled)

@pgrawehr
Copy link
Contributor

@huesla No, because the devices have not been upgraded yet. That may take some time, but I will push that further. I don't have the necessary permissions myself. When the old driver works, it's fine for me to merge anyway.

Copy link
Contributor

@pgrawehr pgrawehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me now.

Copy link
Member

@Ellerbach Ellerbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work and great collaboration in this PR. I've been following the different threads and progress.

/// <summary>
/// Driver factory for different versions of libgpiod.
/// </summary>
internal sealed class LibGpiodDriverFactory
Copy link
Member

@krwq krwq Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see if we can find some existing pattern in dotnet/runtime or other repo for searching for right .so

Copy link
Member

@krwq krwq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM as long as we open P0 issue to improve .so searching logic

@huesla
Copy link
Contributor Author

huesla commented Jan 26, 2024

Great work and great collaboration in this PR. I've been following the different threads and progress.

Thanks to all for the collaboration aswell. If there is anything else to improve, let me know

@pgrawehr
Copy link
Contributor

@huesla We had a call on Thursday and decided to merge this now and then address some of the remaining issues separately. The two points that were still disputed was the library search algorithm and the best way to expose this new functionality.

For this, I have created #2271 and #2272.

In no way, this should be criticism on your work, this is just trying to allign it with large scale standards.

@pgrawehr
Copy link
Contributor

/azp run dotnet.iot

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@pgrawehr pgrawehr merged commit 3fb06eb into dotnet:main Jan 27, 2024
10 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Feb 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

libgpiod v2 support
7 participants