// SPDX-FileCopyrightText: 2019 Kent Gibson // // SPDX-License-Identifier: MIT //go:build linux // +build linux // Package uapi provides the Linux GPIO UAPI definitions for gpiocdev. package uapi import ( "bytes" "encoding/binary" "fmt" "regexp" "strconv" "unsafe" "golang.org/x/sys/unix" ) // GetChipInfo returns the ChipInfo for the GPIO character device. // // The fd is an open GPIO character device. func GetChipInfo(fd uintptr) (ChipInfo, error) { var ci ChipInfo _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getChipInfoIoctl), uintptr(unsafe.Pointer(&ci))) if errno != 0 { return ci, errno } return ci, nil } // GetLineInfo returns the LineInfo for one line from the GPIO character device. // // The fd is an open GPIO character device. // The offset is zero based. func GetLineInfo(fd uintptr, offset int) (LineInfo, error) { li := LineInfo{Offset: uint32(offset)} _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getLineInfoIoctl), uintptr(unsafe.Pointer(&li))) if errno != 0 { return LineInfo{}, errno } return li, nil } // GetLineEvent requests a line from the GPIO character device with event // reporting enabled. // // The fd is an open GPIO character device. // The line must be an input and must not already be requested. // If successful, the fd for the line is returned in the request.fd. func GetLineEvent(fd uintptr, request *EventRequest) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getLineEventIoctl), uintptr(unsafe.Pointer(request))) if errno != 0 { return errno } return nil } // GetLineHandle requests a line from the GPIO character device. // // This request is without event reporting. // The fd is an open GPIO character device. // The lines must not already be requested. // The flags in the request will be applied to all lines in the request. // If successful, the fd for the line is returned in the request.fd. func GetLineHandle(fd uintptr, request *HandleRequest) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getLineHandleIoctl), uintptr(unsafe.Pointer(request))) if errno != 0 { return errno } return nil } // GetLineValues returns the values of a set of requested lines. // // The fd is a requested line, as returned by GetLineHandle or GetLineEvent. func GetLineValues(fd uintptr, values *HandleData) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getLineValuesIoctl), uintptr(unsafe.Pointer(&values[0]))) if errno != 0 { return errno } return nil } // SetLineValues sets the values of a set of requested lines. // // The fd is a requested line, as returned by GetLineHandle or GetLineEvent. func SetLineValues(fd uintptr, values HandleData) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(setLineValuesIoctl), uintptr(unsafe.Pointer(&values[0]))) if errno != 0 { return errno } return nil } // SetLineConfig sets the config of an existing handle request. // // The config flags in the request will be applied to all lines in the handle // request. func SetLineConfig(fd uintptr, config *HandleConfig) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(setLineConfigIoctl), uintptr(unsafe.Pointer(config))) if errno != 0 { return errno } return nil } // WatchLineInfo sets a watch on info of a line. // // A watch is set on the line indicated by info.Offset. If successful the // current line info is returned, else an error is returned. func WatchLineInfo(fd uintptr, info *LineInfo) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(watchLineInfoIoctl), uintptr(unsafe.Pointer(info))) if errno != 0 { return errno } return nil } // UnwatchLineInfo clears a watch on info of a line. // // Disables the watch on info for the line. func UnwatchLineInfo(fd uintptr, offset uint32) error { _, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unwatchLineInfoIoctl), uintptr(unsafe.Pointer(&offset))) if errno != 0 { return errno } return nil } // BytesToString is a helper function that converts strings stored in byte // arrays, as returned by GetChipInfo and GetLineInfo, into strings. func BytesToString(a []byte) string { n := bytes.IndexByte(a, 0) if n == -1 { return string(a) } return string(a[:n]) } type fdReader int func (fd fdReader) Read(b []byte) (int, error) { return unix.Read(int(fd), b[:]) } // ReadEvent reads a single event from a requested line. // // The fd is a requested line, as returned by GetLineEvent. // // This function is blocking and should only be called when the fd is known to // be ready to read. func ReadEvent(fd uintptr) (EventData, error) { var ed EventData err := binary.Read(fdReader(fd), nativeEndian, &ed) return ed, err } // ReadLineInfoChanged reads a line info changed event from a chip. // // The fd is an open GPIO character device. // // This function is blocking and should only be called when the fd is known to // be ready to read. func ReadLineInfoChanged(fd uintptr) (LineInfoChanged, error) { var lic LineInfoChanged err := binary.Read(fdReader(fd), nativeEndian, &lic) return lic, err } // IOCTL command codes type ioctl uintptr var ( getChipInfoIoctl ioctl getLineInfoIoctl ioctl getLineHandleIoctl ioctl getLineEventIoctl ioctl getLineValuesIoctl ioctl setLineValuesIoctl ioctl setLineConfigIoctl ioctl watchLineInfoIoctl ioctl unwatchLineInfoIoctl ioctl ) // Size of name and consumer strings. const nameSize = 32 func init() { // ioctls require struct sizes which are only available at runtime. var ci ChipInfo getChipInfoIoctl = ior(0xB4, 0x01, unsafe.Sizeof(ci)) var li LineInfo getLineInfoIoctl = iorw(0xB4, 0x02, unsafe.Sizeof(li)) var hr HandleRequest getLineHandleIoctl = iorw(0xB4, 0x03, unsafe.Sizeof(hr)) var le EventRequest getLineEventIoctl = iorw(0xB4, 0x04, unsafe.Sizeof(le)) var hd HandleData getLineValuesIoctl = iorw(0xB4, 0x08, unsafe.Sizeof(hd)) setLineValuesIoctl = iorw(0xB4, 0x09, unsafe.Sizeof(hd)) var hc HandleConfig setLineConfigIoctl = iorw(0xB4, 0x0A, unsafe.Sizeof(hc)) watchLineInfoIoctl = iorw(0xB4, 0x0B, unsafe.Sizeof(li)) unwatchLineInfoIoctl = iorw(0xB4, 0x0C, unsafe.Sizeof(li.Offset)) } // ChipInfo contains the details of a GPIO chip. type ChipInfo struct { // The system name of the device. Name [nameSize]byte // An identifying label added by the device driver. Label [nameSize]byte // The number of lines supported by this chip. Lines uint32 } // LineInfo contains the details of a single line of a GPIO chip. type LineInfo struct { // The offset of the line within the chip. Offset uint32 // The line flags applied to this line. Flags LineFlag // The system name for this line. Name [nameSize]byte // If requested, a string added by the requester to identify the // owner of the request. Consumer [nameSize]byte } // LineInfoChanged contains the details of a change to line info. // // This is returned via the chip fd in response to changes to watched lines. type LineInfoChanged struct { // The updated info. Info LineInfo // The time the change occurred. Timestamp uint64 // The type of change. Type ChangeType // reserved for future use. _ [5]uint32 } // ChangeType indicates the type of change that has occurred to a line. type ChangeType uint32 const ( _ ChangeType = iota // LineChangedRequested indicates the line has been requested. LineChangedRequested // LineChangedReleased indicates the line has been released. LineChangedReleased // LineChangedConfig indicates the line configuration has changed. LineChangedConfig ) // LineFlag are the flags for a line. type LineFlag uint32 const ( // LineFlagUsed indicates that the line has been requested. // It may have been requested by this process or another process. // The line cannot be requested again until this flag is clear. LineFlagUsed LineFlag = 1 << iota // LineFlagIsOut indicates that the line is an output. LineFlagIsOut // LineFlagActiveLow indicates that the line is active low. LineFlagActiveLow // LineFlagOpenDrain indicates that the line will pull low when set low but // float when set high. This flag only applies to output lines. // An output cannot be both open drain and open source. LineFlagOpenDrain // LineFlagOpenSource indicates that the line will pull high when set high // but float when set low. This flag only applies to output lines. // An output cannot be both open drain and open source. LineFlagOpenSource // LineFlagPullUp indicates that the internal line pull up is enabled. LineFlagPullUp // LineFlagPullDown indicates that the internal line pull down is enabled. LineFlagPullDown // LineFlagBiasDisabled indicates that the internal line bias is disabled. LineFlagBiasDisabled ) // IsUsed returns true if the line is requested. func (f LineFlag) IsUsed() bool { return f&LineFlagUsed != 0 } // IsOut returns true if the line is an output. func (f LineFlag) IsOut() bool { return f&LineFlagIsOut != 0 } // IsActiveLow returns true if the line is active low. func (f LineFlag) IsActiveLow() bool { return f&LineFlagActiveLow != 0 } // IsOpenDrain returns true if the line is open-drain. func (f LineFlag) IsOpenDrain() bool { return f&LineFlagOpenDrain != 0 } // IsOpenSource returns true if the line is open-source. func (f LineFlag) IsOpenSource() bool { return f&LineFlagOpenSource != 0 } // IsBiasDisable returns true if the line has bias disabled. func (f LineFlag) IsBiasDisable() bool { return f&LineFlagBiasDisabled != 0 } // IsPullDown returns true if the line has pull-down enabled. func (f LineFlag) IsPullDown() bool { return f&LineFlagPullDown != 0 } // IsPullUp returns true if the line has pull-up enabled. func (f LineFlag) IsPullUp() bool { return f&LineFlagPullUp != 0 } // HandleConfig is a request to change the config of an existing request. // // Can be applied to both handle and event requests. // Event requests cannot be reconfigured to outputs. type HandleConfig struct { // The flags to be applied to the lines. Flags HandleFlag // The default values to be applied to output lines (when // HandleRequestOutput is set in the Flags). DefaultValues [HandlesMax]uint8 // reserved for future use. _ [4]uint32 } // HandleRequest is a request for control of a set of lines. // The lines must all be on the same GPIO chip. type HandleRequest struct { // The lines to be requested. Offsets [HandlesMax]uint32 // The flags to be applied to the lines. Flags HandleFlag // The default values to be applied to output lines. DefaultValues [HandlesMax]uint8 // The string identifying the requester to be applied to the lines. Consumer [nameSize]byte // The number of lines being requested. Lines uint32 // The file handle for the requested lines. // Set if the request is successful. Fd int32 } // HandleFlag contains the type HandleFlag uint32 const ( // HandleRequestInput requests the line as an input. // // This is ignored if Output is also set. HandleRequestInput HandleFlag = 1 << iota // HandleRequestOutput requests the line as an output. // // This takes precedence over Input, if both are set. HandleRequestOutput // HandleRequestActiveLow requests the line be made active low. HandleRequestActiveLow // HandleRequestOpenDrain requests the line be made open drain. // // This option requires the line to be requested as an Output. // This cannot be set at the same time as OpenSource. HandleRequestOpenDrain // HandleRequestOpenSource requests the line be made open source. // // This option requires the line to be requested as an Output. // This cannot be set at the same time as OpenDrain. HandleRequestOpenSource // HandleRequestPullUp requests the line have pull-up enabled. HandleRequestPullUp // HandleRequestPullDown requests the line have pull-down enabled. HandleRequestPullDown // HandleRequestBiasDisable requests the line have bias disabled. HandleRequestBiasDisable // HandlesMax is the maximum number of lines that can be requested in a // single request. HandlesMax = 64 ) // IsInput returns true if the line is requested as an input. func (f HandleFlag) IsInput() bool { return f&HandleRequestInput != 0 } // IsOutput returns true if the line is requested as an output. func (f HandleFlag) IsOutput() bool { return f&HandleRequestOutput != 0 } // IsActiveLow returns true if the line is requested as a active low. func (f HandleFlag) IsActiveLow() bool { return f&HandleRequestActiveLow != 0 } // IsOpenDrain returns true if the line is requested as an open drain. func (f HandleFlag) IsOpenDrain() bool { return f&HandleRequestOpenDrain != 0 } // IsOpenSource returns true if the line is requested as an open source. func (f HandleFlag) IsOpenSource() bool { return f&HandleRequestOpenSource != 0 } // HasBiasFlag returns true if any bias flags are set. func (f HandleFlag) HasBiasFlag() bool { return f&(HandleRequestBiasDisable|HandleRequestPullDown|HandleRequestPullUp) != 0 } // IsBiasDisable returns true if the line is requested with bias disabled. func (f HandleFlag) IsBiasDisable() bool { return f&HandleRequestBiasDisable != 0 } // IsPullDown returns true if the line is requested with pull-down enabled. func (f HandleFlag) IsPullDown() bool { return f&HandleRequestPullDown != 0 } // IsPullUp returns true if the line is requested with pull-up enabled. func (f HandleFlag) IsPullUp() bool { return f&HandleRequestPullUp != 0 } // HandleData contains the logical value for each line. // Zero is a logical low and any other value is a logical high. type HandleData [HandlesMax]uint8 // EventRequest is a request for control of a line with event reporting enabled. type EventRequest struct { // The line to be requested. Offset uint32 // The line flags applied to this line. HandleFlags HandleFlag // The type of events to report. EventFlags EventFlag // The string identifying the requester to be applied to the line. Consumer [nameSize]byte // The file handle for the requested line. // Set if the request is successful. Fd int32 } // EventFlag indicates the types of events that will be reported. type EventFlag uint32 const ( // EventRequestRisingEdge requests rising edge events. // This means a transition from a low logical state to a high logical state. // For active high lines (the default) this means a transition from a // physical low to a physical high. // Note that for active low lines this means a transition from a physical // high to a physical low. EventRequestRisingEdge EventFlag = 1 << iota // EventRequestFallingEdge requests falling edge events. // This means a transition from a high logical state to a low logical state. // For active high lines (the default) this means a transition from a // physical high to a physical low. // Note that for active low lines this means a transition from a physical // low to a physical high. EventRequestFallingEdge // EventRequestBothEdges requests both rising and falling edge events. // This is equivalent to requesting both EventRequestRisingEdge and // EventRequestRisingEdge. EventRequestBothEdges = EventRequestRisingEdge | EventRequestFallingEdge ) // IsRisingEdge returns true if rising edge events have been requested. func (f EventFlag) IsRisingEdge() bool { return f&EventRequestRisingEdge != 0 } // IsFallingEdge returns true if falling edge events have been requested. func (f EventFlag) IsFallingEdge() bool { return f&EventRequestFallingEdge != 0 } // IsBothEdges returns true if both rising and falling edge events have been // requested. func (f EventFlag) IsBothEdges() bool { return f&EventRequestBothEdges == EventRequestBothEdges } // KernelVersion returns the running kernel version. func KernelVersion() (semver []byte, err error) { uname := unix.Utsname{} err = unix.Uname(&uname) if err != nil { return } release := string(uname.Release[:]) r := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`) vers := r.FindStringSubmatch(release) if len(vers) != 4 { err = fmt.Errorf("can't parse uname: %s", release) return } v := []byte{0, 0, 0} for i, vf := range vers[1:] { var vfi uint64 vfi, err = strconv.ParseUint(vf, 10, 64) if err != nil { return } v[i] = byte(vfi) } semver = v return } // CheckKernelVersion returns an error if the kernel version is less than the // min. func CheckKernelVersion(min Semver) error { kv, err := KernelVersion() if err != nil { return err } n := bytes.Compare(kv, min[:]) if n < 0 { return ErrorBadVersion{Need: min, Have: kv} } return nil } // Semver is 3 part version, Major, Minor, Patch. type Semver []byte func (v Semver) String() string { if len(v) == 0 { return "" } vstr := fmt.Sprintf("%d", v[0]) for i := 1; i < len(v); i++ { vstr += fmt.Sprintf(".%d", v[i]) } return vstr } // ErrorBadVersion indicates the kernel version is insufficient. type ErrorBadVersion struct { Need Semver Have Semver } func (e ErrorBadVersion) Error() string { return fmt.Sprintf("require kernel %s or later, but running %s", e.Need, e.Have) }