Skip to content

Commit 75941b8

Browse files
authored
bcm283x: Issue 62 Change bcm283x to use ioctl for GPIO pins, not sysfs. (#63)
1 parent 7540d26 commit 75941b8

File tree

12 files changed

+247
-110
lines changed

12 files changed

+247
-110
lines changed

bcm283x/doc.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Package bcm283x exposes the BCM283x GPIO functionality.
66
//
77
// This driver implements memory-mapped GPIO pin manipulation and leverages
8-
// sysfs-gpio for edge detection.
8+
// ioctl-gpio for edge detection.
99
//
1010
// If you are looking for the actual implementation, open doc.go for further
1111
// implementation details.
@@ -15,6 +15,10 @@
1515
// Aliases for GPCLK0, GPCLK1, GPCLK2 are created for corresponding CLKn pins.
1616
// Same for PWM0_OUT and PWM1_OUT, which point respectively to PWM0 and PWM1.
1717
//
18+
// For multi-pin IO, you should prefer using the /host/gpioioctl/GPIOChip.LineSet()
19+
// functionality. It's chipset agnostic because it uses the ioctl interfaces,
20+
// and it offers multi-pin WaitForEdge().
21+
//
1822
// # Datasheet
1923
//
2024
// https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

bcm283x/gpio.go

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"periph.io/x/conn/v3/physic"
2020
"periph.io/x/conn/v3/pin"
2121
"periph.io/x/host/v3/distro"
22+
"periph.io/x/host/v3/gpioioctl"
2223
"periph.io/x/host/v3/pmem"
2324
"periph.io/x/host/v3/sysfs"
2425
"periph.io/x/host/v3/videocore"
@@ -185,7 +186,7 @@ func PinsSetup28To45(drive physic.ElectricCurrent, slewLimit, hysteresis bool) e
185186
return nil
186187
}
187188

188-
// Pin is a GPIO number (GPIOnn) on BCM238(5|6|7).
189+
// Pin is a GPIO number (GPIOnn) on BCM283(5|6|7).
189190
//
190191
// Pin implements gpio.PinIO.
191192
type Pin struct {
@@ -195,7 +196,7 @@ type Pin struct {
195196
defaultPull gpio.Pull // Default pull at system boot, as per datasheet.
196197

197198
// Immutable after driver initialization.
198-
sysfsPin *sysfs.Pin // Set to the corresponding sysfs.Pin, if any.
199+
ioctlPin *gpioioctl.GPIOLine // Set to the corresponding gpioioctl.GPIOLine, if any.
199200

200201
// Mutable.
201202
usingEdge bool // Set when edge detection is enabled.
@@ -217,7 +218,7 @@ func (p *Pin) String() string {
217218
// disabled.
218219
func (p *Pin) Halt() error {
219220
if p.usingEdge {
220-
if err := p.sysfsPin.Halt(); err != nil {
221+
if err := p.ioctlPin.Halt(); err != nil {
221222
return p.wrap(err)
222223
}
223224
p.usingEdge = false
@@ -245,10 +246,10 @@ func (p *Pin) Function() string {
245246
// Func implements pin.PinFunc.
246247
func (p *Pin) Func() pin.Func {
247248
if drvGPIO.gpioMemory == nil {
248-
if p.sysfsPin == nil {
249+
if p.ioctlPin == nil {
249250
return pin.FuncNone
250251
}
251-
return p.sysfsPin.Func()
252+
return p.ioctlPin.Func()
252253
}
253254
switch f := p.function(); f {
254255
case in:
@@ -311,10 +312,10 @@ func (p *Pin) SupportedFuncs() []pin.Func {
311312
// SetFunc implements pin.PinFunc.
312313
func (p *Pin) SetFunc(f pin.Func) error {
313314
if drvGPIO.gpioMemory == nil {
314-
if p.sysfsPin == nil {
315-
return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible"))
315+
if p.ioctlPin == nil {
316+
return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible"))
316317
}
317-
return p.sysfsPin.SetFunc(f)
318+
return p.ioctlPin.SetFunc(f)
318319
}
319320
switch f {
320321
case gpio.FLOAT:
@@ -369,29 +370,17 @@ func (p *Pin) SetFunc(f pin.Func) error {
369370
// possible to 'read back' what value was specified for each pin.
370371
//
371372
// Will fail if requesting to change a pin that is set to special functionality.
372-
//
373-
// Using edge detection requires opening a gpio sysfs file handle. On Raspbian,
374-
// make sure the user is member of group 'gpio'. The pin will be exported at
375-
// /sys/class/gpio/gpio*/. Note that the pin will not be unexported at
376-
// shutdown.
377-
//
378-
// For edge detection, the processor samples the input at its CPU clock rate
379-
// and looks for '011' to rising and '100' for falling detection to avoid
380-
// glitches. Because gpio sysfs is used, the latency is unpredictable.
381373
func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
382374
if p.usingEdge && edge == gpio.NoEdge {
383-
if err := p.sysfsPin.Halt(); err != nil {
375+
if err := p.ioctlPin.Halt(); err != nil {
384376
return p.wrap(err)
385377
}
386378
}
387379
if drvGPIO.gpioMemory == nil {
388-
if p.sysfsPin == nil {
389-
return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible"))
390-
}
391-
if pull != gpio.PullNoChange {
392-
return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized"))
380+
if p.ioctlPin == nil {
381+
return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible"))
393382
}
394-
if err := p.sysfsPin.In(pull, edge); err != nil {
383+
if err := p.ioctlPin.In(pull, edge); err != nil {
395384
return p.wrap(err)
396385
}
397386
p.usingEdge = edge != gpio.NoEdge
@@ -457,11 +446,11 @@ func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
457446
}
458447
}
459448
if edge != gpio.NoEdge {
460-
if p.sysfsPin == nil {
461-
return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.number))
449+
if p.ioctlPin == nil {
450+
return p.wrap(fmt.Errorf("pin %d is not exported by gpioioctl", p.number))
462451
}
463452
// This resets pending edges.
464-
if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil {
453+
if err := p.ioctlPin.In(gpio.PullNoChange, edge); err != nil {
465454
return p.wrap(err)
466455
}
467456
p.usingEdge = true
@@ -474,10 +463,10 @@ func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
474463
// This function is fast. It works even if the pin is set as output.
475464
func (p *Pin) Read() gpio.Level {
476465
if drvGPIO.gpioMemory == nil {
477-
if p.sysfsPin == nil {
466+
if p.ioctlPin == nil {
478467
return gpio.Low
479468
}
480-
return p.sysfsPin.Read()
469+
return p.ioctlPin.Read()
481470
}
482471
if p.number < 32 {
483472
// Important: do not remove the &31 here even if not necessary. Testing
@@ -501,8 +490,8 @@ func (p *Pin) FastRead() gpio.Level {
501490

502491
// WaitForEdge implements gpio.PinIn.
503492
func (p *Pin) WaitForEdge(timeout time.Duration) bool {
504-
if p.sysfsPin != nil {
505-
return p.sysfsPin.WaitForEdge(timeout)
493+
if p.ioctlPin != nil {
494+
return p.ioctlPin.WaitForEdge(timeout)
506495
}
507496
return false
508497
}
@@ -512,7 +501,7 @@ func (p *Pin) WaitForEdge(timeout time.Duration) bool {
512501
// bcm2711/bcm2838 support querying the pull resistor of all GPIO pins. Prior
513502
// to it, bcm283x doesn't support querying the pull resistor of any GPIO pin.
514503
func (p *Pin) Pull() gpio.Pull {
515-
// sysfs does not have the capability to read pull resistor.
504+
// gpioioctl does not have the capability to read pull resistor.
516505
if drvGPIO.gpioMemory != nil {
517506
if drvGPIO.useLegacyPull {
518507
// TODO(maruel): The best that could be added is to cache the last set value
@@ -545,10 +534,10 @@ func (p *Pin) DefaultPull() gpio.Pull {
545534
// Fails if requesting to change a pin that is set to special functionality.
546535
func (p *Pin) Out(l gpio.Level) error {
547536
if drvGPIO.gpioMemory == nil {
548-
if p.sysfsPin == nil {
549-
return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible"))
537+
if p.ioctlPin == nil {
538+
return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible"))
550539
}
551-
return p.sysfsPin.Out(l)
540+
return p.ioctlPin.Out(l)
552541
}
553542
// TODO(maruel): This function call is very costly.
554543
if err := p.Halt(); err != nil {
@@ -973,7 +962,7 @@ var cpuPins = []Pin{
973962
// Mapping may be overridden during driverGPIO.Init().
974963
var mapping = mapping283x
975964

976-
// BCM238x specific alternate function mapping (the default).
965+
// BCM283x specific alternate function mapping (the default).
977966
var mapping283x = [][6]pin.Func{
978967
{"I2C0_SDA"}, // 0
979968
{"I2C0_SCL"},
@@ -1331,17 +1320,20 @@ func (d *driverGPIO) String() string {
13311320
}
13321321

13331322
func (d *driverGPIO) Prerequisites() []string {
1334-
return nil
1323+
return []string{"ioctl-gpio"}
13351324
}
13361325

13371326
func (d *driverGPIO) After() []string {
1338-
return []string{"sysfs-gpio"}
1327+
return nil
13391328
}
13401329

13411330
func (d *driverGPIO) Init() (bool, error) {
13421331
if !Present() {
13431332
return false, errors.New("bcm283x CPU not detected")
13441333
}
1334+
if len(gpioioctl.Chips) == 0 {
1335+
return false, errors.New("gpioioctl not initialized")
1336+
}
13451337
// It's kind of messy, some report bcm283x while others show bcm27xx.
13461338
// Let's play safe here.
13471339
dTCompatible := strings.Join(distro.DTCompatible(), " ")
@@ -1381,17 +1373,18 @@ func (d *driverGPIO) Init() (bool, error) {
13811373
d.gpioBaseAddr = d.baseAddr + 0x200000
13821374

13831375
// Mark the right pins as available even if the memory map fails so they can
1384-
// callback to sysfs.Pins.
1376+
// callback to gpioioctl.Pins.
13851377
functions := map[pin.Func]struct{}{}
1378+
lines := gpioioctl.Chips[0].Lines()
13861379
for i := range cpuPins {
13871380
name := cpuPins[i].name
13881381
num := strconv.Itoa(cpuPins[i].number)
13891382

1390-
// Initializes the sysfs corresponding pin right away.
1391-
cpuPins[i].sysfsPin = sysfs.Pins[cpuPins[i].number]
1383+
// Initializes the ioctl corresponding pin right away.
1384+
cpuPins[i].ioctlPin = lines[cpuPins[i].number]
13921385

1393-
// Unregister the pin if already registered. This happens with sysfs-gpio.
1394-
// Do not error on it, since sysfs-gpio may have failed to load.
1386+
// Unregister the pin if already registered. This happens with ioctl-gpio.
1387+
// Do not error on it, since ioctl-gpio may have failed to load.
13951388
_ = gpioreg.Unregister(name)
13961389
_ = gpioreg.Unregister(num)
13971390

bcm283x/gpio_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"periph.io/x/conn/v3/pin"
1616
"periph.io/x/conn/v3/spi"
1717
"periph.io/x/conn/v3/uart"
18+
_ "periph.io/x/host/v3/gpioioctl"
1819
"periph.io/x/host/v3/pmem"
1920
"periph.io/x/host/v3/videocore"
2021
)
@@ -315,7 +316,7 @@ func TestDriver(t *testing.T) {
315316
if s := drvGPIO.String(); s != "bcm283x-gpio" {
316317
t.Fatal(s)
317318
}
318-
if s := drvGPIO.Prerequisites(); s != nil {
319+
if s := drvGPIO.Prerequisites(); s == nil {
319320
t.Fatal(s)
320321
}
321322
// It will fail to initialize on non-bcm.

gpioioctl/basic_test.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
// periph.io/x/cmd/periph-smoketest/gpiosmoketest
77
// folder.
88

9-
//go:build linux
10-
119
package gpioioctl
1210

1311
import (
@@ -24,11 +22,7 @@ func init() {
2422
var err error
2523

2624
if len(Chips) == 0 {
27-
/*
28-
During pipeline builds, GPIOChips may not be available, or
29-
it may build on another OS. In that case, mock in enough
30-
for a test to pass.
31-
*/
25+
makeDummyChip()
3226
line := GPIOLine{
3327
number: 0,
3428
name: "DummyGPIOLine",

gpioioctl/dummy.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2024 The Periph Authors. All rights reserved.
2+
// Use of this source code is governed under the Apache License, Version 2.0
3+
// that can be found in the LICENSE file.
4+
//
5+
// Create a dummy chip for testing an non-Linux os.
6+
7+
package gpioioctl
8+
9+
import (
10+
"log"
11+
12+
"periph.io/x/conn/v3/gpio"
13+
"periph.io/x/conn/v3/gpio/gpioreg"
14+
)
15+
16+
func makeDummyChip() {
17+
/*
18+
During pipeline builds, GPIOChips may not be available, or
19+
it may build on another OS. In that case, mock in enough
20+
for a test to pass.
21+
*/
22+
23+
line := GPIOLine{
24+
number: 0,
25+
name: "DummyGPIOLine",
26+
consumer: "",
27+
edge: gpio.NoEdge,
28+
pull: gpio.PullNoChange,
29+
direction: LineDirNotSet,
30+
}
31+
32+
chip := GPIOChip{name: "DummyGPIOChip",
33+
path: "/dev/gpiochipdummy",
34+
label: "Dummy GPIOChip for Testing Purposes",
35+
lineCount: 1,
36+
lines: []*GPIOLine{&line},
37+
}
38+
Chips = append(Chips, &chip)
39+
Chips = append(Chips, &chip)
40+
if err := gpioreg.Register(&line); err != nil {
41+
nameStr := chip.Name()
42+
lineStr := line.String()
43+
log.Println("chip", nameStr, " gpioreg.Register(line) ", lineStr, " returned ", err)
44+
}
45+
}

gpioioctl/example_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//go:build linux
2-
31
package gpioioctl_test
42

53
// Copyright 2024 The Periph Authors. All rights reserved.

0 commit comments

Comments
 (0)