Skip to content

Commit 63e6b8c

Browse files
authored
gpioioctl: Improve Registration (#64)
* Improve registration to handle vagaries of how the Linux kernel handles GPIO Chips, particularly on the Pi 5. Improved handling of duplicate pin names. Code now prefixes the pin name with the chip name if it's duplicated. * Fix lint issue with variable shadowing. * Fix possible out of bounds error.
1 parent 75941b8 commit 63e6b8c

File tree

3 files changed

+68
-40
lines changed

3 files changed

+68
-40
lines changed

gpioioctl/basic_test.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,16 @@
99
package gpioioctl
1010

1111
import (
12-
"log"
1312
"testing"
1413

15-
"periph.io/x/conn/v3/gpio"
1614
"periph.io/x/conn/v3/gpio/gpioreg"
1715
)
1816

1917
var testLine *GPIOLine
2018

2119
func init() {
22-
var err error
23-
2420
if len(Chips) == 0 {
2521
makeDummyChip()
26-
line := GPIOLine{
27-
number: 0,
28-
name: "DummyGPIOLine",
29-
consumer: "",
30-
edge: gpio.NoEdge,
31-
pull: gpio.PullNoChange,
32-
direction: LineDirNotSet,
33-
}
34-
35-
chip := GPIOChip{name: "DummyGPIOChip",
36-
path: "/dev/gpiochipdummy",
37-
label: "Dummy GPIOChip for Testing Purposes",
38-
lineCount: 1,
39-
lines: []*GPIOLine{&line},
40-
}
41-
Chips = append(Chips, &chip)
42-
if err = gpioreg.Register(&line); err != nil {
43-
nameStr := chip.Name()
44-
lineStr := line.String()
45-
log.Println("chip", nameStr, " gpioreg.Register(line) ", lineStr, " returned ", err)
46-
}
4722
}
4823
}
4924

gpioioctl/dummy.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ func makeDummyChip() {
3636
lines: []*GPIOLine{&line},
3737
}
3838
Chips = append(Chips, &chip)
39-
Chips = append(Chips, &chip)
4039
if err := gpioreg.Register(&line); err != nil {
4140
nameStr := chip.Name()
4241
lineStr := line.String()

gpioioctl/gpio.go

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"path"
1515
"path/filepath"
1616
"runtime"
17+
"sort"
1718
"strings"
1819
"sync"
1920
"time"
@@ -402,6 +403,9 @@ func newGPIOChip(path string) (*GPIOChip, error) {
402403

403404
chip.name = strings.Trim(string(info.name[:]), "\x00")
404405
chip.label = strings.Trim(string(info.label[:]), "\x00")
406+
if len(chip.label) == 0 {
407+
chip.label = chip.name
408+
}
405409
chip.lineCount = int(info.lines)
406410
var line_info gpio_v2_line_info
407411
for line := 0; line < int(info.lines); line++ {
@@ -591,32 +595,82 @@ func (d *driverGPIO) After() []string {
591595
//
592596
// https://docs.kernel.org/userspace-api/gpio/chardev.html
593597
func (d *driverGPIO) Init() (bool, error) {
594-
if runtime.GOOS == "linux" {
595-
items, err := filepath.Glob("/dev/gpiochip*")
596-
if err != nil {
597-
return true, err
598-
}
599-
if len(items) == 0 {
600-
return false, errors.New("no GPIO chips found")
598+
if runtime.GOOS != "linux" {
599+
return true, nil
600+
}
601+
items, err := filepath.Glob("/dev/gpiochip*")
602+
if err != nil {
603+
return true, fmt.Errorf("gpioioctl: %w", err)
604+
}
605+
if len(items) == 0 {
606+
return false, errors.New("no GPIO chips found")
607+
}
608+
// First, get all of the chips on the system.
609+
var chips []*GPIOChip
610+
var chip *GPIOChip
611+
for _, item := range items {
612+
chip, err = newGPIOChip(item)
613+
if err == nil {
614+
chips = append(chips, chip)
615+
} else {
616+
log.Println("gpioioctl.driverGPIO.Init() Error", err)
601617
}
602-
Chips = make([]*GPIOChip, 0)
603-
for _, item := range items {
604-
chip, err := newGPIOChip(item)
605-
if err != nil {
606-
log.Println("gpioioctl.driverGPIO.Init() Error", err)
607-
return false, err
618+
}
619+
// Now, sort the chips so that those labeled with pinctrl- ( a Pi kernel standard)
620+
// come first. Otherwise, sort them by label. This _should_ protect us from any
621+
// random changes in chip naming/ordering.
622+
sort.Slice(chips, func(i, j int) bool {
623+
I := chips[i]
624+
J := chips[j]
625+
if strings.HasPrefix(I.Label(), "pinctrl-") {
626+
if strings.HasPrefix(J.Label(), "pinctrl-") {
627+
return I.Label() < J.Label()
608628
}
629+
return true
630+
} else if strings.HasPrefix(J.Label(), "pinctrl-") {
631+
return false
632+
}
633+
return I.Label() < J.Label()
634+
})
635+
636+
mName := make(map[string]struct{})
637+
// Get a list of already registered GPIO Line names.
638+
registeredPins := make(map[string]struct{})
639+
for _, pin := range gpioreg.All() {
640+
registeredPins[pin.Name()] = struct{}{}
641+
}
642+
643+
// Now, iterate over the chips we found and add their lines to conn/gpio/gpioreg
644+
for _, chip := range chips {
645+
// On a pi, gpiochip0 is also symlinked to gpiochip4, checking the map
646+
// ensures we don't duplicate the chip.
647+
if _, found := mName[chip.Name()]; !found {
609648
Chips = append(Chips, chip)
649+
mName[chip.Name()] = struct{}{}
650+
// Now, iterate over the lines on this chip.
610651
for _, line := range chip.lines {
652+
// If the line has some sort of reasonable name...
611653
if len(line.name) > 0 && line.name != "_" && line.name != "-" {
654+
// See if the name is already registered. On the Pi5, there are at
655+
// least two chips that export "2712_WAKE" as the line name.
656+
if _, ok := registeredPins[line.Name()]; ok {
657+
// This is a duplicate name. Prefix the line name with the
658+
// chip name.
659+
line.name = chip.Name() + "-" + line.Name()
660+
if _, found := registeredPins[line.Name()]; found {
661+
// It's still not unique. Skip it.
662+
continue
663+
}
664+
}
665+
registeredPins[line.Name()] = struct{}{}
612666
if err = gpioreg.Register(line); err != nil {
613667
log.Println("chip", chip.Name(), " gpioreg.Register(line) ", line, " returned ", err)
614668
}
615669
}
616670
}
617671
}
618672
}
619-
return true, nil
673+
return len(Chips) > 0, nil
620674
}
621675

622676
var drvGPIO driverGPIO

0 commit comments

Comments
 (0)