Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gpioioctl:Implement ioctl access to Linux GPIO chips/lines. #59

Merged
merged 32 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f2576ad
Implement ioctl access to Linux GPIO chips/lines.
gsexton Aug 28, 2024
3606c9b
Restructure tests. Remoe exposted fields from Chip and replace with g…
gsexton Sep 2, 2024
fde9c61
Remove obsoleted tests
gsexton Sep 2, 2024
5a2bd2e
Restore deleted files from sysfs
gsexton Sep 4, 2024
7e310bb
Changes for review comments
gsexton Sep 8, 2024
a4bd079
Pickup missed file
gsexton Sep 8, 2024
ac0bfa3
Fix prerequisite
gsexton Sep 8, 2024
7105f30
Cleanup JSON Marshaling for String()
gsexton Sep 9, 2024
9f4e183
Fix tests
gsexton Sep 9, 2024
6ff2ec3
Fix versions in test
gsexton Sep 9, 2024
1010695
try again
gsexton Sep 9, 2024
ad454c1
Remove String() funcs made for debugging since they're not really nee…
gsexton Sep 9, 2024
91c118d
Add gpioioctl initialization to host since it's required, and netlink…
gsexton Sep 9, 2024
0a36c23
gofmt
gsexton Sep 9, 2024
f80454e
generalize comment
gsexton Sep 9, 2024
ec651b5
Fix lint errors on various close statements.
gsexton Sep 9, 2024
9e052c9
Fix lint errors on various close statements.
gsexton Sep 9, 2024
7d39120
Fix test failure
gsexton Sep 9, 2024
342cb00
Fix commit
gsexton Sep 9, 2024
8cf3652
Fix sysfs test
gsexton Sep 9, 2024
dd8d970
Add conditional compilation
gsexton Sep 12, 2024
d6353a3
gofmt
gsexton Sep 12, 2024
1b97518
Revert go.mod
gsexton Sep 12, 2024
21d9499
Fix lint error on shadowed variable.
gsexton Sep 12, 2024
0f05e40
Lint fixes
gsexton Sep 12, 2024
2eaa0bc
Add timeout to onewire init to ensure pipeline compatibility.
gsexton Sep 12, 2024
47b0b70
add fd to socket. sort of necessary because the case of s to interfac…
gsexton Sep 12, 2024
0ffd4b2
Add conditional compilation to deal with OS differences
gsexton Sep 12, 2024
6990635
add license
gsexton Sep 12, 2024
1edbb95
Fix typo
gsexton Sep 12, 2024
aaec1cc
Revert init changes
gsexton Sep 12, 2024
9204ccc
More revert
gsexton Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions gpioioctl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# GPIO IOCTL

This directory contains an implementation for Linux GPIO manipulation
using the ioctl v2 interface.

Basic test is provided, but a much more complete smoke test is provided
in periph.io/x/cmd/periph-smoketest/gpiosmoketest
140 changes: 140 additions & 0 deletions gpioioctl/basic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

// Basic tests. More complete test is contained in the
// periph.io/x/cmd/periph-smoketest/gpiosmoketest
// folder.

//go:build linux

package gpioioctl

import (
"log"
"testing"

"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/gpio/gpioreg"
)

var testLine *GPIOLine

func init() {
var err error

if len(Chips) == 0 {
/*
During pipeline builds, GPIOChips may not be available, or
it may build on another OS. In that case, mock in enough
for a test to pass.
*/
line := GPIOLine{
number: 0,
name: "DummyGPIOLine",
consumer: "",
edge: gpio.NoEdge,
pull: gpio.PullNoChange,
direction: LineDirNotSet,
}

chip := GPIOChip{name: "DummyGPIOChip",
path: "/dev/gpiochipdummy",
label: "Dummy GPIOChip for Testing Purposes",
lineCount: 1,
lines: []*GPIOLine{&line},
}
Chips = append(Chips, &chip)
if err = gpioreg.Register(&line); err != nil {
nameStr := chip.Name()
lineStr := line.String()
log.Println("chip", nameStr, " gpioreg.Register(line) ", lineStr, " returned ", err)
}
}
}

func TestChips(t *testing.T) {
chip := Chips[0]
t.Log(chip.String())
if len(chip.Name()) == 0 {
t.Error("chip.Name() is 0 length")
}
if len(chip.Path()) == 0 {
t.Error("chip path is 0 length")
}
if len(chip.Label()) == 0 {
t.Error("chip label is 0 length!")
}
if len(chip.Lines()) != chip.LineCount() {
t.Errorf("Incorrect line count. Found: %d for LineCount, Returned Lines length=%d", chip.LineCount(), len(chip.Lines()))
}
for _, line := range chip.Lines() {
if len(line.Consumer()) == 0 && len(line.Name()) > 0 {
testLine = line
break
}
}
if testLine == nil {
t.Error("Error finding unused line for testing!")
}
for _, c := range Chips {
s := c.String()
if len(s) == 0 {
t.Error("Error calling chip.String(). No output returned!")
} else {
t.Log(s)
}

}

}

func TestGPIORegistryByName(t *testing.T) {
if testLine == nil {
return
}
outLine := gpioreg.ByName(testLine.Name())
if outLine == nil {
t.Fatalf("Error retrieving GPIO Line %s", testLine.Name())
}
if outLine.Name() != testLine.Name() {
t.Errorf("Error checking name. Expected %s, received %s", testLine.Name(), outLine.Name())
}

if outLine.Number() < 0 || outLine.Number() >= len(Chips[0].Lines()) {
t.Errorf("Invalid chip number %d received for %s", outLine.Number(), testLine.Name())
}
}

func TestNumber(t *testing.T) {
chip := Chips[0]
if testLine == nil {
return
}
l := chip.ByName(testLine.Name())
if l == nil {
t.Fatalf("Error retrieving GPIO Line %s", testLine.Name())
}
if l.Number() < 0 || l.Number() >= chip.LineCount() {
t.Errorf("line.Number() returned value (%d) out of range", l.Number())
}
l2 := chip.ByNumber(l.Number())
if l2 == nil {
t.Errorf("retrieve Line from chip by number %d failed.", l.Number())
}

}

func TestString(t *testing.T) {
if testLine == nil {
return
}
line := gpioreg.ByName(testLine.Name())
if line == nil {
t.Fatalf("Error retrieving GPIO Line %s", testLine.Name())
}
s := line.String()
if len(s) == 0 {
t.Errorf("GPIOLine.String() failed.")
}
}
15 changes: 15 additions & 0 deletions gpioioctl/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
//
// Package gpioioctl provides access to Linux GPIO lines using the ioctl interface.
//
// https://docs.kernel.org/userspace-api/gpio/index.html
//
// GPIO Pins can be accessed via periph.io/x/conn/v3/gpio/gpioreg,
// or using the Chips collection to access the specific GPIO chip
// and using it's ByName()/ByNumber methods.
//
// GPIOChip provides a LineSet feature that allows you to atomically
// read/write to multiple GPIO pins as a single operation.
package gpioioctl
105 changes: 105 additions & 0 deletions gpioioctl/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//go:build linux

package gpioioctl_test

// Copyright 2024 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

import (
"fmt"
"log"
"time"

"periph.io/x/conn/v3/driver/driverreg"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/gpio/gpioreg"
"periph.io/x/host/v3"
"periph.io/x/host/v3/gpioioctl"
)

func Example() {
_, _ = host.Init()
_, _ = driverreg.Init()

fmt.Println("GPIO Test Program")
chip := gpioioctl.Chips[0]
defer chip.Close()
fmt.Println(chip.String())
// Test by flashing an LED.
led := gpioreg.ByName("GPIO5")
fmt.Println("Flashing LED ", led.Name())
for i := range 20 {
_ = led.Out((i % 2) == 0)
time.Sleep(500 * time.Millisecond)
}
_ = led.Out(true)

testRotary(chip, "GPIO20", "GPIO21", "GPIO19")
gsexton marked this conversation as resolved.
Show resolved Hide resolved
}

// Test the LineSet functionality by using it to read a Rotary Encoder w/ Button.
func testRotary(chip *gpioioctl.GPIOChip, stateLine, dataLine, buttonLine string) {
config := gpioioctl.LineSetConfig{DefaultDirection: gpioioctl.LineInput, DefaultEdge: gpio.RisingEdge, DefaultPull: gpio.PullUp}
config.Lines = []string{stateLine, dataLine, buttonLine}
// The Data Pin of the Rotary Encoder should NOT have an edge.
_ = config.AddOverrides(gpioioctl.LineInput, gpio.NoEdge, gpio.PullUp, dataLine)
ls, err := chip.LineSetFromConfig(&config)
if err != nil {
log.Fatal(err)
}
defer ls.Close()
statePinNumber := uint32(ls.ByOffset(0).Number())
buttonPinNumber := uint32(ls.ByOffset(2).Number())

var tLast = time.Now().Add(-1 * time.Second)
var halting bool
go func() {
time.Sleep(60 * time.Second)
halting = true
fmt.Println("Sending halt!")
_ = ls.Halt()
}()
fmt.Println("Test Rotary Switch - Turn dial to test rotary encoder, press button to test it.")
for {
lineNumber, _, err := ls.WaitForEdge(0)
if err == nil {
tNow := time.Now()
if (tNow.UnixMilli() - tLast.UnixMilli()) < 100 {
continue
}
tLast = tNow
if lineNumber == statePinNumber {
var bits uint64
tDeadline := tNow.UnixNano() + 20_000_000
var consecutive uint64
for time.Now().UnixNano() < tDeadline {
// Spin on reading the pins until we get some number
// of consecutive readings that are the same.
bits, _ = ls.Read(0x03)
if bits&0x01 == 0x00 {
// We're bouncing.
consecutive = 0
} else {
consecutive += 1
if consecutive > 25 {
if bits == 0x01 {
fmt.Printf("Clockwise bits=%d\n", bits)
} else if bits == 0x03 {
fmt.Printf("CounterClockwise bits=%d\n", bits)
}
break
}
}
}
} else if lineNumber == buttonPinNumber {
fmt.Println("Button Pressed!")
}
} else {
fmt.Println("Timeout detected")
if halting {
break
}
}
}
}
Loading
Loading