From 1310b957de288b074bdfc18d0babc5ff81f5ce2c Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 11:49:26 -0600 Subject: [PATCH 1/9] Change bcm283x to use ioctl for GPIO pins, not sysfs. --- bcm283x/doc.go | 6 +++- bcm283x/gpio.go | 79 ++++++++++++++++++++------------------------ bcm283x/gpio_test.go | 3 +- gpioioctl/gpio.go | 47 +++++++++++++++++++++++--- 4 files changed, 85 insertions(+), 50 deletions(-) diff --git a/bcm283x/doc.go b/bcm283x/doc.go index 0bbea729..d7ff34a3 100644 --- a/bcm283x/doc.go +++ b/bcm283x/doc.go @@ -5,7 +5,7 @@ // Package bcm283x exposes the BCM283x GPIO functionality. // // This driver implements memory-mapped GPIO pin manipulation and leverages -// sysfs-gpio for edge detection. +// ioctl-gpio for edge detection. // // If you are looking for the actual implementation, open doc.go for further // implementation details. @@ -15,6 +15,10 @@ // Aliases for GPCLK0, GPCLK1, GPCLK2 are created for corresponding CLKn pins. // Same for PWM0_OUT and PWM1_OUT, which point respectively to PWM0 and PWM1. // +// For multi-pin IO, you should prefer using the /host/gpioioctl/GPIOChip.LineSet() +// functionality. It's chipset agnostic because it uses the ioctl interfaces, +// and it offers multi-pin WaitForEdge(). +// // # Datasheet // // https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf diff --git a/bcm283x/gpio.go b/bcm283x/gpio.go index b49a3d3b..84ed6e88 100644 --- a/bcm283x/gpio.go +++ b/bcm283x/gpio.go @@ -19,6 +19,7 @@ import ( "periph.io/x/conn/v3/physic" "periph.io/x/conn/v3/pin" "periph.io/x/host/v3/distro" + "periph.io/x/host/v3/gpioioctl" "periph.io/x/host/v3/pmem" "periph.io/x/host/v3/sysfs" "periph.io/x/host/v3/videocore" @@ -185,7 +186,7 @@ func PinsSetup28To45(drive physic.ElectricCurrent, slewLimit, hysteresis bool) e return nil } -// Pin is a GPIO number (GPIOnn) on BCM238(5|6|7). +// Pin is a GPIO number (GPIOnn) on BCM283(5|6|7). // // Pin implements gpio.PinIO. type Pin struct { @@ -195,7 +196,7 @@ type Pin struct { defaultPull gpio.Pull // Default pull at system boot, as per datasheet. // Immutable after driver initialization. - sysfsPin *sysfs.Pin // Set to the corresponding sysfs.Pin, if any. + ioctlPin *gpioioctl.GPIOLine // Set to the corresponding gpioioctl.GPIOLine, if any. // Mutable. usingEdge bool // Set when edge detection is enabled. @@ -217,7 +218,7 @@ func (p *Pin) String() string { // disabled. func (p *Pin) Halt() error { if p.usingEdge { - if err := p.sysfsPin.Halt(); err != nil { + if err := p.ioctlPin.Halt(); err != nil { return p.wrap(err) } p.usingEdge = false @@ -245,10 +246,10 @@ func (p *Pin) Function() string { // Func implements pin.PinFunc. func (p *Pin) Func() pin.Func { if drvGPIO.gpioMemory == nil { - if p.sysfsPin == nil { + if p.ioctlPin == nil { return pin.FuncNone } - return p.sysfsPin.Func() + return p.ioctlPin.Func() } switch f := p.function(); f { case in: @@ -311,10 +312,10 @@ func (p *Pin) SupportedFuncs() []pin.Func { // SetFunc implements pin.PinFunc. func (p *Pin) SetFunc(f pin.Func) error { if drvGPIO.gpioMemory == nil { - if p.sysfsPin == nil { - return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible")) + if p.ioctlPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible")) } - return p.sysfsPin.SetFunc(f) + return p.ioctlPin.SetFunc(f) } switch f { case gpio.FLOAT: @@ -369,29 +370,17 @@ func (p *Pin) SetFunc(f pin.Func) error { // possible to 'read back' what value was specified for each pin. // // Will fail if requesting to change a pin that is set to special functionality. -// -// Using edge detection requires opening a gpio sysfs file handle. On Raspbian, -// make sure the user is member of group 'gpio'. The pin will be exported at -// /sys/class/gpio/gpio*/. Note that the pin will not be unexported at -// shutdown. -// -// For edge detection, the processor samples the input at its CPU clock rate -// and looks for '011' to rising and '100' for falling detection to avoid -// glitches. Because gpio sysfs is used, the latency is unpredictable. func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error { if p.usingEdge && edge == gpio.NoEdge { - if err := p.sysfsPin.Halt(); err != nil { + if err := p.ioctlPin.Halt(); err != nil { return p.wrap(err) } } if drvGPIO.gpioMemory == nil { - if p.sysfsPin == nil { - return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible")) - } - if pull != gpio.PullNoChange { - return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized")) + if p.ioctlPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible")) } - if err := p.sysfsPin.In(pull, edge); err != nil { + if err := p.ioctlPin.In(pull, edge); err != nil { return p.wrap(err) } p.usingEdge = edge != gpio.NoEdge @@ -457,11 +446,11 @@ func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error { } } if edge != gpio.NoEdge { - if p.sysfsPin == nil { - return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.number)) + if p.ioctlPin == nil { + return p.wrap(fmt.Errorf("pin %d is not exported by gpioioctl", p.number)) } // This resets pending edges. - if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil { + if err := p.ioctlPin.In(gpio.PullNoChange, edge); err != nil { return p.wrap(err) } p.usingEdge = true @@ -474,10 +463,10 @@ func (p *Pin) In(pull gpio.Pull, edge gpio.Edge) error { // This function is fast. It works even if the pin is set as output. func (p *Pin) Read() gpio.Level { if drvGPIO.gpioMemory == nil { - if p.sysfsPin == nil { + if p.ioctlPin == nil { return gpio.Low } - return p.sysfsPin.Read() + return p.ioctlPin.Read() } if p.number < 32 { // Important: do not remove the &31 here even if not necessary. Testing @@ -501,8 +490,8 @@ func (p *Pin) FastRead() gpio.Level { // WaitForEdge implements gpio.PinIn. func (p *Pin) WaitForEdge(timeout time.Duration) bool { - if p.sysfsPin != nil { - return p.sysfsPin.WaitForEdge(timeout) + if p.ioctlPin != nil { + return p.ioctlPin.WaitForEdge(timeout) } return false } @@ -512,7 +501,7 @@ func (p *Pin) WaitForEdge(timeout time.Duration) bool { // bcm2711/bcm2838 support querying the pull resistor of all GPIO pins. Prior // to it, bcm283x doesn't support querying the pull resistor of any GPIO pin. func (p *Pin) Pull() gpio.Pull { - // sysfs does not have the capability to read pull resistor. + // gpioioctl does not have the capability to read pull resistor. if drvGPIO.gpioMemory != nil { if drvGPIO.useLegacyPull { // 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 { // Fails if requesting to change a pin that is set to special functionality. func (p *Pin) Out(l gpio.Level) error { if drvGPIO.gpioMemory == nil { - if p.sysfsPin == nil { - return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible")) + if p.ioctlPin == nil { + return p.wrap(errors.New("subsystem gpiomem not initialized and gpioioctl not accessible")) } - return p.sysfsPin.Out(l) + return p.ioctlPin.Out(l) } // TODO(maruel): This function call is very costly. if err := p.Halt(); err != nil { @@ -973,7 +962,7 @@ var cpuPins = []Pin{ // Mapping may be overridden during driverGPIO.Init(). var mapping = mapping283x -// BCM238x specific alternate function mapping (the default). +// BCM283x specific alternate function mapping (the default). var mapping283x = [][6]pin.Func{ {"I2C0_SDA"}, // 0 {"I2C0_SCL"}, @@ -1331,17 +1320,20 @@ func (d *driverGPIO) String() string { } func (d *driverGPIO) Prerequisites() []string { - return nil + return []string{"ioctl-gpio"} } func (d *driverGPIO) After() []string { - return []string{"sysfs-gpio"} + return nil } func (d *driverGPIO) Init() (bool, error) { if !Present() { return false, errors.New("bcm283x CPU not detected") } + if len(gpioioctl.Chips) == 0 { + return false, errors.New("gpioioctl not initialized.") + } // It's kind of messy, some report bcm283x while others show bcm27xx. // Let's play safe here. dTCompatible := strings.Join(distro.DTCompatible(), " ") @@ -1381,17 +1373,18 @@ func (d *driverGPIO) Init() (bool, error) { d.gpioBaseAddr = d.baseAddr + 0x200000 // Mark the right pins as available even if the memory map fails so they can - // callback to sysfs.Pins. + // callback to gpioioctl.Pins. functions := map[pin.Func]struct{}{} + lines := gpioioctl.Chips[0].Lines() for i := range cpuPins { name := cpuPins[i].name num := strconv.Itoa(cpuPins[i].number) - // Initializes the sysfs corresponding pin right away. - cpuPins[i].sysfsPin = sysfs.Pins[cpuPins[i].number] + // Initializes the ioctl corresponding pin right away. + cpuPins[i].ioctlPin = lines[cpuPins[i].number] - // Unregister the pin if already registered. This happens with sysfs-gpio. - // Do not error on it, since sysfs-gpio may have failed to load. + // Unregister the pin if already registered. This happens with ioctl-gpio. + // Do not error on it, since ioctl-gpio may have failed to load. _ = gpioreg.Unregister(name) _ = gpioreg.Unregister(num) diff --git a/bcm283x/gpio_test.go b/bcm283x/gpio_test.go index 447ce87a..59914d53 100644 --- a/bcm283x/gpio_test.go +++ b/bcm283x/gpio_test.go @@ -15,6 +15,7 @@ import ( "periph.io/x/conn/v3/pin" "periph.io/x/conn/v3/spi" "periph.io/x/conn/v3/uart" + _ "periph.io/x/host/v3/gpioioctl" "periph.io/x/host/v3/pmem" "periph.io/x/host/v3/videocore" ) @@ -315,7 +316,7 @@ func TestDriver(t *testing.T) { if s := drvGPIO.String(); s != "bcm283x-gpio" { t.Fatal(s) } - if s := drvGPIO.Prerequisites(); s != nil { + if s := drvGPIO.Prerequisites(); s == nil { t.Fatal(s) } // It will fail to initialize on non-bcm. diff --git a/gpioioctl/gpio.go b/gpioioctl/gpio.go index 2005c3ab..77368a3a 100644 --- a/gpioioctl/gpio.go +++ b/gpioioctl/gpio.go @@ -25,6 +25,7 @@ import ( "periph.io/x/conn/v3/gpio" "periph.io/x/conn/v3/gpio/gpioreg" "periph.io/x/conn/v3/physic" + "periph.io/x/conn/v3/pin" ) // LineDir is the configured direction of a GPIOLine. @@ -110,11 +111,6 @@ func (line *GPIOLine) DefaultPull() gpio.Pull { return gpio.PullNoChange } -// Deprecated: Use PinFunc.Func. Will be removed in v4. -func (line *GPIOLine) Function() string { - return "deprecated" -} - // Halt interrupts a pending WaitForEdge() command. func (line *GPIOLine) Halt() error { if line.fEdge != nil { @@ -301,6 +297,46 @@ func (line *GPIOLine) setLine(flags uint64) error { return ioctl_gpio_v2_line_config(uintptr(req_fd), &req) } +// Deprecated: Use PinFunc.Func. Will be removed in v4. Function implements pin.Pin. +func (line *GPIOLine) Function() string { + return string(line.Func()) +} + +// Func implements pin.PinFunc. +func (line *GPIOLine) Func() pin.Func { + if line.direction == LineInput { + if line.Read() { + return gpio.IN_HIGH + } + return gpio.IN_LOW + } else if line.direction == LineOutput { + if line.Read() { + return gpio.OUT_HIGH + } + return gpio.OUT_LOW + } + return pin.FuncNone +} + +// SupportedFuncs implements pin.PinFunc. +func (line *GPIOLine) SupportedFuncs() []pin.Func { + return []pin.Func{gpio.IN, gpio.OUT} +} + +// SetFunc implements pin.PinFunc. +func (line *GPIOLine) SetFunc(f pin.Func) error { + switch f { + case gpio.IN: + return line.In(gpio.PullNoChange, gpio.NoEdge) + case gpio.OUT_HIGH: + return line.Out(gpio.High) + case gpio.OUT, gpio.OUT_LOW: + return line.Out(gpio.Low) + default: + return errors.New("unsupported function") + } +} + // A representation of a Linux GPIO Chip. A computer may have // more than one GPIOChip. type GPIOChip struct { @@ -608,3 +644,4 @@ func init() { var _ gpio.PinIO = &GPIOLine{} var _ gpio.PinIn = &GPIOLine{} var _ gpio.PinOut = &GPIOLine{} +var _ pin.PinFunc = &GPIOLine{} From a479572c2029bcbd34cbd97db2a42ba2945cec8c Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 13:05:38 -0600 Subject: [PATCH 2/9] Fix lint/formatting --- bcm283x/doc.go | 4 ++-- bcm283x/gpio.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bcm283x/doc.go b/bcm283x/doc.go index d7ff34a3..51453cfe 100644 --- a/bcm283x/doc.go +++ b/bcm283x/doc.go @@ -15,9 +15,9 @@ // Aliases for GPCLK0, GPCLK1, GPCLK2 are created for corresponding CLKn pins. // Same for PWM0_OUT and PWM1_OUT, which point respectively to PWM0 and PWM1. // -// For multi-pin IO, you should prefer using the /host/gpioioctl/GPIOChip.LineSet() +// For multi-pin IO, you should prefer using the /host/gpioioctl/GPIOChip.LineSet() // functionality. It's chipset agnostic because it uses the ioctl interfaces, -// and it offers multi-pin WaitForEdge(). +// and it offers multi-pin WaitForEdge(). // // # Datasheet // diff --git a/bcm283x/gpio.go b/bcm283x/gpio.go index 84ed6e88..04e09924 100644 --- a/bcm283x/gpio.go +++ b/bcm283x/gpio.go @@ -1332,7 +1332,7 @@ func (d *driverGPIO) Init() (bool, error) { return false, errors.New("bcm283x CPU not detected") } if len(gpioioctl.Chips) == 0 { - return false, errors.New("gpioioctl not initialized.") + return false, errors.New("gpioioctl not initialized") } // It's kind of messy, some report bcm283x while others show bcm27xx. // Let's play safe here. From 7722d7b830fc610c72acb96cc0c0deceb6ece64c Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 16:43:46 -0600 Subject: [PATCH 3/9] Handle cross compilation issues. --- gpioioctl/basic_test.go | 2 -- gpioioctl/example_test.go | 2 -- gpioioctl/gpio.go | 9 +++------ gpioioctl/gpio_other.go | 21 +++++++++++++++++++++ gpioioctl/ioctl.go | 15 ++++++--------- gpioioctl/lineset.go | 7 ++----- gpioioctl/syscall.go | 32 ++++++++++++++++++++++++++++++++ gpioioctl/syscall_other.go | 28 ++++++++++++++++++++++++++++ 8 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 gpioioctl/gpio_other.go create mode 100644 gpioioctl/syscall.go create mode 100644 gpioioctl/syscall_other.go diff --git a/gpioioctl/basic_test.go b/gpioioctl/basic_test.go index 9e6bf2ed..d3aa7adb 100644 --- a/gpioioctl/basic_test.go +++ b/gpioioctl/basic_test.go @@ -6,8 +6,6 @@ // periph.io/x/cmd/periph-smoketest/gpiosmoketest // folder. -//go:build linux - package gpioioctl import ( diff --git a/gpioioctl/example_test.go b/gpioioctl/example_test.go index a5af3809..a9b126fa 100644 --- a/gpioioctl/example_test.go +++ b/gpioioctl/example_test.go @@ -1,5 +1,3 @@ -//go:build linux - package gpioioctl_test // Copyright 2024 The Periph Authors. All rights reserved. diff --git a/gpioioctl/gpio.go b/gpioioctl/gpio.go index 77368a3a..1017eaf9 100644 --- a/gpioioctl/gpio.go +++ b/gpioioctl/gpio.go @@ -1,5 +1,3 @@ -//go:build linux - package gpioioctl // Copyright 2024 The Periph Authors. All rights reserved. @@ -18,7 +16,6 @@ import ( "runtime" "strings" "sync" - "syscall" "time" "periph.io/x/conn/v3/driver/driverreg" @@ -89,7 +86,7 @@ func (line *GPIOLine) Close() { if line.fEdge != nil { _ = line.fEdge.Close() } else if line.fd != 0 { - _ = syscall.Close(int(line.fd)) + _ = syscall_close_wrapper(int(line.fd)) } line.fd = 0 line.consumer = "" @@ -230,7 +227,7 @@ func (line *GPIOLine) WaitForEdge(timeout time.Duration) bool { } var err error if line.fEdge == nil { - err = syscall.SetNonblock(int(line.fd), true) + err = syscall_nonblock_wrapper(int(line.fd), true) if err != nil { log.Println("WaitForEdge() SetNonblock(): ", err) return false @@ -433,7 +430,7 @@ func (chip *GPIOChip) Close() { for _, lineset := range chip.lineSets { _ = lineset.Close() } - _ = syscall.Close(int(chip.fd)) + _ = syscall_close_wrapper(int(chip.fd)) } // ByName returns a GPIOLine for a specific name. If not diff --git a/gpioioctl/gpio_other.go b/gpioioctl/gpio_other.go new file mode 100644 index 00000000..f982f5e8 --- /dev/null +++ b/gpioioctl/gpio_other.go @@ -0,0 +1,21 @@ +//go:build !linux + +// 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. +// +// Create a dummy chip because ioctl is only supported on Linux + +package gpioioctl + +func init() { + if len(Chips) == 0 { + chip := GPIOChip{name: "DummyGPIOChip", + path: "/dev/gpiochipdummy", + label: "Dummy GPIOChip for Testing Purposes", + lineCount: 1, + lines: []*GPIOLine{&line}, + } + Chips = append(Chips, &chip) + } +} diff --git a/gpioioctl/ioctl.go b/gpioioctl/ioctl.go index c45cdbb0..5c921cdf 100644 --- a/gpioioctl/ioctl.go +++ b/gpioioctl/ioctl.go @@ -1,5 +1,3 @@ -//go:build linux - package gpioioctl // Copyright 2024 The Periph Authors. All rights reserved. @@ -14,7 +12,6 @@ package gpioioctl import ( "errors" - "syscall" "unsafe" ) @@ -145,7 +142,7 @@ type gpio_v2_line_event struct { func ioctl_get_gpio_v2_line_values(fd uintptr, data *gpio_v2_line_values) error { arg := _IOWR(0xb4, 0x0e, unsafe.Sizeof(gpio_v2_line_values{})) - _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, arg, uintptr(unsafe.Pointer(data))) + _, _, ep := syscall_wrapper(_IOCTL_FUNCTION, fd, arg, uintptr(unsafe.Pointer(data))) if ep != 0 { return errors.New(ep.Error()) } @@ -154,7 +151,7 @@ func ioctl_get_gpio_v2_line_values(fd uintptr, data *gpio_v2_line_values) error func ioctl_set_gpio_v2_line_values(fd uintptr, data *gpio_v2_line_values) error { arg := _IOWR(0xb4, 0x0f, unsafe.Sizeof(gpio_v2_line_values{})) - _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, arg, uintptr(unsafe.Pointer(data))) + _, _, ep := syscall_wrapper(_IOCTL_FUNCTION, fd, arg, uintptr(unsafe.Pointer(data))) if ep != 0 { return errors.New(ep.Error()) } @@ -162,7 +159,7 @@ func ioctl_set_gpio_v2_line_values(fd uintptr, data *gpio_v2_line_values) error } func ioctl_gpiochip_info(fd uintptr, data *gpiochip_info) error { arg := _IOR(0xb4, 0x01, unsafe.Sizeof(gpiochip_info{})) - _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, arg, uintptr(unsafe.Pointer(data))) + _, _, ep := syscall_wrapper(_IOCTL_FUNCTION, fd, arg, uintptr(unsafe.Pointer(data))) if ep != 0 { return errors.New(ep.Error()) } @@ -171,7 +168,7 @@ func ioctl_gpiochip_info(fd uintptr, data *gpiochip_info) error { func ioctl_gpio_v2_line_info(fd uintptr, data *gpio_v2_line_info) error { arg := _IOWR(0xb4, 0x05, unsafe.Sizeof(gpio_v2_line_info{})) - _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, arg, uintptr(unsafe.Pointer(data))) + _, _, ep := syscall_wrapper(_IOCTL_FUNCTION, fd, arg, uintptr(unsafe.Pointer(data))) if ep != 0 { return errors.New(ep.Error()) } @@ -180,7 +177,7 @@ func ioctl_gpio_v2_line_info(fd uintptr, data *gpio_v2_line_info) error { func ioctl_gpio_v2_line_config(fd uintptr, data *gpio_v2_line_config) error { arg := _IOWR(0xb4, 0x0d, unsafe.Sizeof(gpio_v2_line_config{})) - _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, arg, uintptr(unsafe.Pointer(data))) + _, _, ep := syscall_wrapper(_IOCTL_FUNCTION, fd, arg, uintptr(unsafe.Pointer(data))) if ep != 0 { return errors.New(ep.Error()) } @@ -189,7 +186,7 @@ func ioctl_gpio_v2_line_config(fd uintptr, data *gpio_v2_line_config) error { func ioctl_gpio_v2_line_request(fd uintptr, data *gpio_v2_line_request) error { arg := _IOWR(0xb4, 0x07, unsafe.Sizeof(gpio_v2_line_request{})) - _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, arg, uintptr(unsafe.Pointer(data))) + _, _, ep := syscall_wrapper(_IOCTL_FUNCTION, fd, arg, uintptr(unsafe.Pointer(data))) if ep != 0 { return errors.New(ep.Error()) } diff --git a/gpioioctl/lineset.go b/gpioioctl/lineset.go index 8eeb3612..24de22df 100644 --- a/gpioioctl/lineset.go +++ b/gpioioctl/lineset.go @@ -1,5 +1,3 @@ -//go:build linux - package gpioioctl // Copyright 2024 The Periph Authors. All rights reserved. @@ -14,7 +12,6 @@ import ( "log" "os" "sync" - "syscall" "time" "periph.io/x/conn/v3/gpio" @@ -131,7 +128,7 @@ func (ls *LineSet) Close() error { if ls.fEdge != nil { err = ls.fEdge.Close() } else if ls.fd != 0 { - err = syscall.Close(int(ls.fd)) + err = syscall_close_wrapper(int(ls.fd)) } ls.fd = 0 ls.fEdge = nil @@ -223,7 +220,7 @@ func (ls *LineSet) WaitForEdge(timeout time.Duration) (number uint32, edge gpio. number = 0 edge = gpio.NoEdge if ls.fEdge == nil { - err = syscall.SetNonblock(int(ls.fd), true) + err = syscall_nonblock_wrapper(int(ls.fd), true) if err != nil { err = fmt.Errorf("WaitForEdge() - SetNonblock: %w", err) return diff --git a/gpioioctl/syscall.go b/gpioioctl/syscall.go new file mode 100644 index 00000000..4d147f9c --- /dev/null +++ b/gpioioctl/syscall.go @@ -0,0 +1,32 @@ +//go:build linux + +// 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. +// +// This file provides a wrapper around syscall so that we can have the same source +// for Windows/Linux. The problem is that in Linux syscall.Syscall takes a unintptr +// as the first arg, while on windows it's a syscall.Handle. It also handles +// syscall.SYS_IOCTL not being defined things besides linux. + +package gpioioctl + +import ( + "syscall" +) + +const ( + _IOCTL_FUNCTION = syscall.SYS_IOCTL +) + +func syscall_wrapper(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { + return syscall.Syscall(trap, a1, a2, a3) +} + +func syscall_close_wrapper(fd int) (err error) { + return syscall.Close(fd) +} + +func syscall_nonblock_wrapper(fd int, nonblocking bool) (err error) { + return syscall.SetNonblock(fd, nonblocking) +} diff --git a/gpioioctl/syscall_other.go b/gpioioctl/syscall_other.go new file mode 100644 index 00000000..aec80913 --- /dev/null +++ b/gpioioctl/syscall_other.go @@ -0,0 +1,28 @@ +//go:build !linux + +// 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 + +import ( + "syscall" +) + +const ( + _IOCTL_FUNCTION = 0 +) + +type Errno uintptr + +func syscall_wrapper(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { //nolint:unused + return uintptr(0), uintptr(0), syscall.ERROR_NOT_FOUND +} + +func syscall_close_wrapper(fd int) (err error) { + return syscall.Close(syscall.Handle(fd)) +} + +func syscall_nonblock_wrapper(fd int, nonblocking bool) (err error) { + return syscall.SetNonblock(syscall.Handle(fd), nonblocking) +} From c4e57a732d85ce97986f184707490f9f7506e815 Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 16:49:46 -0600 Subject: [PATCH 4/9] Fix non-linux init --- gpioioctl/gpio_other.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gpioioctl/gpio_other.go b/gpioioctl/gpio_other.go index f982f5e8..50c7b076 100644 --- a/gpioioctl/gpio_other.go +++ b/gpioioctl/gpio_other.go @@ -10,6 +10,15 @@ package gpioioctl func init() { if len(Chips) == 0 { + 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", From 171ad1acfc25afeef2be417643e261da2403e97f Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 16:50:54 -0600 Subject: [PATCH 5/9] Add import --- gpioioctl/gpio_other.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gpioioctl/gpio_other.go b/gpioioctl/gpio_other.go index 50c7b076..a993e1e5 100644 --- a/gpioioctl/gpio_other.go +++ b/gpioioctl/gpio_other.go @@ -8,6 +8,10 @@ package gpioioctl +import ( + "periph.io/x/conn/v3/gpio" +) + func init() { if len(Chips) == 0 { line := GPIOLine{ From c98af7f6184d6af0a895e6cd3ad5be3180805177 Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 16:54:45 -0600 Subject: [PATCH 6/9] Try again --- gpioioctl/syscall.go | 2 +- gpioioctl/syscall_other.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gpioioctl/syscall.go b/gpioioctl/syscall.go index 4d147f9c..90c2dd2f 100644 --- a/gpioioctl/syscall.go +++ b/gpioioctl/syscall.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build !windows // Copyright 2024 The Periph Authors. All rights reserved. // Use of this source code is governed under the Apache License, Version 2.0 diff --git a/gpioioctl/syscall_other.go b/gpioioctl/syscall_other.go index aec80913..3372c44c 100644 --- a/gpioioctl/syscall_other.go +++ b/gpioioctl/syscall_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build windows // Copyright 2024 The Periph Authors. All rights reserved. // Use of this source code is governed under the Apache License, Version 2.0 From ee5f017eb9504acc7681394e421ee31193557aa2 Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 17:03:28 -0600 Subject: [PATCH 7/9] More init work --- gpioioctl/gpio.go | 63 +++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/gpioioctl/gpio.go b/gpioioctl/gpio.go index 1017eaf9..3407e6f3 100644 --- a/gpioioctl/gpio.go +++ b/gpioioctl/gpio.go @@ -591,25 +591,27 @@ func (d *driverGPIO) After() []string { // // https://docs.kernel.org/userspace-api/gpio/chardev.html func (d *driverGPIO) Init() (bool, error) { - items, err := filepath.Glob("/dev/gpiochip*") - if err != nil { - return true, err - } - if len(items) == 0 { - return false, errors.New("no GPIO chips found") - } - Chips = make([]*GPIOChip, 0) - for _, item := range items { - chip, err := newGPIOChip(item) + if runtime.GOOS == "linux" { + items, err := filepath.Glob("/dev/gpiochip*") if err != nil { - log.Println("gpioioctl.driverGPIO.Init() Error", err) - return false, err + return true, err + } + if len(items) == 0 { + return false, errors.New("no GPIO chips found") } - Chips = append(Chips, chip) - for _, line := range chip.lines { - if len(line.name) > 0 && line.name != "_" && line.name != "-" { - if err = gpioreg.Register(line); err != nil { - log.Println("chip", chip.Name(), " gpioreg.Register(line) ", line, " returned ", err) + Chips = make([]*GPIOChip, 0) + for _, item := range items { + chip, err := newGPIOChip(item) + if err != nil { + log.Println("gpioioctl.driverGPIO.Init() Error", err) + return false, err + } + Chips = append(Chips, chip) + for _, line := range chip.lines { + if len(line.name) > 0 && line.name != "_" && line.name != "-" { + if err = gpioreg.Register(line); err != nil { + log.Println("chip", chip.Name(), " gpioreg.Register(line) ", line, " returned ", err) + } } } } @@ -620,21 +622,18 @@ func (d *driverGPIO) Init() (bool, error) { var drvGPIO driverGPIO func init() { - if runtime.GOOS == "linux" { - - // Init our consumer name. It's used when a line is requested, and - // allows utility programs like gpioinfo to find out who has a line - // open. - fname := path.Base(os.Args[0]) - s := fmt.Sprintf("%s@%d", fname, os.Getpid()) - charBytes := []byte(s) - if len(charBytes) >= _GPIO_MAX_NAME_SIZE { - charBytes = charBytes[:_GPIO_MAX_NAME_SIZE-1] - } - consumer = charBytes - - driverreg.MustRegister(&drvGPIO) - } + // Init our consumer name. It's used when a line is requested, and + // allows utility programs like gpioinfo to find out who has a line + // open. + fname := path.Base(os.Args[0]) + s := fmt.Sprintf("%s@%d", fname, os.Getpid()) + charBytes := []byte(s) + if len(charBytes) >= _GPIO_MAX_NAME_SIZE { + charBytes = charBytes[:_GPIO_MAX_NAME_SIZE-1] + } + consumer = charBytes + + driverreg.MustRegister(&drvGPIO) } // Ensure that Interfaces for these types are implemented fully. From 758db76e88482bbafe4442a7712d3d538ae8690b Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 17:09:58 -0600 Subject: [PATCH 8/9] More init work --- gpioioctl/gpio_other.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gpioioctl/gpio_other.go b/gpioioctl/gpio_other.go index a993e1e5..27a2559b 100644 --- a/gpioioctl/gpio_other.go +++ b/gpioioctl/gpio_other.go @@ -10,6 +10,7 @@ package gpioioctl import ( "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/gpio/gpioreg" ) func init() { @@ -30,5 +31,11 @@ func init() { lines: []*GPIOLine{&line}, } Chips = append(Chips, &chip) + 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) + } } } From 5005af95593534405de6d3dce3cabd3d870eef15 Mon Sep 17 00:00:00 2001 From: George Sexton Date: Mon, 21 Oct 2024 17:18:28 -0600 Subject: [PATCH 9/9] more cross-os --- gpioioctl/basic_test.go | 6 +----- gpioioctl/dummy.go | 45 +++++++++++++++++++++++++++++++++++++++++ gpioioctl/gpio_other.go | 28 +------------------------ 3 files changed, 47 insertions(+), 32 deletions(-) create mode 100644 gpioioctl/dummy.go diff --git a/gpioioctl/basic_test.go b/gpioioctl/basic_test.go index d3aa7adb..875ca29b 100644 --- a/gpioioctl/basic_test.go +++ b/gpioioctl/basic_test.go @@ -22,11 +22,7 @@ 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. - */ + makeDummyChip() line := GPIOLine{ number: 0, name: "DummyGPIOLine", diff --git a/gpioioctl/dummy.go b/gpioioctl/dummy.go new file mode 100644 index 00000000..7f3c3ea9 --- /dev/null +++ b/gpioioctl/dummy.go @@ -0,0 +1,45 @@ +// 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. +// +// Create a dummy chip for testing an non-Linux os. + +package gpioioctl + +import ( + "log" + + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/gpio/gpioreg" +) + +func makeDummyChip() { + /* + 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) + 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) + } +} diff --git a/gpioioctl/gpio_other.go b/gpioioctl/gpio_other.go index 27a2559b..b942a8b3 100644 --- a/gpioioctl/gpio_other.go +++ b/gpioioctl/gpio_other.go @@ -8,34 +8,8 @@ package gpioioctl -import ( - "periph.io/x/conn/v3/gpio" - "periph.io/x/conn/v3/gpio/gpioreg" -) - func init() { if len(Chips) == 0 { - 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) - 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) - } + makeDummyChip() } }