Recent

Author Topic: Accessing GPIOs using Linux  (Read 2591 times)

MarkMLl

  • Hero Member
  • *****
  • Posts: 3535
Accessing GPIOs using Linux
« on: August 30, 2021, 01:47:18 pm »
Various single-board Linux-based computers allow direct access to the GPIO pins via (memory-mapped) ports etc.

However, the "correct" way of doing this has, for a long time, been to go via /sys/class/gpio.

This has now been deprecated, and instead one is supposed to use libgpiod, which despite its name is not a daemon but is supposed to use "non-forgeable" descriptor tokens to refer to chips and lines.

APPROACH THIS NEW API WITH CAUTION: IT IS NOT STABLE AND SOME OF THE ENTRY POINTS HAVE CHANGED THE NUMBER OF EXPECTED PARAMETERS WITH NO EASY WAY OF DETERMINING WHAT VERSION IS ON ANY PARTICULAR SYSTEM.

Apologies to anybody who thinks this is misplaced, but I think it's important for people developing for e.g. the Raspberry Pi to see it, even if they don't realise that they're running Linux or "a unix".

Subsequently: I'd like to say that I'm unhappy about this being moved by a moderator. However I consider it sufficiently important that I'm not going to "throw my toys out of the pram" by deleting it.

MarkMLl
« Last Edit: August 30, 2021, 02:19:14 pm by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

PascalDragon

  • Hero Member
  • *****
  • Posts: 3518
  • Compiler Developer
Re: Accessing GPIOs using Linux
« Reply #1 on: August 30, 2021, 02:31:54 pm »
Subsequently: I'd like to say that I'm unhappy about this being moved by a moderator. However I consider it sufficiently important that I'm not going to "throw my toys out of the pram" by deleting it.

This is about a Linux-specific API, so of course this belongs into the Linux-specific sub forum and not into the general one.

Nimral

  • Full Member
  • ***
  • Posts: 186
  • Keep it simple.
Re: Accessing GPIOs using Linux
« Reply #2 on: August 30, 2021, 03:12:15 pm »
No matter where the warning is placed, I welcome it.

I tried to look up those "non-forgeable" desctiptor tokens, but found nothing.

Where can I read more about that mechanism?

Tnnx, Armin.
Lazarus 2.0.12 & 2.3.0  (trunk) on Windows 10, Raspberry Pi OS "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15

MarkMLl

  • Hero Member
  • *****
  • Posts: 3535
Re: Accessing GPIOs using Linux
« Reply #3 on: August 30, 2021, 03:51:30 pm »
This is about a Linux-specific API, so of course this belongs into the Linux-specific sub forum and not into the general one.

Does it apply to Android? How often are Android and for that matter Raspbian (or WTF it's now called) acknowledged as being Linux by their devotees?

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

MarkMLl

  • Hero Member
  • *****
  • Posts: 3535
Re: Accessing GPIOs using Linux
« Reply #4 on: August 30, 2021, 04:13:51 pm »
I tried to look up those "non-forgeable" desctiptor tokens, but found nothing.

Where can I read more about that mechanism?

I think they're just numbers, I've not checked whether they are in fact non-sequential and not pointers but the important point is they should be /treated/ as opaque. My suspicion is that it was some programmer's bright idea and he hoped to encourage more people to adopt the style rather than blithely passing pointers around.

The documentation situation is lousy, there's fairly decent descriptions of the parameters and return values in the .h file, but that's about the best one can expect.

What I can say is that gpiod.h which come with Debian Buster and Bullseye has an API where one has to allocate bulk objects oneself and pass them as a parameter to the calls that do bulk reads, wait for events on a number of lines etc.; contrast this with the current kernel where you ask the API for a bulk object. So in the old API you have e.g.

Code: C  [Select][+][-]
  1. int gpiod_chip_get_all_lines(struct gpiod_chip *chip, struct gpiod_line_bulk *bulk) GPIOD_API;
  2.  

while in the new one you have

Code: C  [Select][+][-]
  1. struct gpiod_line_bulk * gpiod_chip_get_all_lines(struct gpiod_chip *chip);
  2.  

...get it wrong and the program will crash.

I think that it's possible to distinguish between the available .so libraries using

Code: Pascal  [Select][+][-]
  1.   (* At some point the API has changed from the bulk calls requiring a pointer
  2.     to a bulk object as an explicit parameter to allocating bulk object internal
  3.     to the library or kernel and returning a pointer or nil. The presence of
  4.     this entry point might be sufficient distinction.
  5.   *)
  6.   function hasOldBulkApi(): boolean;
  7.  
  8.   var
  9.     bulk: Pgpiod_line_bulk;
  10.  
  11.   begin
  12.     result := true;
  13.     try
  14.       bulk := Gpiod.gpiod_line_bulk_new(1);
  15.       Gpiod.gpiod_line_bulk_free(bulk);
  16.       result := false
  17.     except
  18.     end
  19.   end { hasOldBulkApi } ;
  20.  

where the Gpiod object wraps all of the dynamic loading and will raise an exception if the gpiod_line_bulk_new() entry point is unimplemented.

Knowing that, it would be possible to have a single Pascal interface which implemented e.g. gpiod_chip_get_all_lines() differently depending on the run-time behaviour of libgpiod. However a preferable solution- and one that I think the (unpublished) utility I use for processing some of this stuff could handle- would be to have libgpio0_dynamic.pas for the old API and libgpio_dynamic.pas for the new one... but it's still a lot of work to fix a foul-up which quite simply SHOULD NOT HAVE HAPPENED TO A PUBLIC API.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

ojz0r

  • New Member
  • *
  • Posts: 43
Re: Accessing GPIOs using Linux
« Reply #5 on: August 31, 2021, 11:21:00 am »
This is good to know.
At what kernel version is the old /sys/class/gpio function depricated?

Have you tried reaching out to the kernel developers that merged this?
Just trying to learn.

MarkMLl

  • Hero Member
  • *****
  • Posts: 3535
Re: Accessing GPIOs using Linux
« Reply #6 on: August 31, 2021, 11:55:04 am »
This is good to know.
At what kernel version is the old /sys/class/gpio function depricated?

Have you tried reaching out to the kernel developers that merged this?

Debian "Stretch" doesn't have this functionality, "Buster" and "Bullseye" have the old API. Buster is on kernel 4.19, Bullseye 5.10 so I think the change has been /published/ comparatively recently.

I've not tried contacting LKML etc. since quite frankly I think it would be a waste of time, and right now I'm far more interested in getting something that works with FPC and is likely to stay working... I'm afraid that I've got rather a lot on my plate.

I've just finished running the old API through h2pas. My next step involves a certain amount of manual massaging to put the declarations into a .inc file, and after that I've got a program which reads that and generates wrappers to allow the library to be called either statically or dynamically... in the current case static usage is obviously out of the question but I'm hoping that it will be possible first to allocate an object Gpiod, using that I can make a call as I described yesterday to determine whether the binary library only supports the old API, and if necessary I can revert to an object Gpio0 with methods describing the entry points.

I'm sure that everybody can appreciate that I'm disappointed and irritated to have to do this sort of thing, but quite frankly the interface via /sys/class/gpio is a toy and while it works it's not really suitable for robust software.

What I can say however is that I've been able to use both the sysfs and the "descriptor based" API for basic GPIO twiddling on both an RPi and a PC using the board described e.g. at https://hackaday.com/2018/02/21/linux-adds-ch341-gpio/ The point at which I ran into problems was adding event (i.e. interrupt) handling: the contextless calls have been removed entirely (which is no big deal) and the _bulk calls have had their parameters changed.

MarkMLl
« Last Edit: August 31, 2021, 12:38:49 pm by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

PascalDragon

  • Hero Member
  • *****
  • Posts: 3518
  • Compiler Developer
Re: Accessing GPIOs using Linux
« Reply #7 on: August 31, 2021, 01:01:21 pm »
This is about a Linux-specific API, so of course this belongs into the Linux-specific sub forum and not into the general one.

Does it apply to Android? How often are Android and for that matter Raspbian (or WTF it's now called) acknowledged as being Linux by their devotees?

Google usually cooks up their own stuff anyway and it could very well be that the new GPIO API came from them. And Raspbian is definitely a Debian derivate and thus a Linux no matter what any devotee says.

MarkMLl

  • Hero Member
  • *****
  • Posts: 3535
Re: Accessing GPIOs using Linux
« Reply #8 on: August 31, 2021, 05:00:22 pm »
I think I can provide a bit more useful information about versions. The first thing to note is that all the versions I'm looking at have a function returning a char* (i.e. a PChar in Pascal terms) to a brief version number e.g. '1.2', this is set in configure.ac in the project's root directory e.g.

Code: [Select]
AC_INIT([libgpiod], 1.6.3) <=====
AC_SUBST(EXTRA_VERSION, [])

AC_DEFINE_UNQUOTED([GPIOD_VERSION_STR],
["$PACKAGE_VERSION$EXTRA_VERSION"],
[Full library version string.])
AC_SUBST(VERSION_STR, [$PACKAGE_VERSION$EXTRA_VERSION])

The one I've been using since about February, which comes with Debian "Buster" (which was stable at the time) is v1.2. The one being shipped with "Bullseye" (now Debian stable) is 1.6.2, there's also a 1.6.3 at kernel.org which appears to still have the "old" API.

The master branch at kernel.org is at 2.0, but all of the application developer discussion etc. ends up there... there's a possibility that the only people using that API are "insiders" working on the bundled tools with everybody else still using the sysfs API or (worse) direct access.

Being charitable, kernel.org's git suggests that the branch was "only" made in about March this year (in which case how did I mistakenly pick up the v2 header in February?), so we can hope that either they fix the entry point naming or that the various distreaux have the decency to call it libgpio2 even if the developers don't.

So I think that what I need to do is fairly clear: rename the interface I've been using for the last few months gpiod2 and call the one I'm currently working on gpiod (i.e. an implicit v1).

In any event, straightforward medium-performance bit-twiddling looks- so far at least- as though it is unchanged between the two APIs.

One of the nasties in the v1 API is that in an effort to avoid giving anything away about the internal representation they don't document how large a bulk object should be, space has to be reserved at the application level for the result of gpiod_line_event_wait_bulk(), and when an event is received this has to treated as an indexable array of pointers to lines. Something like this:

Code: Pascal  [Select][+][-]
  1. procedure TmonitorThread.Execute;
  2.  
  3. const
  4.   milliSec= 1000 * 1000;
  5.  
  6. (* The size of the bulk object isn't documented, a guess would suggest that it  *)
  7. (* should be GPIOD_LINE_BULK_MAX_LINES but inspection indicates that some extra *)
  8. (* space is needed to store the count.                                          *)
  9.  
  10. var
  11.   lineHandles: array[0..255] of Pgpiod_line;
  12.   eventedLines: array[1..(GPIOD_LINE_BULK_MAX_LINES + 1)] of Pgpiod_line;
  13.   event: gpiod_line_event;
  14.   timeout: timespec;
  15.   i: integer;
  16.  
  17. begin
  18.   FillByte(lineHandles, SizeOf(lineHandles), 0);
  19.  
  20. // Assume chipHandle is stored at thread creation.
  21.  
  22.   if Gpiod.gpiod_chip_get_all_lines(chipHandle, @lineHandles) <> 0 then
  23.     exit;
  24.   try
  25.     if Gpiod.gpiod_line_request_bulk_both_edges_events(@lineHandles, 'gui_test') = 0 then
  26.       while not stopMonitorThread do begin
  27.         timeout.tv_sec := 0;
  28.         timeout.tv_nsec := 100 * milliSec;
  29.         FillByte(eventedLines, SizeOf(eventedLines), 0);
  30.         i := Gpiod.gpiod_line_event_wait_bulk(@lineHandles, @timeout, @eventedLines);
  31.         if i < 0 then                   (* 0 represents timeout, <0 error       *)
  32.           break;
  33.         while i > 0 do begin
  34.           if Gpiod.gpiod_line_event_read(eventedLines[i], @event) = 0 then
  35.             seenEvent := true;
  36.           i -= 1
  37.         end
  38.       end
  39.   finally
  40.     Gpiod.gpiod_line_release_bulk(@lineHandles)
  41.   end
  42. end { TmonitorThread.Execute } ;
  43.  

I can confirm that gpiod is applicable to Android, although I don't know whether it is used by internal devices or purely by people using an Android-based phone/tablet/board to control e.g. laboratory equipment. As I've already said, it works fairly well with an external board on a PC.

Later: I don't like the position of the try in that example, but I'm not changing it since it's working code.

MarkMLl
« Last Edit: September 04, 2021, 12:40:04 pm by MarkMLl »
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Bi0T1N

  • Jr. Member
  • **
  • Posts: 65
Re: Accessing GPIOs using Linux
« Reply #9 on: September 04, 2021, 11:29:52 am »
The one I've been using since about February, which comes with Debian "Buster" (which was stable at the time) is v1.2. The one being shipped with "Bullseye" (now Debian stable) is 1.6.2, there's also a 1.6.3 at kernel.org which appears to still have the "old" API.

The master branch at kernel.org is at 2.0, but all of the application developer discussion etc. ends up there... there's a possibility that the only people using that API are "insiders" working on the bundled tools with everybody else still using the sysfs API or (worse) direct access.
Actually I had the same problem when I had to implement something for the Raspberry Pi and thought about using the libgpiod as it sounded like a nice way to make it also working on other linux systems. But as you said there is the version 1.x and another branch next/libgpiod-2.0 which implements a new API which indicates that the "old" API might not be the best approach to be used for current developments as the newer one is still work in progress and after that the "old" one will probably be deprecated.
Another big drawback for me was that I couldn't find anything about using the hardware clock of the Raspberry Pi with it but I assumed that it won't work as it seems to be a hack that is implemented in the pigpio library.

Being charitable, kernel.org's git suggests that the branch was "only" made in about March this year (in which case how did I mistakenly pick up the v2 header in February?), so we can hope that either they fix the entry point naming or that the various distreaux have the decency to call it libgpio2 even if the developers don't.
It seems they messed it up as the master branch contains the bump to v2 with the date of 2020-10-02.

MarkMLl

  • Hero Member
  • *****
  • Posts: 3535
Re: Accessing GPIOs using Linux
« Reply #10 on: September 04, 2021, 11:47:35 am »
Gut feeling at the moment: the gpiod API's stable and usable for polled I/O, and is probably to be preferred over the sysfs one since devices won't appear/disappear if some other process does something.

The gpiod interrupt/event API isn't usable. v1 forces the programmer to rely on information which the library's original author explicitly said should be obfuscated i.e. what's in the bulk array, while v2 is effectively unpublished (not picked up by the major distreaux) and has library and entry point naming problems. What something's doing at kernel.org in this state defies explanation.

I'm not saying that sysfs offers better interrupt support, but it's documented as usable with a select() call and Linux's behaviour if a file or directory disappears should be fairly well understood by now.

My own test code using sysfs is in a particularly unfortunate state, since it in effect uses cat and echo i.e. opens/closes files to excess: as such also using select() calls would be... painful and I don't know whether FAM works. But OTOH the existing code is extremely defensive, and makes the explicit assumption that somebody will be adding/removing bits and changing their direction while the program's running.

I can't speak for the higher-level FPC etc. libraries.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

 

TinyPortal © 2005-2018