44
55#include "gpio_nif.h"
66
7- #include <string.h>
8-
9- #include <stdint.h>
107#include <fcntl.h>
8+ #include <limits.h>
9+ #include <stdint.h>
1110#include <string.h>
1211#include <unistd.h>
1312
1413#include "hal_sysfs.h"
1514
15+ /* Some time between Linux 5.10 and Linux 5.15, GPIO numbering changed on
16+ * AM335x devices (Beaglebone, etc.). These devices have 4 banks of 32 GPIOs.
17+ * They used to be alphabetically sorted for file names which mirrored the
18+ * order they showed up in the I/O address map. Now they show up. Now they show
19+ * up with the bank at address 0x44c00000 coming after all of the 0x48000000
20+ * banks.
21+ *
22+ * To get the original mapping, GPIO numbers between 0 and 127 need to be
23+ * rotated by 32. I.e., x' = (x + 32) % 128.
24+ *
25+ * The real fix would be to embrace cdev and stop using GPIO numbers and the
26+ * sysfs interface, but that requires changing a lot of code, so work around
27+ * it.
28+ */
29+ static int bbb_rotate_gpio = 0 ;
30+
31+ static void check_bbb_linux_5_15_gpio_change ()
32+ {
33+ // Check for the gpiochip ordering that has the 0x44c00000 controller
34+ // ordered AFTER the 0x48000000.
35+ //
36+ // These are ordered so that the for loop fails as soon as possible on
37+ // non-AM335x platforms. Since few devices get up to gpiochip3, the
38+ // readlink(2) call should fail and there shouldn't even be a string
39+ // compare.
40+ static const char * symlink_value [] = {
41+ "/sys/bus/gpio/devices/gpiochip3" ,
42+ "../../../devices/platform/ocp/44c00000.interconnect/44c00000.interconnect:segment@200000/44e07000.target-module/44e07000.gpio/gpiochip3" ,
43+ "/sys/bus/gpio/devices/gpiochip0" ,
44+ "../../../devices/platform/ocp/48000000.interconnect/48000000.interconnect:segment@0/4804c000.target-module/4804c000.gpio/gpiochip0" ,
45+ "/sys/bus/gpio/devices/gpiochip1" ,
46+ "../../../devices/platform/ocp/48000000.interconnect/48000000.interconnect:segment@100000/481ac000.target-module/481ac000.gpio/gpiochip1" ,
47+ "/sys/bus/gpio/devices/gpiochip2" ,
48+ "../../../devices/platform/ocp/48000000.interconnect/48000000.interconnect:segment@100000/481ae000.target-module/481ae000.gpio/gpiochip2"
49+ };
50+
51+ char path [PATH_MAX ];
52+ int i ;
53+
54+ bbb_rotate_gpio = 0 ;
55+ for (i = 0 ; i < 8 ; i += 2 ) {
56+ ssize_t path_len = readlink (symlink_value [i ], path , sizeof (path ) - 1 );
57+ if (path_len < 0 )
58+ return ;
59+
60+ path [path_len ] = '\0' ;
61+ if (strcmp (symlink_value [i + 1 ], path ) != 0 )
62+ return ;
63+ }
64+ bbb_rotate_gpio = 1 ;
65+ }
66+
67+ static int fix_gpio_number (int pin_number )
68+ {
69+ if (!bbb_rotate_gpio || pin_number >= 128 )
70+ return pin_number ;
71+
72+ return (pin_number + 32 ) & 0x7f ;
73+ }
74+
1675size_t hal_priv_size ()
1776{
1877 return sizeof (struct sysfs_priv );
@@ -102,6 +161,11 @@ ERL_NIF_TERM hal_info(ErlNifEnv *env, void *hal_priv, ERL_NIF_TERM info)
102161{
103162 enif_make_map_put (env , info , enif_make_atom (env , "name" ), enif_make_atom (env , "sysfs" ), & info );
104163
164+ if (bbb_rotate_gpio < 0 )
165+ check_bbb_linux_5_15_gpio_change ();
166+
167+ enif_make_map_put (env , info , enif_make_atom (env , "remap_bbb_gpios" ), bbb_rotate_gpio ? enif_make_atom (env , "true" ) : enif_make_atom (env , "false" ), & info );
168+
105169#ifdef TARGET_RPI
106170 return rpi_info (env , hal_priv , info );
107171#else
@@ -125,6 +189,8 @@ int hal_load(void *hal_priv)
125189 return 1 ;
126190 }
127191
192+ check_bbb_linux_5_15_gpio_change ();
193+
128194 return 0 ;
129195}
130196
@@ -151,11 +217,12 @@ int hal_open_gpio(struct gpio_pin *pin,
151217{
152218 * error_str = '\0' ;
153219
220+ int pin_number = fix_gpio_number (pin -> pin_number );
154221 char value_path [64 ];
155- sprintf (value_path , "/sys/class/gpio/gpio%d/value" , pin -> pin_number );
222+ sprintf (value_path , "/sys/class/gpio/gpio%d/value" , pin_number );
156223 pin -> fd = open (value_path , O_RDWR );
157224 if (pin -> fd < 0 ) {
158- if (export_pin (pin -> pin_number ) < 0 ) {
225+ if (export_pin (pin_number ) < 0 ) {
159226 strcpy (error_str , "export_failed" );
160227 return -1 ;
161228 }
@@ -232,7 +299,8 @@ int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env)
232299 (void ) env ;
233300
234301 char edge_path [64 ];
235- sprintf (edge_path , "/sys/class/gpio/gpio%d/edge" , pin -> pin_number );
302+ int pin_number = fix_gpio_number (pin -> pin_number );
303+ sprintf (edge_path , "/sys/class/gpio/gpio%d/edge" , pin_number );
236304
237305 /* Allow 1000 * 1ms = 1 second max for retries. This is a workaround
238306 * for a first-time initialization issue where the file doesn't appear
@@ -250,7 +318,8 @@ int hal_apply_interrupts(struct gpio_pin *pin, ErlNifEnv *env)
250318int hal_apply_direction (struct gpio_pin * pin )
251319{
252320 char direction_path [64 ];
253- sprintf (direction_path , "/sys/class/gpio/gpio%d/direction" , pin -> pin_number );
321+ int pin_number = fix_gpio_number (pin -> pin_number );
322+ sprintf (direction_path , "/sys/class/gpio/gpio%d/direction" , pin_number );
254323
255324 /* Allow 1000 * 1ms = 1 second max for retries. See hal_apply_interrupts too. */
256325 char current_dir [16 ];
0 commit comments