521 lines
13 KiB
Go
521 lines
13 KiB
Go
// Copyright 2016 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 sysfs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"periph.io/x/periph"
|
|
"periph.io/x/periph/conn"
|
|
"periph.io/x/periph/conn/gpio"
|
|
"periph.io/x/periph/conn/gpio/gpioreg"
|
|
"periph.io/x/periph/conn/physic"
|
|
"periph.io/x/periph/conn/pin"
|
|
"periph.io/x/periph/host/fs"
|
|
)
|
|
|
|
// Pins is all the pins exported by GPIO sysfs.
|
|
//
|
|
// Some CPU architectures have the pin numbers start at 0 and use consecutive
|
|
// pin numbers but this is not the case for all CPU architectures, some
|
|
// have gaps in the pin numbering.
|
|
//
|
|
// This global variable is initialized once at driver initialization and isn't
|
|
// mutated afterward. Do not modify it.
|
|
var Pins map[int]*Pin
|
|
|
|
// Pin represents one GPIO pin as found by sysfs.
|
|
type Pin struct {
|
|
number int
|
|
name string
|
|
root string // Something like /sys/class/gpio/gpio%d/
|
|
|
|
mu sync.Mutex
|
|
err error // If open() failed
|
|
direction direction // Cache of the last known direction
|
|
edge gpio.Edge // Cache of the last edge used.
|
|
fDirection fileIO // handle to /sys/class/gpio/gpio*/direction; never closed
|
|
fEdge fileIO // handle to /sys/class/gpio/gpio*/edge; never closed
|
|
fValue fileIO // handle to /sys/class/gpio/gpio*/value; never closed
|
|
event fs.Event // Initialized once
|
|
buf [4]byte // scratch buffer for Func(), Read() and Out()
|
|
}
|
|
|
|
// String implements conn.Resource.
|
|
func (p *Pin) String() string {
|
|
return p.name
|
|
}
|
|
|
|
// Halt implements conn.Resource.
|
|
//
|
|
// It stops edge detection if enabled.
|
|
func (p *Pin) Halt() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
return p.haltEdge()
|
|
}
|
|
|
|
// Name implements pin.Pin.
|
|
func (p *Pin) Name() string {
|
|
return p.name
|
|
}
|
|
|
|
// Number implements pin.Pin.
|
|
func (p *Pin) Number() int {
|
|
return p.number
|
|
}
|
|
|
|
// Function implements pin.Pin.
|
|
func (p *Pin) Function() string {
|
|
return string(p.Func())
|
|
}
|
|
|
|
// Func implements pin.PinFunc.
|
|
func (p *Pin) Func() pin.Func {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
// TODO(maruel): There's an internal bug which causes p.direction to be
|
|
// invalid (!?) Need to figure it out ASAP.
|
|
if err := p.open(); err != nil {
|
|
return pin.FuncNone
|
|
}
|
|
if _, err := seekRead(p.fDirection, p.buf[:]); err != nil {
|
|
return pin.FuncNone
|
|
}
|
|
if p.buf[0] == 'i' && p.buf[1] == 'n' {
|
|
p.direction = dIn
|
|
} else if p.buf[0] == 'o' && p.buf[1] == 'u' && p.buf[2] == 't' {
|
|
p.direction = dOut
|
|
}
|
|
if p.direction == dIn {
|
|
if p.Read() {
|
|
return gpio.IN_HIGH
|
|
}
|
|
return gpio.IN_LOW
|
|
} else if p.direction == dOut {
|
|
if p.Read() {
|
|
return gpio.OUT_HIGH
|
|
}
|
|
return gpio.OUT_LOW
|
|
}
|
|
return pin.FuncNone
|
|
}
|
|
|
|
// SupportedFuncs implements pin.PinFunc.
|
|
func (p *Pin) SupportedFuncs() []pin.Func {
|
|
return []pin.Func{gpio.IN, gpio.OUT}
|
|
}
|
|
|
|
// SetFunc implements pin.PinFunc.
|
|
func (p *Pin) SetFunc(f pin.Func) error {
|
|
switch f {
|
|
case gpio.IN:
|
|
return p.In(gpio.PullNoChange, gpio.NoEdge)
|
|
case gpio.OUT_HIGH:
|
|
return p.Out(gpio.High)
|
|
case gpio.OUT, gpio.OUT_LOW:
|
|
return p.Out(gpio.Low)
|
|
default:
|
|
return p.wrap(errors.New("unsupported function"))
|
|
}
|
|
}
|
|
|
|
// In implements gpio.PinIn.
|
|
func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error {
|
|
if pull != gpio.PullNoChange && pull != gpio.Float {
|
|
return p.wrap(errors.New("doesn't support pull-up/pull-down"))
|
|
}
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if p.direction != dIn {
|
|
if err := p.open(); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
if err := seekWrite(p.fDirection, bIn); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
p.direction = dIn
|
|
}
|
|
// Always push none to help accumulated flush edges. This is not fool proof
|
|
// but it seems to help.
|
|
if p.fEdge != nil {
|
|
if err := seekWrite(p.fEdge, bNone); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
}
|
|
// Assume that when the pin was switched, the driver doesn't recall if edge
|
|
// triggering was enabled.
|
|
if edge != gpio.NoEdge {
|
|
if p.fEdge == nil {
|
|
var err error
|
|
p.fEdge, err = fileIOOpen(p.root+"edge", os.O_RDWR)
|
|
if err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
if err = p.event.MakeEvent(p.fValue.Fd()); err != nil {
|
|
_ = p.fEdge.Close()
|
|
p.fEdge = nil
|
|
return p.wrap(err)
|
|
}
|
|
}
|
|
// Always reset the edge detection mode to none after starting the epoll
|
|
// otherwise edges are not always delivered, as observed on an Allwinner A20
|
|
// running kernel 4.14.14.
|
|
if err := seekWrite(p.fEdge, bNone); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
var b []byte
|
|
switch edge {
|
|
case gpio.RisingEdge:
|
|
b = bRising
|
|
case gpio.FallingEdge:
|
|
b = bFalling
|
|
case gpio.BothEdges:
|
|
b = bBoth
|
|
}
|
|
if err := seekWrite(p.fEdge, b); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
}
|
|
p.edge = edge
|
|
// This helps to remove accumulated edges but this is not 100% sufficient.
|
|
// Most of the time the interrupts are handled promptly enough that this loop
|
|
// flushes the accumulated interrupt.
|
|
// Sometimes the kernel may have accumulated interrupts that haven't been
|
|
// processed for a long time, it can easily be >300µs even on a quite idle
|
|
// CPU. In this case, the loop below is not sufficient, since the interrupt
|
|
// will happen afterward "out of the blue".
|
|
if edge != gpio.NoEdge {
|
|
p.WaitForEdge(0)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Read implements gpio.PinIn.
|
|
func (p *Pin) Read() gpio.Level {
|
|
// There's no lock here.
|
|
if p.fValue == nil {
|
|
return gpio.Low
|
|
}
|
|
if _, err := seekRead(p.fValue, p.buf[:]); err != nil {
|
|
// Error.
|
|
return gpio.Low
|
|
}
|
|
if p.buf[0] == '0' {
|
|
return gpio.Low
|
|
}
|
|
if p.buf[0] == '1' {
|
|
return gpio.High
|
|
}
|
|
// Error.
|
|
return gpio.Low
|
|
}
|
|
|
|
// WaitForEdge implements gpio.PinIn.
|
|
func (p *Pin) WaitForEdge(timeout time.Duration) bool {
|
|
// Run lockless, as the normal use is to call in a busy loop.
|
|
var ms int
|
|
if timeout == -1 {
|
|
ms = -1
|
|
} else {
|
|
ms = int(timeout / time.Millisecond)
|
|
}
|
|
start := time.Now()
|
|
for {
|
|
if nr, err := p.event.Wait(ms); err != nil {
|
|
return false
|
|
} else if nr == 1 {
|
|
// TODO(maruel): According to pigpio, the correct way to consume the
|
|
// interrupt is to call Seek().
|
|
return true
|
|
}
|
|
// A signal occurred.
|
|
if timeout != -1 {
|
|
ms = int((timeout - time.Since(start)) / time.Millisecond)
|
|
}
|
|
if ms <= 0 {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pull implements gpio.PinIn.
|
|
//
|
|
// It returns gpio.PullNoChange since gpio sysfs has no support for input pull
|
|
// resistor.
|
|
func (p *Pin) Pull() gpio.Pull {
|
|
return gpio.PullNoChange
|
|
}
|
|
|
|
// DefaultPull implements gpio.PinIn.
|
|
//
|
|
// It returns gpio.PullNoChange since gpio sysfs has no support for input pull
|
|
// resistor.
|
|
func (p *Pin) DefaultPull() gpio.Pull {
|
|
return gpio.PullNoChange
|
|
}
|
|
|
|
// Out implements gpio.PinOut.
|
|
func (p *Pin) Out(l gpio.Level) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if p.direction != dOut {
|
|
if err := p.open(); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
if err := p.haltEdge(); err != nil {
|
|
return err
|
|
}
|
|
// "To ensure glitch free operation, values "low" and "high" may be written
|
|
// to configure the GPIO as an output with that initial value."
|
|
var d []byte
|
|
if l == gpio.Low {
|
|
d = bLow
|
|
} else {
|
|
d = bHigh
|
|
}
|
|
if err := seekWrite(p.fDirection, d); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
p.direction = dOut
|
|
return nil
|
|
}
|
|
if l == gpio.Low {
|
|
p.buf[0] = '0'
|
|
} else {
|
|
p.buf[0] = '1'
|
|
}
|
|
if err := seekWrite(p.fValue, p.buf[:1]); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PWM implements gpio.PinOut.
|
|
//
|
|
// This is not supported on sysfs.
|
|
func (p *Pin) PWM(gpio.Duty, physic.Frequency) error {
|
|
return p.wrap(errors.New("pwm is not supported via sysfs"))
|
|
}
|
|
|
|
//
|
|
|
|
// open opens the gpio sysfs handle to /value and /direction.
|
|
//
|
|
// lock must be held.
|
|
func (p *Pin) open() error {
|
|
if p.fDirection != nil || p.err != nil {
|
|
return p.err
|
|
}
|
|
|
|
if drvGPIO.exportHandle == nil {
|
|
return errors.New("sysfs gpio is not initialized")
|
|
}
|
|
|
|
// Try to open the pin if it was there. It's possible it had been exported
|
|
// already.
|
|
if p.fValue, p.err = fileIOOpen(p.root+"value", os.O_RDWR); p.err == nil {
|
|
// Fast track.
|
|
goto direction
|
|
} else if !os.IsNotExist(p.err) {
|
|
// It exists but not accessible, not worth doing the remainder.
|
|
p.err = fmt.Errorf("need more access, try as root or setup udev rules: %v", p.err)
|
|
return p.err
|
|
}
|
|
|
|
if _, p.err = drvGPIO.exportHandle.Write([]byte(strconv.Itoa(p.number))); p.err != nil && !isErrBusy(p.err) {
|
|
if os.IsPermission(p.err) {
|
|
p.err = fmt.Errorf("need more access, try as root or setup udev rules: %v", p.err)
|
|
}
|
|
return p.err
|
|
}
|
|
|
|
// There's a race condition where the file may be created but udev is still
|
|
// running the Raspbian udev rule to make it readable to the current user.
|
|
// It's simpler to just loop a little as if /export is accessible, it doesn't
|
|
// make sense that gpioN/value doesn't become accessible eventually.
|
|
for start := time.Now(); time.Since(start) < 5*time.Second; {
|
|
// The virtual file creation is synchronous when writing to /export; albeit
|
|
// udev rule execution is asynchronous, so file mode change via udev rules
|
|
// takes some time to propagate.
|
|
if p.fValue, p.err = fileIOOpen(p.root+"value", os.O_RDWR); p.err == nil || !os.IsPermission(p.err) {
|
|
// Either success or a failure that is not a permission error.
|
|
break
|
|
}
|
|
}
|
|
if p.err != nil {
|
|
return p.err
|
|
}
|
|
|
|
direction:
|
|
if p.fDirection, p.err = fileIOOpen(p.root+"direction", os.O_RDWR); p.err != nil {
|
|
_ = p.fValue.Close()
|
|
p.fValue = nil
|
|
}
|
|
return p.err
|
|
}
|
|
|
|
// haltEdge stops any on-going edge detection.
|
|
func (p *Pin) haltEdge() error {
|
|
if p.edge != gpio.NoEdge {
|
|
if err := seekWrite(p.fEdge, bNone); err != nil {
|
|
return p.wrap(err)
|
|
}
|
|
p.edge = gpio.NoEdge
|
|
// This is still important to remove an accumulated edge.
|
|
p.WaitForEdge(0)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Pin) wrap(err error) error {
|
|
return fmt.Errorf("sysfs-gpio (%s): %v", p, err)
|
|
}
|
|
|
|
//
|
|
|
|
type direction int
|
|
|
|
const (
|
|
dUnknown direction = 0
|
|
dIn direction = 1
|
|
dOut direction = 2
|
|
)
|
|
|
|
var (
|
|
bIn = []byte("in")
|
|
bLow = []byte("low")
|
|
bHigh = []byte("high")
|
|
bNone = []byte("none")
|
|
bRising = []byte("rising")
|
|
bFalling = []byte("falling")
|
|
bBoth = []byte("both")
|
|
)
|
|
|
|
// readInt reads a pseudo-file (sysfs) that is known to contain an integer and
|
|
// returns the parsed number.
|
|
func readInt(path string) (int, error) {
|
|
f, err := fileIOOpen(path, os.O_RDONLY)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
var b [24]byte
|
|
n, err := f.Read(b[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
raw := b[:n]
|
|
if len(raw) == 0 || raw[len(raw)-1] != '\n' {
|
|
return 0, errors.New("invalid value")
|
|
}
|
|
return strconv.Atoi(string(raw[:len(raw)-1]))
|
|
}
|
|
|
|
// driverGPIO implements periph.Driver.
|
|
type driverGPIO struct {
|
|
exportHandle io.Writer // handle to /sys/class/gpio/export
|
|
}
|
|
|
|
func (d *driverGPIO) String() string {
|
|
return "sysfs-gpio"
|
|
}
|
|
|
|
func (d *driverGPIO) Prerequisites() []string {
|
|
return nil
|
|
}
|
|
|
|
func (d *driverGPIO) After() []string {
|
|
return nil
|
|
}
|
|
|
|
// Init initializes GPIO sysfs handling code.
|
|
//
|
|
// Uses gpio sysfs as described at
|
|
// https://www.kernel.org/doc/Documentation/gpio/sysfs.txt
|
|
//
|
|
// GPIO sysfs is often the only way to do edge triggered interrupts. Doing this
|
|
// requires cooperation from a driver in the kernel.
|
|
//
|
|
// The main drawback of GPIO sysfs is that it doesn't expose internal pull
|
|
// resistor and it is much slower than using memory mapped hardware registers.
|
|
func (d *driverGPIO) Init() (bool, error) {
|
|
items, err := filepath.Glob("/sys/class/gpio/gpiochip*")
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
if len(items) == 0 {
|
|
return false, errors.New("no GPIO pin found")
|
|
}
|
|
|
|
// There are hosts that use non-continuous pin numbering so use a map instead
|
|
// of an array.
|
|
Pins = map[int]*Pin{}
|
|
for _, item := range items {
|
|
if err := d.parseGPIOChip(item + "/"); err != nil {
|
|
return true, err
|
|
}
|
|
}
|
|
drvGPIO.exportHandle, err = fileIOOpen("/sys/class/gpio/export", os.O_WRONLY)
|
|
if os.IsPermission(err) {
|
|
return true, fmt.Errorf("need more access, try as root or setup udev rules: %v", err)
|
|
}
|
|
return true, err
|
|
}
|
|
|
|
func (d *driverGPIO) parseGPIOChip(path string) error {
|
|
base, err := readInt(path + "base")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
number, err := readInt(path + "ngpio")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO(maruel): The chip driver may lie and lists GPIO pins that cannot be
|
|
// exported. The only way to know about it is to export it before opening.
|
|
for i := base; i < base+number; i++ {
|
|
if _, ok := Pins[i]; ok {
|
|
return fmt.Errorf("found two pins with number %d", i)
|
|
}
|
|
p := &Pin{
|
|
number: i,
|
|
name: fmt.Sprintf("GPIO%d", i),
|
|
root: fmt.Sprintf("/sys/class/gpio/gpio%d/", i),
|
|
}
|
|
Pins[i] = p
|
|
if err := gpioreg.Register(p); err != nil {
|
|
return err
|
|
}
|
|
// If there is a CPU memory mapped gpio pin with the same number, the
|
|
// driver has to unregister this pin and map its own after.
|
|
if err := gpioreg.RegisterAlias(strconv.Itoa(i), p.name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
if isLinux {
|
|
periph.MustRegister(&drvGPIO)
|
|
}
|
|
}
|
|
|
|
var drvGPIO driverGPIO
|
|
|
|
var _ conn.Resource = &Pin{}
|
|
var _ gpio.PinIn = &Pin{}
|
|
var _ gpio.PinOut = &Pin{}
|
|
var _ gpio.PinIO = &Pin{}
|
|
var _ pin.PinFunc = &Pin{}
|