From 75941b83ae1854a44dd407dda76b3b7a99de8838 Mon Sep 17 00:00:00 2001 From: gsexton Date: Mon, 21 Oct 2024 17:39:19 -0600 Subject: [PATCH] bcm283x: Issue 62 Change bcm283x to use ioctl for GPIO pins, not sysfs. (#63) --- bcm283x/doc.go | 6 +- bcm283x/gpio.go | 79 ++++++++++++------------- bcm283x/gpio_test.go | 3 +- gpioioctl/basic_test.go | 8 +-- gpioioctl/dummy.go | 45 ++++++++++++++ gpioioctl/example_test.go | 2 - gpioioctl/gpio.go | 117 ++++++++++++++++++++++++------------- gpioioctl/gpio_other.go | 15 +++++ gpioioctl/ioctl.go | 15 ++--- gpioioctl/lineset.go | 7 +-- gpioioctl/syscall.go | 32 ++++++++++ gpioioctl/syscall_other.go | 28 +++++++++ 12 files changed, 247 insertions(+), 110 deletions(-) create mode 100644 gpioioctl/dummy.go create mode 100644 gpioioctl/gpio_other.go create mode 100644 gpioioctl/syscall.go create mode 100644 gpioioctl/syscall_other.go diff --git a/bcm283x/doc.go b/bcm283x/doc.go index 0bbea729..51453cfe 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..04e09924 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/basic_test.go b/gpioioctl/basic_test.go index 9e6bf2ed..875ca29b 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 ( @@ -24,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/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 2005c3ab..3407e6f3 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,13 +16,13 @@ import ( "runtime" "strings" "sync" - "syscall" "time" "periph.io/x/conn/v3/driver/driverreg" "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. @@ -88,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 = "" @@ -110,11 +108,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 { @@ -234,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 @@ -301,6 +294,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 { @@ -397,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 @@ -558,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 } - 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) + 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 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) + } } } } @@ -587,24 +622,22 @@ 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. var _ gpio.PinIO = &GPIOLine{} var _ gpio.PinIn = &GPIOLine{} var _ gpio.PinOut = &GPIOLine{} +var _ pin.PinFunc = &GPIOLine{} diff --git a/gpioioctl/gpio_other.go b/gpioioctl/gpio_other.go new file mode 100644 index 00000000..b942a8b3 --- /dev/null +++ b/gpioioctl/gpio_other.go @@ -0,0 +1,15 @@ +//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 { + makeDummyChip() + } +} 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..90c2dd2f --- /dev/null +++ b/gpioioctl/syscall.go @@ -0,0 +1,32 @@ +//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 +// 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..3372c44c --- /dev/null +++ b/gpioioctl/syscall_other.go @@ -0,0 +1,28 @@ +//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 +// 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) +}