Skip to content

Commit 1eb50e1

Browse files
committed
Add hack to handle BBB GPIO remapping
As of Linux 5.15, the AM335x GPIO banks got reordered and this throws off the pin numbering. See comments for more details. The gist is that we hide it from existing programs to avoid the new mapping that's different from almost every Beaglebone doc on the web. This will make programs work with the caveat that anyone doing manual debugging of sysfs will notice it, be confused, and hopefully find this commit. The future has been to switch to cdev, but that's not happening in the v1.x branch so this is the workaround to allow Linux 5.15 and later.
1 parent 5a0e470 commit 1eb50e1

File tree

1 file changed

+76
-7
lines changed

1 file changed

+76
-7
lines changed

c_src/hal_sysfs.c

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,74 @@
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+
1675
size_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)
250318
int 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

Comments
 (0)