Use gpio char device interface
The Linux kernel is encouraging people to switch away from sysfs to the new character device interface. The new interface has some nice advantages (timestamps on interrupts being a nice one for me), but is only supported on Linux 4.8 and later. Since all current Nerves systems are past that version (finally), I think that it's safe to upgrade.
Adafruit is using the new gpio interface to support DHT sensors. This is a great idea and hopefully we can support an Elixir DHT library as well with the new gpio interface.
https://blog.adafruit.com/2018/11/26/sysfs-is-dead-long-live-libgpiod-libgpiod-for-linux-circuitpython/
Are we going to want to use libgpiod or use the linux/gpio.h?
I think that's TBD. We'll have to try it out.
OK. I think I can get this started. My Elixir skills are still somewhat half-assed, so I'll definitely need some guidance with the API and someone to polish the whole thing when I finish. But I'm pretty comfortable with the NIF end of things.
The first thing to consider is whether we want to start a new library with a new API for the cdev interface, or update this library to support both. I would recommend we go with a new, separate, cdev library for several reasons:
- The API for the two are significantly different and it would be difficult to support both with a single API.
- If we did attempt to create a uniform API, it would likely lead to some bad practices that the cdev interface was designed to avoid (such as the use of global pin numbers).
- We will want new applications to switch to the new API. We really wouldn't want an application mixing the two interfaces, and that would be easier to enforce with a new/separate library.
- Legacy applications could keep the old sysfs API without breaking, for as long as the kernel supports it.
- It would be easy to phase out the old library when it was no longer supported, and not have legacy code in the new lib.
Let me know what you think. I'll follow up shortly with a bullet list of the differences that the cdev library would bring.
Oh, also, we wouldn't need to wrap libgpiod. It's an excellent project, but it's primary usefulness is the command line tools that it provides. Since the cdev drivers don't have a sysfs interface, it's difficult or impossible to access them from a shell prompt without these tools. So they make exploration and debug of the chips and lines possible from the shell.
But the actual "library" part of libgpiod is a fairly thin wrapper around the ioctl interface to the kernel, and it would be just as easy for us to go straight to that API without the burden of compiling and linking in a separate library.
Regarding the question on starting a new library or not, you provided a lot of good reasons for why it should be new library. Currently, the reasons I have against starting a new library are pretty minor and I don't feel like arguing any of them.
We will need a name, but we could use circuits_cdev for the time being for development and then rename it if a better name arises.
I'm quite happy with anything that gets us to trying out the cdev interface from Elixir and homing in on an API. Thanks!
Having tried to work on this in the past I really do think a library is a really good way to go. I struggled a lot with trying to maintain the API with the chardev implementation and at the Nif level, things did not make a lot of sense since sysfs and chardev are very different.
I am :+1: for a new library and circuits_cdev or circuits_gpio_cdev are good with me.
@fpagliughi let me know if there is anything I can do to help. I know the API pretty well but never got very far in implementing due to life and being stuck on the API incompatibility issues you brought up.
Yeah, I don't have strong feelings about it (yet), but it seems better to go with a separate library. That's what some of the other languages I looked at have done as well. I assume that it will be easiest to fork this code and then break it, but I'm sure there will be plenty of reuse in that way.
Thanks, @mattludwigs. I recently worked on the Rust GPIO cdev, so I'm pretty good with the kernel API, but will definitely need guidance on how to express it in Elixir.
Just some quick notes and differences. Based on memory, these are true :-)
- The Linux kernel groups GPIO into "chips", each of which is typically an MCU port/register or expander chip, possibly connected via SPI or I2C.
- The chips are enumerated in the dev filesystem as
/dev/gpiochipN, whereNis an integer 0, 1,2, etc. Chip devices are opened and accessed through a normal Linux file handle. - Each chip can have up to 64 pins (kernel limit), enumerated as "offsets" 0, 1, 2, ...
- Global pin numbering no longer exists. Pins are offset numbers into the different chips.
- One or more of the lines (pins) of a chip can be requested. If granted, the kernel will return a file handle to access and manipulate those lines.
- The request grants exclusive access, so no other process (OS or Elixir) can mess with your pins. Alternately, the request can fail if the pin is already in use by another process.
- When multiple lines are managed by a single handle, they can be read or written atomically. Depending on the hardware/driver this can be at the hardware or kernel level.
- To monitor incoming events, the application can request an event handle be created on a group of one or more pins.
- Events are queued and timestamped in the kernel, with timestamps normally assigned in the hardware ISR. So events don't get lost and the time at which they occur is recorded very precisely.
- The event handle can be used to easily create a blocking iterator for incoming events.
- Interactions with the kernel happen via ioctl() calls on the various file handles (chip, line, and event handles). There is no sysfs access.