├── .github └── workflows │ └── go.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── LICENSES ├── CC0-1.0.txt └── MIT.txt ├── Makefile ├── README.md ├── device ├── bananapi │ └── bananapi.go └── rpi │ ├── rpi.go │ └── rpi_test.go ├── examples ├── find_line_by_name │ ├── .gitignore │ └── main.go ├── get_chip_info │ ├── .gitignore │ └── main.go ├── get_line_info │ ├── .gitignore │ └── main.go ├── get_line_value │ ├── .gitignore │ └── main.go ├── get_multiple_line_values │ ├── .gitignore │ └── main.go ├── readme │ ├── .gitignore │ └── main.go ├── reconfigure_input_to_output │ ├── .gitignore │ └── main.go ├── select_watch_line_value │ ├── .gitignore │ └── main.go ├── toggle_line_value │ ├── .gitignore │ └── main.go ├── toggle_multiple_line_values │ ├── .gitignore │ └── main.go ├── watch_line_info │ ├── .gitignore │ └── main.go ├── watch_line_rising │ ├── .gitignore │ └── main.go └── watch_line_value │ ├── .gitignore │ └── main.go ├── go.mod ├── go.sum ├── go.sum.license ├── gpiocdev.go ├── gpiocdev_bmark_test.go ├── gpiocdev_test.go ├── infowatcher.go ├── options.go ├── options_test.go ├── uapi ├── README.md ├── build.sh ├── endian.go ├── endian_intel.go ├── eventdata.go ├── eventdata_386.go ├── ioctl.go ├── ioctl_default.go ├── ioctl_mips32.go ├── kernel_test.go ├── test │ └── reflectsize.go ├── uapi.go ├── uapi_bmark_test.go ├── uapi_test.go ├── uapi_v2.go ├── uapi_v2_bmark_test.go └── uapi_v2_test.go └── watcher.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | # This workflow will build a golang project 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 7 | 8 | name: Go 9 | 10 | on: 11 | push: 12 | branches: [ "master" ] 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | jobs: 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Set up Go 24 | uses: actions/setup-go@v3 25 | with: 26 | go-version: 1.19 27 | 28 | - name: Build 29 | run: go build -v ./... 30 | 31 | # - name: Test 32 | # run: go test -v ./... 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | *.test 5 | *.out 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 6 | ## [Unreleased](https://github.com/warthog618/go-gpiocdev/compare/v0.9.1...HEAD) 7 | 8 | ## v0.9.1 - 2024-10-30 9 | 10 | - add *FindLine* functions to *Chip* and global. 11 | - rework and extend examples. 12 | - better document line values. 13 | - update tests for Linux 6.10 directionless reconfigure behaviour. 14 | - extend kernel uapi tests. 15 | 16 | ## v0.9.0 - 2024-03-16 17 | 18 | - rename from **gpiod** to **gpiocdev**. 19 | - move cli into its own repo - **go-gpiocdev-cli**. 20 | - remove SPI examples. 21 | 22 | ## v0.8.3 - 2024-03-16 23 | 24 | - deprecate in favour of **go-gpiocdev** 25 | 26 | ## v0.8.2 - 2023-07-28 27 | 28 | - switch tests from **gpio-mockup** to **gpio-sim**. 29 | - drop test dependency on *pilebones/go-udev*. 30 | - drop example dependency on *warthog618/config*. 31 | 32 | ## v0.8.1 - 2022-12-31 33 | 34 | - add bananapi pin mappings. 35 | - fix config check in **gpioset**. 36 | 37 | ## v0.8.0 - 2022-02-13 38 | 39 | - add top level *RequestLine* and *RequestLines* functions to simplify common use cases. 40 | - **blinker** and **watcher** examples interwork with each other on a Raspberry Pi with a jumper across **J8-15** and **J8-16**. 41 | - fix deadlock in **gpiodctl set** no-wait. 42 | 43 | ## v0.7.1 - 2021-10-10 44 | 45 | - restore LICENSE file for go.dev. 46 | 47 | ## v0.7.0 - 2021-10-08 48 | 49 | - *LineEvent* exposes sequence numbers for uAPI v2 events. 50 | - Info tools (**gpiodctl info** and **gpioinfo**) report debounce-period. 51 | - **gpiodctl mon** and watcher example report event sequence numbers. 52 | - **gpiodctl mon** supports setting debounce period. 53 | - **gpiodctl detect** reports kernel uAPI version in use. 54 | - Watchers use Eventfd instead of pipes to reduce open file descriptors. 55 | - start migrating to Go 1.17 go:build style build tags. 56 | - make licensing [REUSE](https://reuse.software/) compliant. 57 | 58 | ## v0.6.0 - 2020-12-12 59 | 60 | - *gpiod* now supports both the old GPIO uAPI (v1) and the newer (v2) introduced 61 | in Linux 5.10. The library automatically detects the available uAPI versions 62 | and makes use of the latest. 63 | - applications written for uAPI v1 will continue to work with uAPI v2. 64 | - applications that make use of v2 specific features will return errors when run 65 | on Linux kernels prior to 5.10. 66 | 67 | Breaking API changes: 68 | 69 | 1. The event handler parameter has been moved from edge options into the 70 | *WithEventHandler(eh)* option to allow for reconfiguration of edge detection 71 | which is supported in Linux 5.10. 72 | 73 | Old edge options should be replaced with the *WithEventHandler* option and 74 | the now parameterless edge option, e.g.: 75 | 76 | ```sed 77 | s/gpiod\.WithBothEdges(/gpiod.WithBothEdges, gpiod.WithEventHandler(/g 78 | ``` 79 | 80 | 2. *WithBiasDisable* is renamed *WithBiasDisabled*. This option is probably 81 | rarely used and the renaming is trivial, so no backward compatibility is 82 | provided. 83 | 84 | 3. *FindLine* has been dropped as line names are not guaranteed to be unique. 85 | Iterating over the available chips and lines to search for line by name can 86 | be easily done - the *Chips* function provides the list of available chips as 87 | a starting point. 88 | 89 | Refer to the *find* command in **gpiodctl** for example code. 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kent Gibson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice (including the next 13 | paragraph) shall be included in all copies or substantial portions of the 14 | Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kent Gibson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice (including the next 13 | paragraph) shall be included in all copies or substantial portions of the 14 | Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | GOCMD=go 6 | GOBUILD=$(GOCMD) build 7 | GOCLEAN=$(GOCMD) clean 8 | 9 | VERSION ?= $(shell git describe --tags --always --dirty 2> /dev/null ) 10 | LDFLAGS=-ldflags "-X=main.version=$(VERSION)" 11 | 12 | examples=$(patsubst %.go, %, $(wildcard examples/*/main.go)) 13 | bins= $(examples) 14 | 15 | all: $(bins) 16 | 17 | $(bins) : % : %.go 18 | cd $(@D); \ 19 | $(GOBUILD) 20 | 21 | clean: 22 | $(GOCLEAN) ./... 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # gpiocdev 8 | 9 | [![Build Status](https://img.shields.io/github/actions/workflow/status/warthog618/go-gpiocdev/go.yml?logo=github&branch=master)](https://github.com/warthog618/go-gpiocdev/actions/workflows/go.yml) 10 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/warthog618/go-gpiocdev)](https://pkg.go.dev/github.com/warthog618/go-gpiocdev) 11 | [![Go Report Card](https://goreportcard.com/badge/github.com/warthog618/go-gpiocdev)](https://goreportcard.com/report/github.com/warthog618/go-gpiocdev) 12 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/warthog618/go-gpiocdev/blob/master/LICENSE) 13 | 14 | A native Go library for Linux GPIO. 15 | 16 | **gpiocdev** is a library for accessing GPIO pins/lines on Linux platforms using 17 | the GPIO character device. 18 | 19 | The goal of this library is to provide the Go equivalent of the C 20 | **[libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/)** 21 | library. The intent is not to mirror the **libgpiod** API but to provide the 22 | equivalent functionality. 23 | 24 | ## Features 25 | 26 | Supports the following functionality per line and for collections of lines: 27 | 28 | - direction (input/output)**1** 29 | - write (active/inactive) 30 | - read (active/inactive) 31 | - active high/low (defaults to high) 32 | - output mode (push-pull/open-drain/open-source) 33 | - pull up/down**2** 34 | - watches and edge detection (rising/falling/both) 35 | - chip and line labels 36 | - debouncing input lines**3** 37 | - different configurations for lines within a collection**3** 38 | 39 | **1** Dynamically changing line direction without releasing the line 40 | requires Linux 5.5 or later. 41 | 42 | **2** Requires Linux 5.5 or later. 43 | 44 | **3** Requires Linux 5.10 or later. 45 | 46 | All library functions are safe to call from different goroutines. 47 | 48 | ## Quick Start 49 | 50 | A simple piece of wire example that reads the value of an input line (pin 2) and 51 | writes its value to an output line (pin 3): 52 | 53 | ```go 54 | import "github.com/warthog618/go-gpiocdev" 55 | 56 | ... 57 | 58 | in, _ := gpiocdev.RequestLine("gpiochip0", 2, gpiocdev.AsInput) 59 | val, _ := in.Value() 60 | out, _ := gpiocdev.RequestLine("gpiochip0", 3, gpiocdev.AsOutput(val)) 61 | 62 | ... 63 | ``` 64 | 65 | Error handling and releasing of resources omitted for brevity. 66 | 67 | ## Usage 68 | 69 | ```go 70 | import "github.com/warthog618/go-gpiocdev" 71 | ``` 72 | 73 | Error handling is omitted from the following examples for brevity. 74 | 75 | ### Line Requests 76 | 77 | To read or alter the value of a 78 | [line](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Line) it must first be 79 | requested using [*gpiocdev.RequestLine*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#RequestLine): 80 | 81 | ```go 82 | l, _ := gpiocdev.RequestLine("gpiochip0", 4) // in its existing state 83 | ``` 84 | 85 | or from the [*Chip*](#chip-initialization) object using 86 | [*Chip.RequestLine*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Chip.RequestLine): 87 | 88 | ```go 89 | l, _ := c.RequestLine(4) // from a Chip object 90 | ``` 91 | 92 | The offset parameter identifies the line on the chip, and is specific to the 93 | GPIO chip. To improve readability, convenience mappings can be provided for 94 | specific devices, such as the Raspberry Pi: 95 | 96 | ```go 97 | l, _ := c.RequestLine(rpi.J8p7) // using Raspberry Pi J8 mapping 98 | ``` 99 | 100 | The initial configuration of the line can be set by providing line 101 | [configuration options](#configuration-options), as shown in this *AsOutput* 102 | example: 103 | 104 | ```go 105 | l, _ := gpiocdev.RequestLine("gpiochip0", 4, gpiocdev.AsOutput(1)) // as an output line 106 | ``` 107 | 108 | Multiple lines from the same chip may be requested as a collection of 109 | [lines](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Lines) using 110 | [*gpiocdev.RequestLines*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#RequestLines) 111 | 112 | ```go 113 | ll, _ := gpiocdev.RequestLines("gpiochip0", []int{0, 1, 2, 3}, gpiocdev.AsOutput(0, 0, 1, 1)) 114 | ``` 115 | 116 | or from a Chip object using 117 | [*Chip.RequestLines*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Chip.RequestLines): 118 | 119 | ```go 120 | ll, _ := c.RequestLines([]int{0, 1, 2, 3}, gpiocdev.AsOutput(0, 0, 1, 1)) 121 | ``` 122 | 123 | When no longer required, the line(s) should be closed to release resources: 124 | 125 | ```go 126 | l.Close() 127 | ll.Close() 128 | ``` 129 | 130 | ### Line Values 131 | 132 | Lines must be requsted using [*RequestLine*](#line-requests) before their 133 | values can be accessed. 134 | 135 | #### Read Input 136 | 137 | The current line value can be read with the 138 | [*Value*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Line.Value) 139 | method: 140 | 141 | ```go 142 | r, _ := l.Value() // Read state from line (active / inactive) 143 | ``` 144 | 145 | For collections of lines, the level of all lines is read simultaneously using 146 | the [*Values*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Lines.SetValues) 147 | method: 148 | 149 | ```go 150 | rr := []int{0, 0, 0, 0} // buffer to read into... 151 | ll.Values(rr) // Read the state of a collection of lines 152 | ``` 153 | 154 | #### Write Output 155 | 156 | For lines requested as *output*, the current line value can be set with the 157 | [*SetValue*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Line.SetValue) 158 | method: 159 | 160 | ```go 161 | l.SetValue(1) // Set line active 162 | l.SetValue(0) // Set line inactive 163 | ``` 164 | 165 | Also refer to the [toggle_line_value](examples/toggle_line_value/main.go) example. 166 | 167 | For collections of lines, all lines are set simultaneously using the 168 | [*SetValues*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Lines.SetValues) 169 | method: 170 | 171 | ```go 172 | ll.SetValues([]int{0, 1, 0, 1}) // Set a collection of lines 173 | ``` 174 | 175 | #### Edge Watches 176 | 177 | The value of an input line can be watched and trigger calls to handler 178 | functions. 179 | 180 | The watch can be on rising or falling edges, or both. 181 | 182 | The events are passed to a handler function provided using the 183 | *WithEventHandler(eh)* option. The handler function is passed a 184 | [*LineEvent*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#LineEvent), which 185 | contains details of the edge event including the offset of the triggering line, 186 | the time the edge was detected and the type of edge detected: 187 | 188 | ```go 189 | func handler(evt gpiocdev.LineEvent) { 190 | // handle edge event 191 | } 192 | 193 | l, _ = c.RequestLine(rpi.J8p7, gpiocdev.WithEventHandler(handler), gpiocdev.WithBothEdges) 194 | ``` 195 | 196 | To maintain event ordering, the event handler is called serially from a 197 | goroutine that reads the events from the kernel. The event handler is expected 198 | to be short lived, and so should hand off any potentially blocking operations to 199 | a separate goroutine. 200 | 201 | An edge watch can be removed by closing the line: 202 | 203 | ```go 204 | l.Close() 205 | ``` 206 | 207 | or by reconfiguring the requested lines to disable edge detection: 208 | 209 | ```go 210 | l.Reconfigure(gpiocdev.WithoutEdges) 211 | ``` 212 | 213 | Note that the *Close* waits for the event handler to return and so must not be 214 | called from the event handler context - it should be called from a separate 215 | goroutine. 216 | 217 | Also see the [watch_line_value](examples/watch_line_value/main.go) example. 218 | 219 | ### Line Configuration 220 | 221 | Line configuration is set via [options](#configuration-options) to 222 | *Chip.RequestLine(s)* and *Line.Reconfigure*. These override any default which 223 | may be set in *NewChip*. 224 | 225 | Note that configuration options applied to a collection of lines apply to all 226 | lines in the collection, unless they are applied to a subset of the requested 227 | lines using the *WithLines* option. 228 | 229 | #### Reconfiguration 230 | 231 | Requested lines may be reconfigured using the Reconfigure method: 232 | 233 | ```go 234 | l.Reconfigure(gpiocdev.AsInput) // set direction to Input 235 | ll.Reconfigure(gpiocdev.AsOutput(1, 0)) // set direction to Output (and values to active and inactive) 236 | ``` 237 | 238 | The *Line.Reconfigure* method accepts differential changes to the configuration 239 | for the lines, so option categories not specified or overridden by the specified 240 | changes will remain unchanged. 241 | 242 | The *Line.Reconfigure* method requires Linux 5.5 or later. 243 | 244 | #### Complex Configurations 245 | 246 | It is sometimes necessary for the configuration of lines within a request to 247 | have slightly different configurations. Line options may be applied to a subset 248 | of requested lines using the *WithLines(offsets, options)* option. 249 | 250 | The following example requests a set of output lines and sets some of the lines 251 | in the request to active low: 252 | 253 | ```go 254 | ll, _ = c.RequestLines([]int{0, 1, 2, 3}, gpiocdev.AsOutput(0, 0, 1, 1), 255 | gpiocdev.WithLines([]int{0, 3}, gpiocdev.AsActiveLow), 256 | gpiocdev.AsOpenDrain) 257 | ``` 258 | 259 | The configuration of the subset of lines inherits the configuration of the 260 | request at the point the *WithLines* is invoked. Subsequent changes to the 261 | request configuration do not alter the configuration of the subset - in the 262 | example above, lines 0 and 3 will not be configured as open-drain. 263 | 264 | Once a line's configuration has branched from the request configuration it can 265 | only be altered with *WithLines* options: 266 | 267 | ```go 268 | ll.Reconfigure(gpiocdev.WithLines([]int{0}, gpiocdev.AsActiveHigh)) 269 | ``` 270 | 271 | or reset to the request configuration using the *Defaulted* option: 272 | 273 | ```go 274 | ll.Reconfigure(gpiocdev.WithLines([]int{3}, gpiocdev.Defaulted)) 275 | ``` 276 | 277 | Complex configurations require Linux 5.10 or later. 278 | 279 | ### Chip Initialization 280 | 281 | The Chip object is used to discover details about avaialble lines and can be used 282 | to request lines from a GPIO chip. 283 | 284 | A Chip object is constructed using the 285 | [*NewChip*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#NewChip) function. 286 | 287 | ```go 288 | c, _ := gpiocdev.NewChip("gpiochip0") 289 | ``` 290 | 291 | The parameter is the chip name, which corresponds to the name of the device in 292 | the **/dev** directory, so in this example **/dev/gpiochip0**. 293 | 294 | The list of currently available GPIO chips is returned by the *Chips* function: 295 | 296 | ```go 297 | cc := gpiocdev.Chips() 298 | ``` 299 | 300 | Default attributes for Lines requested from the Chip can be set via 301 | [configuration options](#configuration-options) to 302 | [*NewChip*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#NewChip). 303 | 304 | ```go 305 | c, _ := gpiocdev.NewChip("gpiochip0", gpiocdev.WithConsumer("myapp")) 306 | ``` 307 | 308 | In this example the consumer label is defaulted to "myapp". 309 | 310 | When no longer required, the chip should be closed to release resources: 311 | 312 | ```go 313 | c.Close() 314 | ``` 315 | 316 | Closing a chip does not close or otherwise alter the state of any lines 317 | requested from the chip. 318 | 319 | ### Line Info 320 | 321 | [Info](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#LineInfo) about a line can 322 | be read at any time from the chip using the 323 | [*LineInfo*](https://pkg.go.dev/github.com/warthog618/go-gpiocdev#Chip.LineInfo) 324 | method: 325 | 326 | ```go 327 | inf, _ := c.LineInfo(4) 328 | inf, _ := c.LineInfo(rpi.J8p7) // Using Raspberry Pi J8 mapping 329 | ``` 330 | 331 | Note that the line info does not include the value. The line must be requested 332 | from the chip to access the value. 333 | 334 | Once requested, the line info can also be read from the line: 335 | 336 | ```go 337 | inf, _ := l.Info() 338 | infs, _ := ll.Info() 339 | ``` 340 | 341 | #### Info Watches 342 | 343 | Changes to the line info can be monitored by adding an info watch for the line: 344 | 345 | ```go 346 | func infoChangeHandler( evt gpiocdev.LineInfoChangeEvent) { 347 | // handle change in line info 348 | } 349 | 350 | inf, _ := c.WatchLineInfo(4, infoChangeHandler) 351 | ``` 352 | 353 | Note that the info watch does not monitor the line value (active or inactive) 354 | only its configuration. Refer to [Edge Watches](#edge-watches) for monitoring 355 | line value. 356 | 357 | An info watch can be cancelled by unwatching: 358 | 359 | ```go 360 | c.UnwatchLineInfo(4) 361 | ``` 362 | 363 | or by closing the chip. 364 | 365 | #### Categories 366 | 367 | Most line configuration options belong to one of the following categories: 368 | 369 | - Active Level 370 | - Direction 371 | - Bias 372 | - Drive 373 | - Debounce 374 | - Edge Detection 375 | - Event Clock 376 | 377 | Only one option from each category may be applied. If multiple options from a 378 | category are applied then all but the last are ignored. 379 | 380 | ##### Active Level 381 | 382 | The values used throughout the API for line values are the logical value, which 383 | is 0 for inactive and 1 for active. The physical value considered active can be 384 | controlled using the *AsActiveHigh* and *AsActiveLow* options: 385 | 386 | ```go 387 | l, _ := c.RequestLine(4, gpiocdev.AsActiveLow) // during request 388 | l.Reconfigure(gpiocdev.AsActiveHigh) // once requested 389 | ``` 390 | 391 | Lines are typically active high by default. 392 | 393 | ##### Direction 394 | 395 | The line direction can be controlled using the *AsInput* and *AsOutput* options: 396 | 397 | ```go 398 | l, _ := c.RequestLine(4, gpiocdev.AsInput) // during request 399 | l.Reconfigure(gpiocdev.AsInput) // set direction to Input 400 | l.Reconfigure(gpiocdev.AsOutput(0)) // set direction to Output (and value to inactive) 401 | ``` 402 | 403 | ##### Bias 404 | 405 | The bias options control the pull up/down state of the line: 406 | 407 | ```go 408 | l, _ = c.RequestLine(4, gpiocdev.WithPullUp) // during request 409 | l.Reconfigure(gpiocdev.WithBiasDisabled) // once requested 410 | ``` 411 | 412 | The bias options require Linux 5.5 or later. 413 | 414 | ##### Drive 415 | 416 | The drive options control how an output line is driven when active and inactive: 417 | 418 | ```go 419 | l,_ := c.RequestLine(4, gpiocdev.AsOpenDrain) // during request 420 | l.Reconfigure(gpiocdev.AsOpenSource) // once requested 421 | ``` 422 | 423 | The default drive for output lines is push-pull, which actively drives the line 424 | in both directions. 425 | 426 | ##### Debounce 427 | 428 | Input lines may be debounced using the *WithDebounce* option. The debouncing will 429 | be performed by the underlying hardware, if supported, else by the Linux 430 | kernel. 431 | 432 | ```go 433 | period := 10 * time.Millisecond 434 | l, _ = c.RequestLine(4, gpiocdev.WithDebounce(period))// during request 435 | l.Reconfigure(gpiocdev.WithDebounce(period)) // once requested 436 | ``` 437 | 438 | The WithDebounce option requires Linux 5.10 or later. 439 | 440 | ##### Edge Detection 441 | 442 | The edge options control which edges on input lines will generate edge events. 443 | Edge events are passed to the event handler specified in the *WithEventHandler(eh)* 444 | option. 445 | 446 | By default edge detection is not enabled on requested lines. 447 | 448 | Refer to [Edge Watches](#edge-watches) for examples of the edge detection options. 449 | 450 | ##### Event Clock 451 | 452 | The event clock options control the source clock used to timestamp edge events. 453 | This is only useful for Linux kernels 5.11 and later - prior to that the clock 454 | source is fixed. 455 | 456 | The event clock source used by the kernel has changed over time as follows: 457 | 458 | Kernel Version | Clock source 459 | --- | --- 460 | pre-5.7 | CLOCK_REALTIME 461 | 5.7 - 5.10 | CLOCK_MONOTONIC 462 | 5.11 and later | configurable (defaults to CLOCK_MONOTONIC) 463 | 464 | Determining which clock the edge event timestamps contain is currently left as 465 | an exercise for the user. 466 | 467 | #### Configuration Options 468 | 469 | The available configuration options are: 470 | 471 | Option | Category | Description 472 | ---|---|--- 473 | *WithConsumer***1** | Info | Set the consumer label for the lines 474 | *AsActiveLow* | Level | Treat a low physical line value as active 475 | *AsActiveHigh* | Level | Treat a high physical line value as active (**default**) 476 | *AsInput* | Direction | Request lines as input 477 | *AsIs***2** | Direction | Request lines in their current input/output state (**default**) 478 | *AsOutput(\...)***3** | Direction | Request lines as output with the provided values 479 | *AsPushPull* | Drive | Request output lines drive both high and low (**default**) 480 | *AsOpenDrain* | Drive | Request lines as open drain outputs 481 | *AsOpenSource* | Drive | Request lines as open source outputs 482 | *WithEventHandler(eh)**1*** | | Send edge events detected on requested lines to the provided handler 483 | *WithEventBufferSize(num)**1**,**5*** | | Suggest the minimum number of events that can be stored in the kernel event buffer for the requested lines 484 | *WithFallingEdge* | Edge Detection**3** | Request lines with falling edge detection 485 | *WithRisingEdge* | Edge Detection**3** | Request lines with rising edge detection 486 | *WithBothEdges* | Edge Detection**3** | Request lines with rising and falling edge detection 487 | *WithoutEdges***5** | Edge Detection**3** | Request lines with edge detection disabled (**default**) 488 | *WithBiasAsIs* | Bias**4** | Request the lines have their bias setting left unaltered (**default**) 489 | *WithBiasDisabled* | Bias**4** | Request the lines have internal bias disabled 490 | *WithPullDown* | Bias**4** | Request the lines have internal pull-down enabled 491 | *WithPullUp* | Bias**4** | Request the lines have internal pull-up enabled 492 | *WithDebounce(period)***5** | Debounce | Request the lines be debounced with the provided period 493 | *WithMonotonicEventClock* | Event Clock | Request the timestamp in edge events use the monotonic clock (**default**) 494 | *WithRealtimeEventClock***6** | Event Clock | Request the timestamp in edge events use the realtime clock 495 | *WithLines(offsets, options...)***3**,**5** | | Specify configuration options for a subset of lines in a request 496 | *Defaulted***5** | | Reset the configuration for a request to the default configuration, or the configuration of a particular line in a request to the default for that request 497 | 498 | The options described as **default** are generally not required, except to 499 | override other options earlier in a chain of configuration options. 500 | 501 | **1** Can be applied to either *NewChip* or *Chip.RequestLine*, but 502 | cannot be used with *Line.Reconfigure*. 503 | 504 | **2** Can be applied to *Chip.RequestLine*, but cannot be used 505 | with *NewChip* or *Line.Reconfigure*. 506 | 507 | **3** Can be applied to either *Chip.RequestLine* or 508 | *Line.Reconfigure*, but cannot be used with *NewChip*. 509 | 510 | **4** Requires Linux 5.5 or later. 511 | 512 | **5** Requires Linux 5.10 or later. 513 | 514 | **6** Requires Linux 5.11 or later. 515 | 516 | ## Installation 517 | 518 | On Linux: 519 | 520 | ```shell 521 | go get github.com/warthog618/go-gpiocdev 522 | ``` 523 | 524 | For other platforms, where you intend to cross-compile for Linux, don't attempt 525 | to compile the package when it is installed: 526 | 527 | ```shell 528 | go get -d github.com/warthog618/go-gpiocdev 529 | ``` 530 | 531 | ## Tools 532 | 533 | A companion package, **[gpiocdev-cli](https://github.com/warthog618/go-gpiocdev-cli)** provides a command line tool that allows 534 | manual or scripted manipulation of GPIO lines. This utility combines the Go 535 | equivalent of all the **libgpiod** command line tools into a single tool. 536 | 537 | ## Tests 538 | 539 | The library is fully tested, other than some error cases and sanity checks that 540 | are difficult to trigger. 541 | 542 | The tests require a kernel release 5.19 or later to run, built with 543 | **CONFIG_GPIO_SIM** set or as a module. 544 | 545 | The tests must be run as root, to allow contruction of **gpio-sims**. 546 | They can still be built as an unprivileged user, e.g. 547 | 548 | ```shell 549 | $ go test -c 550 | ``` 551 | 552 | but must be run as root. 553 | 554 | The tests can also be cross-compiled for other platforms. 555 | e.g. build tests for a Raspberry Pi using: 556 | 557 | ```shell 558 | $ GOOS=linux GOARCH=arm GOARM=6 go test -c 559 | ``` 560 | 561 | Later Pis can also use ARM7 (GOARM=7). 562 | 563 | ### Benchmarks 564 | 565 | The tests include benchmarks on reads, writes, bulk reads and writes, and 566 | interrupt latency. 567 | 568 | These are the results from a Raspberry Pi Zero W running Linux 6.4 and built 569 | with go1.20.6: 570 | 571 | ```shell 572 | $ ./go-gpiocdev.test -test.bench=.* 573 | goos: linux 574 | goarch: arm 575 | pkg: github.com/warthog618/go-gpiocdev 576 | BenchmarkChipNewClose 248 4381075 ns/op 577 | BenchmarkLineInfo 24651 47278 ns/op 578 | BenchmarkLineReconfigure 20312 55273 ns/op 579 | BenchmarkLineValue 71774 14933 ns/op 580 | BenchmarkLinesValues 54920 24659 ns/op 581 | BenchmarkLineSetValue 73359 16501 ns/op 582 | BenchmarkLinesSetValues 53557 21056 ns/op 583 | BenchmarkInterruptLatency 105 10407929 ns/op 584 | PASS 585 | ``` 586 | 587 | The latency benchmark is no longer representative as the measurement now depends 588 | on how quickly **gpio-sim** can toggle lines, and that is considerably slower 589 | than how quickly **gpiocdev** responds. For comparison, the same test using 590 | looped Raspberry Pi lines produced a result of ~640μsec on the same platform. 591 | 592 | And on a Raspberry Pi 4 running Linux 6.4 (32bit kernel) and built with go1.20.6: 593 | 594 | ```shell 595 | $ ./go-gpiocdev.test -test.bench=.* 596 | goos: linux 597 | goarch: arm 598 | pkg: github.com/warthog618/go-gpiocdev 599 | BenchmarkChipNewClose-4 9727 118291 ns/op 600 | BenchmarkLineInfo-4 185316 6104 ns/op 601 | BenchmarkLineReconfigure-4 364795 3205 ns/op 602 | BenchmarkLineValue-4 1072785 1061 ns/op 603 | BenchmarkLinesValues-4 816200 1428 ns/op 604 | BenchmarkLineSetValue-4 1015972 1150 ns/op 605 | BenchmarkLinesSetValues-4 715154 1717 ns/op 606 | BenchmarkInterruptLatency-4 18439 61145 ns/op 607 | PASS 608 | ``` 609 | 610 | ## Prerequisites 611 | 612 | The library targets Linux with support for the GPIO character device API. That 613 | generally means that **/dev/gpiochip0** exists. 614 | 615 | The caller must have access to the character device - typically 616 | **/dev/gpiochip0**. That is generally root unless you have changed the 617 | permissions of that device. 618 | 619 | The Bias line options and the Line.Reconfigure method both require Linux 5.5 or 620 | later. 621 | 622 | Debounce and other uAPI v2 features require Linux 5.10 or later. 623 | 624 | The requirements for each [configuration option](#configuration-options) are 625 | noted in that section. 626 | -------------------------------------------------------------------------------- /device/bananapi/bananapi.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package bananapi provides convenience mappings from Banana Pi pin names to 6 | // offsets. 7 | package bananapi 8 | 9 | import ( 10 | "errors" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // GPIO aliases to offsets 16 | var ( 17 | GPIO2 = GPIO_TO_OFFSET[2] 18 | GPIO3 = GPIO_TO_OFFSET[3] 19 | GPIO4 = GPIO_TO_OFFSET[4] 20 | GPIO5 = GPIO_TO_OFFSET[5] 21 | GPIO6 = GPIO_TO_OFFSET[6] 22 | GPIO7 = GPIO_TO_OFFSET[7] 23 | GPIO8 = GPIO_TO_OFFSET[8] 24 | GPIO9 = GPIO_TO_OFFSET[9] 25 | GPIO10 = GPIO_TO_OFFSET[10] 26 | GPIO11 = GPIO_TO_OFFSET[11] 27 | GPIO12 = GPIO_TO_OFFSET[12] 28 | GPIO13 = GPIO_TO_OFFSET[13] 29 | GPIO14 = GPIO_TO_OFFSET[14] 30 | GPIO15 = GPIO_TO_OFFSET[15] 31 | GPIO16 = GPIO_TO_OFFSET[16] 32 | GPIO17 = GPIO_TO_OFFSET[17] 33 | GPIO18 = GPIO_TO_OFFSET[18] 34 | GPIO19 = GPIO_TO_OFFSET[19] 35 | GPIO20 = GPIO_TO_OFFSET[20] 36 | GPIO21 = GPIO_TO_OFFSET[21] 37 | GPIO22 = GPIO_TO_OFFSET[22] 38 | GPIO23 = GPIO_TO_OFFSET[23] 39 | GPIO24 = GPIO_TO_OFFSET[24] 40 | GPIO25 = GPIO_TO_OFFSET[25] 41 | GPIO26 = GPIO_TO_OFFSET[26] 42 | GPIO27 = GPIO_TO_OFFSET[27] 43 | ) 44 | 45 | // GPIO_TO_OFFSET maps from GPIO number to offset. 46 | var GPIO_TO_OFFSET = map[int]int{ 47 | 2: 53, 48 | 3: 52, 49 | 4: 259, 50 | 5: 37, 51 | 6: 38, 52 | 7: 270, 53 | 8: 266, 54 | 9: 269, 55 | 10: 268, 56 | 11: 267, 57 | 12: 38, 58 | 13: 39, 59 | 14: 224, 60 | 15: 225, 61 | 16: 277, 62 | 17: 275, 63 | 18: 226, 64 | 19: 40, 65 | 20: 276, 66 | 21: 45, 67 | 22: 273, 68 | 23: 244, 69 | 24: 245, 70 | 25: 272, 71 | 26: 35, 72 | 27: 274, 73 | } 74 | 75 | // ErrInvalid indicates the pin name does not match a known pin. 76 | var ErrInvalid = errors.New("invalid pin number") 77 | 78 | func rangeCheck(p int) (int, error) { 79 | if p < 2 || p >= 27 { 80 | return 0, ErrInvalid 81 | } 82 | return p, nil 83 | } 84 | 85 | // Pin maps a pin string name to a pin number. 86 | // 87 | // Pin names are case insensitive and may be of the form GPIOX, or X. 88 | func Pin(s string) (int, error) { 89 | s = strings.ToLower(s) 90 | switch { 91 | case strings.HasPrefix(s, "gpio"): 92 | v, err := strconv.ParseInt(s[4:], 10, 8) 93 | if err != nil { 94 | return 0, err 95 | } 96 | return rangeCheck(int(v)) 97 | default: 98 | v, err := strconv.ParseInt(s, 10, 8) 99 | if err != nil { 100 | return 0, err 101 | } 102 | return rangeCheck(int(v)) 103 | } 104 | } 105 | 106 | // MustPin converts the string to the corresponding pin number or panics if that 107 | // is not possible. 108 | func MustPin(s string) int { 109 | v, err := Pin(s) 110 | if err != nil { 111 | panic(err) 112 | } 113 | return v 114 | } 115 | -------------------------------------------------------------------------------- /device/rpi/rpi.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // Package rpi provides convenience mappings from Raspberry Pi pin names to 6 | // offsets. 7 | // 8 | // The gpiochip these mappings apply to depends on the Pi variant. 9 | // For Pi0 to Pi4 it is gpiochip0. 10 | // For Pi5 it is gpiochip0 or gpiochip4 (depending on kernel version). 11 | package rpi 12 | 13 | import ( 14 | "errors" 15 | "strconv" 16 | "strings" 17 | ) 18 | 19 | // Convenience mapping from J8 pinouts to BCM pinouts. 20 | const ( 21 | J8p27 = iota 22 | J8p28 23 | J8p3 24 | J8p5 25 | J8p7 26 | J8p29 27 | J8p31 28 | J8p26 29 | J8p24 30 | J8p21 31 | J8p19 32 | J8p23 33 | J8p32 34 | J8p33 35 | J8p8 36 | J8p10 37 | J8p36 38 | J8p11 39 | J8p12 40 | J8p35 41 | J8p38 42 | J8p40 43 | J8p15 44 | J8p16 45 | J8p18 46 | J8p22 47 | J8p37 48 | J8p13 49 | ) 50 | 51 | // GPIO aliases to J8 pins 52 | const ( 53 | _ = iota 54 | _ 55 | GPIO2 56 | GPIO3 57 | GPIO4 58 | GPIO5 59 | GPIO6 60 | GPIO7 61 | GPIO8 62 | GPIO9 63 | GPIO10 64 | GPIO11 65 | GPIO12 66 | GPIO13 67 | GPIO14 68 | GPIO15 69 | GPIO16 70 | GPIO17 71 | GPIO18 72 | GPIO19 73 | GPIO20 74 | GPIO21 75 | GPIO22 76 | GPIO23 77 | GPIO24 78 | GPIO25 79 | GPIO26 80 | GPIO27 81 | MaxGPIOPin 82 | ) 83 | 84 | var j8Names = map[string]int{ 85 | "3": J8p3, 86 | "5": J8p5, 87 | "7": J8p7, 88 | "8": J8p8, 89 | "10": J8p10, 90 | "11": J8p11, 91 | "12": J8p12, 92 | "13": J8p13, 93 | "15": J8p15, 94 | "16": J8p16, 95 | "18": J8p18, 96 | "19": J8p19, 97 | "21": J8p21, 98 | "22": J8p22, 99 | "23": J8p23, 100 | "24": J8p24, 101 | "26": J8p26, 102 | "27": J8p27, 103 | "28": J8p28, 104 | "29": J8p29, 105 | "31": J8p31, 106 | "32": J8p32, 107 | "33": J8p33, 108 | "35": J8p35, 109 | "36": J8p36, 110 | "37": J8p37, 111 | "38": J8p38, 112 | "40": J8p40, 113 | } 114 | 115 | // ErrInvalid indicates the pin name does not match a known pin. 116 | var ErrInvalid = errors.New("invalid pin name") 117 | 118 | func rangeCheck(p int) (int, error) { 119 | if p < GPIO2 || p >= MaxGPIOPin { 120 | return 0, ErrInvalid 121 | } 122 | return p, nil 123 | } 124 | 125 | // Pin maps a pin string name to a pin number. 126 | // 127 | // Pin names are case insensitive and may be of the form J8pX, GPIOX, or X. 128 | func Pin(s string) (int, error) { 129 | s = strings.ToLower(s) 130 | switch { 131 | case strings.HasPrefix(s, "j8p"): 132 | v, ok := j8Names[s[3:]] 133 | if !ok { 134 | return 0, ErrInvalid 135 | } 136 | return v, nil 137 | case strings.HasPrefix(s, "gpio"): 138 | v, err := strconv.ParseInt(s[4:], 10, 8) 139 | if err != nil { 140 | return 0, err 141 | } 142 | return rangeCheck(int(v)) 143 | default: 144 | v, err := strconv.ParseInt(s, 10, 8) 145 | if err != nil { 146 | return 0, err 147 | } 148 | return rangeCheck(int(v)) 149 | } 150 | } 151 | 152 | // MustPin converts the string to the corresponding pin number or panics if that 153 | // is not possible. 154 | func MustPin(s string) int { 155 | v, err := Pin(s) 156 | if err != nil { 157 | panic(err) 158 | } 159 | return v 160 | } 161 | -------------------------------------------------------------------------------- /device/rpi/rpi_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package rpi_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/warthog618/go-gpiocdev/device/rpi" 12 | ) 13 | 14 | var patterns = []struct { 15 | name string 16 | val int 17 | err error 18 | }{ 19 | {"gpio0", 0, rpi.ErrInvalid}, 20 | {"gpio1", 0, rpi.ErrInvalid}, 21 | {"gpio2", 2, nil}, 22 | {"gpio02", 2, nil}, 23 | {"GPIO2", 2, nil}, 24 | {"Gpio2", 2, nil}, 25 | {"gpio27", 27, nil}, 26 | {"gpio28", 0, rpi.ErrInvalid}, 27 | {"J8p0", 0, rpi.ErrInvalid}, 28 | {"J8p1", 0, rpi.ErrInvalid}, 29 | {"J8p2", 0, rpi.ErrInvalid}, 30 | {"j8p3", rpi.J8p3, nil}, 31 | {"J8P3", rpi.J8p3, nil}, 32 | {"J8p3", rpi.J8p3, nil}, 33 | {"J8p4", 0, rpi.ErrInvalid}, 34 | {"J8p5", rpi.J8p5, nil}, 35 | {"J8p6", 0, rpi.ErrInvalid}, 36 | {"J8p7", rpi.J8p7, nil}, 37 | {"J8p8", rpi.J8p8, nil}, 38 | {"J8p9", 0, rpi.ErrInvalid}, 39 | {"J8p10", rpi.J8p10, nil}, 40 | {"J8p11", rpi.J8p11, nil}, 41 | {"J8p12", rpi.J8p12, nil}, 42 | {"J8p13", rpi.J8p13, nil}, 43 | {"J8p14", 0, rpi.ErrInvalid}, 44 | {"J8p15", rpi.J8p15, nil}, 45 | {"J8p16", rpi.J8p16, nil}, 46 | {"J8p17", 0, rpi.ErrInvalid}, 47 | {"J8p18", rpi.J8p18, nil}, 48 | {"J8p19", rpi.J8p19, nil}, 49 | {"J8p20", 0, rpi.ErrInvalid}, 50 | {"J8p21", rpi.J8p21, nil}, 51 | {"J8p22", rpi.J8p22, nil}, 52 | {"J8p23", rpi.J8p23, nil}, 53 | {"J8p24", rpi.J8p24, nil}, 54 | {"J8p25", 0, rpi.ErrInvalid}, 55 | {"J8p26", rpi.J8p26, nil}, 56 | {"J8p27", rpi.J8p27, nil}, 57 | {"J8p28", rpi.J8p28, nil}, 58 | {"J8p29", rpi.J8p29, nil}, 59 | {"J8p30", 0, rpi.ErrInvalid}, 60 | {"J8p31", rpi.J8p31, nil}, 61 | {"J8p32", rpi.J8p32, nil}, 62 | {"J8p33", rpi.J8p33, nil}, 63 | {"J8p34", 0, rpi.ErrInvalid}, 64 | {"J8p35", rpi.J8p35, nil}, 65 | {"J8p36", rpi.J8p36, nil}, 66 | {"J8p37", rpi.J8p37, nil}, 67 | {"J8p38", rpi.J8p38, nil}, 68 | {"J8p39", 0, rpi.ErrInvalid}, 69 | {"J8p40", rpi.J8p40, nil}, 70 | {"0", 0, rpi.ErrInvalid}, 71 | {"02", 2, nil}, 72 | {"2", 2, nil}, 73 | {"27", 27, nil}, 74 | {"40", 0, rpi.ErrInvalid}, 75 | } 76 | 77 | func TestPin(t *testing.T) { 78 | for _, p := range patterns { 79 | tf := func(t *testing.T) { 80 | val, err := rpi.Pin(p.name) 81 | assert.Equal(t, p.err, err) 82 | assert.Equal(t, p.val, val) 83 | } 84 | t.Run(p.name, tf) 85 | } 86 | } 87 | 88 | func TestMustPin(t *testing.T) { 89 | for _, p := range patterns { 90 | tf := func(t *testing.T) { 91 | if p.err != nil { 92 | assert.Panics(t, func() { 93 | rpi.MustPin(p.name) 94 | }) 95 | } else { 96 | val := rpi.MustPin(p.name) 97 | assert.Equal(t, p.val, val) 98 | } 99 | } 100 | t.Run(p.name, tf) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/find_line_by_name/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | find_line_by_name 5 | -------------------------------------------------------------------------------- /examples/find_line_by_name/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that finds a line by name. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | 14 | "github.com/warthog618/go-gpiocdev" 15 | ) 16 | 17 | // Finds the chip and offset of a named line. 18 | func main() { 19 | name := "GPIO22" 20 | if len(os.Args) > 1 { 21 | name = os.Args[1] 22 | } 23 | chip, offset, err := gpiocdev.FindLine(name) 24 | if err != nil { 25 | fmt.Printf("Finding line %s returned error: %s\n", name, err) 26 | os.Exit(1) 27 | } 28 | fmt.Printf("%s:%d %s\n", chip, offset, name) 29 | } 30 | -------------------------------------------------------------------------------- /examples/get_chip_info/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | get_chip_info 5 | -------------------------------------------------------------------------------- /examples/get_chip_info/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that read the info for gpiochip0. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | 14 | "github.com/warthog618/go-gpiocdev" 15 | ) 16 | 17 | // Reads the info for gpiochip0. 18 | func main() { 19 | c, err := gpiocdev.NewChip("gpiochip0") 20 | if err != nil { 21 | fmt.Printf("Opening chip returned error: %s\n", err) 22 | os.Exit(1) 23 | } 24 | defer c.Close() 25 | 26 | fmt.Printf("%s (%s): %d lines\n", c.Name, c.Label, c.Lines()) 27 | } 28 | -------------------------------------------------------------------------------- /examples/get_line_info/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | get_line_info 5 | -------------------------------------------------------------------------------- /examples/get_line_info/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that read the info for line 22 on gpiochip0. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | 14 | "github.com/warthog618/go-gpiocdev" 15 | ) 16 | 17 | // Reads the info for line 22 on gpiochip0. 18 | func main() { 19 | c, err := gpiocdev.NewChip("gpiochip0") 20 | if err != nil { 21 | fmt.Printf("Opening chip returned error: %s\n", err) 22 | os.Exit(1) 23 | } 24 | defer c.Close() 25 | 26 | info, err := c.LineInfo(22) 27 | if err != nil { 28 | fmt.Printf("Reading line info returned error: %s\n", err) 29 | os.Exit(1) 30 | } 31 | fmt.Printf("%v\n", info) 32 | } 33 | -------------------------------------------------------------------------------- /examples/get_line_value/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | get_line_value 5 | -------------------------------------------------------------------------------- /examples/get_line_value/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that reads an input pin. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | 13 | "github.com/warthog618/go-gpiocdev" 14 | ) 15 | 16 | // This example reads line 22 on gpiochip0. 17 | func main() { 18 | offset := 22 19 | chip := "gpiochip0" 20 | l, err := gpiocdev.RequestLine(chip, offset, gpiocdev.AsInput) 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer l.Close() 25 | 26 | values := map[int]string{0: "inactive", 1: "active"} 27 | v, err := l.Value() 28 | fmt.Printf("%s:%d %s\n", chip, offset, values[v]) 29 | } 30 | -------------------------------------------------------------------------------- /examples/get_multiple_line_values/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | get_multiple_line_values 5 | -------------------------------------------------------------------------------- /examples/get_multiple_line_values/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that reads multiple input pins. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | 14 | "github.com/warthog618/go-gpiocdev" 15 | ) 16 | 17 | // This example reads lines 21 and 22 on gpiochip0. 18 | func main() { 19 | offsets := []int{21, 22} 20 | chip := "gpiochip0" 21 | l, err := gpiocdev.RequestLines(chip, offsets, gpiocdev.AsInput) 22 | if err != nil { 23 | fmt.Printf("Requesting lines returned error: %s\n", err) 24 | os.Exit(1) 25 | } 26 | defer l.Close() 27 | 28 | values := map[int]string{0: "inactive", 1: "active"} 29 | vv := []int{0, 0} 30 | err = l.Values(vv) 31 | if err != nil { 32 | fmt.Printf("Reading values returned error: %s\n", err) 33 | os.Exit(1) 34 | } 35 | for i, o := range offsets { 36 | fmt.Printf("%s:%d %s\n", chip, o, values[vv[i]]) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/readme/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | readme 5 | -------------------------------------------------------------------------------- /examples/readme/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A collection of code snippets contained in the READMEs. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "time" 14 | 15 | "github.com/warthog618/go-gpiocdev" 16 | "github.com/warthog618/go-gpiocdev/device/rpi" 17 | "github.com/warthog618/go-gpiocdev/uapi" 18 | "golang.org/x/sys/unix" 19 | ) 20 | 21 | func main() { 22 | // Chip Initialisation 23 | c, _ := gpiocdev.NewChip("gpiochip0", gpiocdev.WithConsumer("myapp")) 24 | 25 | // Quick Start 26 | in, _ := gpiocdev.RequestLine("gpiochip0", 2, gpiocdev.AsInput) 27 | val, _ := in.Value() 28 | out, _ := gpiocdev.RequestLine("gpiochip0", 3, gpiocdev.AsOutput(val)) 29 | in.Close() 30 | out.Close() 31 | 32 | // Line Requests 33 | l, _ := gpiocdev.RequestLine("gpiochip0", 4) 34 | l.Close() 35 | l, _ = c.RequestLine(4) 36 | l.Close() 37 | l, _ = c.RequestLine(rpi.J8p7) // Using Raspberry Pi J8 mapping. 38 | l.Close() 39 | l, _ = c.RequestLine(4, gpiocdev.AsOutput(1)) 40 | 41 | ll, _ := gpiocdev.RequestLines("gpiochip0", []int{0, 1, 2, 3}, gpiocdev.AsOutput(0, 0, 1, 1)) 42 | ll.Close() 43 | 44 | ll, _ = c.RequestLines([]int{0, 1, 2, 3}, gpiocdev.AsOutput(0, 0, 1, 1)) 45 | 46 | // Line Info 47 | inf, _ := c.LineInfo(2) 48 | fmt.Printf("name: %s\n", inf.Name) // ineffassign bypass 49 | inf, _ = c.LineInfo(rpi.J8p7) 50 | fmt.Printf("name: %s\n", inf.Name) // ineffassign bypass 51 | inf, _ = l.Info() 52 | infs, _ := ll.Info() 53 | fmt.Printf("name: %s\n", inf.Name) 54 | fmt.Printf("name: %s\n", infs[0].Name) 55 | 56 | // Info Watch 57 | inf, _ = c.WatchLineInfo(4, infoChangeHandler) 58 | c.UnwatchLineInfo(4) 59 | 60 | // Active Level 61 | l, _ = c.RequestLine(4, gpiocdev.AsActiveLow) // during request 62 | l.Reconfigure(gpiocdev.AsActiveHigh) // once requested 63 | 64 | // Direction 65 | l.Reconfigure(gpiocdev.AsInput) // Set direction to Input 66 | l.Reconfigure(gpiocdev.AsOutput(1, 0)) // Set direction to Output (and values to active and inactive) 67 | 68 | // Input 69 | r, _ := l.Value() // Read state from line (active/inactive) 70 | fmt.Printf("value: %d\n", r) 71 | 72 | rr := []int{0, 0, 0, 0} 73 | ll.Values(rr) // Read state from a group of lines 74 | 75 | // Output 76 | l.SetValue(1) // Set line active 77 | l.SetValue(0) // Set line inactive 78 | 79 | ll.SetValues([]int{0, 1, 0, 1}) // Set a group of lines 80 | 81 | // Bias 82 | l, _ = c.RequestLine(4, gpiocdev.WithPullUp) // during request 83 | l.Reconfigure(gpiocdev.WithBiasDisabled) // once requested 84 | 85 | // Debounce 86 | period := 10 * time.Millisecond 87 | l, _ = c.RequestLine(4, gpiocdev.WithDebounce(period)) // during request 88 | l.Reconfigure(gpiocdev.WithDebounce(period)) // once requested 89 | 90 | // Edge Watches 91 | l, _ = c.RequestLine(rpi.J8p7, gpiocdev.WithEventHandler(handler), gpiocdev.WithBothEdges) 92 | l.Reconfigure(gpiocdev.WithoutEdges) 93 | 94 | // Options 95 | ll, _ = c.RequestLines([]int{0, 1, 2, 3}, gpiocdev.AsOutput(0, 0, 1, 1), 96 | gpiocdev.WithLines([]int{0, 3}, gpiocdev.AsActiveLow), 97 | gpiocdev.AsOpenDrain) 98 | ll.Reconfigure(gpiocdev.WithLines([]int{0}, gpiocdev.AsActiveHigh)) 99 | ll.Reconfigure(gpiocdev.WithLines([]int{3}, gpiocdev.Defaulted)) 100 | 101 | // Line Requests (2) 102 | l.Close() 103 | ll.Close() 104 | 105 | // Chip Initialisation (2) 106 | c.Close() 107 | } 108 | 109 | func handler(evt gpiocdev.LineEvent) { 110 | // handle change in line state 111 | } 112 | 113 | func infoChangeHandler(evt gpiocdev.LineInfoChangeEvent) { 114 | // handle change in line info 115 | } 116 | 117 | func uapiV2() error { 118 | offset := 32 119 | offsets := []int{1, 2, 3} 120 | 121 | f, _ := os.OpenFile("/dev/gpiochip0", unix.O_CLOEXEC, unix.O_RDONLY) 122 | 123 | // get chip info 124 | ci, _ := uapi.GetChipInfo(f.Fd()) 125 | fmt.Print(ci) 126 | 127 | // get line info 128 | li, _ := uapi.GetLineInfo(f.Fd(), offset) 129 | fmt.Print(li) 130 | 131 | // request a line 132 | lr := uapi.LineRequest{ 133 | Lines: uint32(len(offsets)), 134 | Config: uapi.LineConfig{ 135 | Flags: uapi.LineFlagV2Output, 136 | }, 137 | // initialise Offsets, DefaultValues and Consumer... 138 | } 139 | err := uapi.GetLine(f.Fd(), &lr) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | // request a line with events 145 | lr = uapi.LineRequest{ 146 | Lines: uint32(len(offsets)), 147 | Config: uapi.LineConfig{ 148 | Flags: uapi.LineFlagV2Input | uapi.LineFlagV2ActiveLow | uapi.LineFlagV2EdgeBoth, 149 | }, 150 | // initialise Offsets and Consumer... 151 | } 152 | err = uapi.GetLine(f.Fd(), &lr) 153 | if err != nil { 154 | // wait on lr.fd for events... 155 | 156 | // read event 157 | evt, _ := uapi.ReadLineEvent(uintptr(lr.Fd)) 158 | fmt.Print(evt) 159 | } 160 | 161 | // get values 162 | var values uapi.LineValues 163 | err = uapi.GetLineValuesV2(uintptr(lr.Fd), &values) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | // set values 169 | err = uapi.SetLineValuesV2(uintptr(lr.Fd), values) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | // update line config - change to outputs 175 | err = uapi.SetLineConfigV2(uintptr(lr.Fd), &uapi.LineConfig{ 176 | Flags: uapi.LineFlagV2Output, 177 | NumAttrs: 1, 178 | // initialise OutputValues... 179 | }) 180 | 181 | return err 182 | } 183 | 184 | func uapiV1() error { 185 | offset := 32 186 | offsets := []int{1, 2, 3} 187 | value := 1 188 | 189 | f, _ := os.OpenFile("/dev/gpiochip0", unix.O_CLOEXEC, unix.O_RDONLY) 190 | 191 | // get chip info 192 | ci, _ := uapi.GetChipInfo(f.Fd()) 193 | fmt.Print(ci) 194 | 195 | // get line info 196 | li, _ := uapi.GetLineInfo(f.Fd(), offset) 197 | fmt.Print(li) 198 | 199 | // request a line 200 | hr := uapi.HandleRequest{ 201 | Lines: uint32(len(offsets)), 202 | Flags: uapi.HandleRequestOutput, 203 | // initialise Offsets, DefaultValues and Consumer... 204 | } 205 | err := uapi.GetLineHandle(f.Fd(), &hr) 206 | if err != nil { 207 | return err 208 | } 209 | 210 | // request a line with events 211 | er := uapi.EventRequest{ 212 | Offset: uint32(offset), 213 | HandleFlags: uapi.HandleRequestActiveLow, 214 | EventFlags: uapi.EventRequestBothEdges, 215 | // initialise Consumer... 216 | } 217 | err = uapi.GetLineEvent(f.Fd(), &er) 218 | if err != nil { 219 | // wait on er.fd for events... 220 | 221 | // read event 222 | evt, _ := uapi.ReadEvent(uintptr(er.Fd)) 223 | fmt.Print(evt) 224 | } 225 | 226 | // get values 227 | var values uapi.HandleData 228 | err = uapi.GetLineValues(uintptr(er.Fd), &values) 229 | if err != nil { 230 | return err 231 | } 232 | 233 | // set values 234 | values[0] = uint8(value) 235 | err = uapi.SetLineValues(uintptr(hr.Fd), values) 236 | if err != nil { 237 | return err 238 | } 239 | 240 | // update line config - change to active low 241 | err = uapi.SetLineConfig(uintptr(hr.Fd), &uapi.HandleConfig{ 242 | Flags: uapi.HandleRequestInput, 243 | }) 244 | 245 | return err 246 | } 247 | -------------------------------------------------------------------------------- /examples/reconfigure_input_to_output/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | reconfigure_input_to_output 5 | -------------------------------------------------------------------------------- /examples/reconfigure_input_to_output/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that requests a line as an input and subsequently switches it to an output. 8 | // DO NOT run this on a platform where that line is externally driven. 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | "time" 14 | 15 | "github.com/warthog618/go-gpiocdev" 16 | ) 17 | 18 | // This example requests line 23 on gpiochip0 as an input then switches it to an output. 19 | // DO NOT run this on a platform where that line is externally driven. 20 | func main() { 21 | offset := 23 22 | chip := "gpiochip0" 23 | l, err := gpiocdev.RequestLine(chip, offset, gpiocdev.AsInput) 24 | if err != nil { 25 | panic(err) 26 | } 27 | // revert line to input on the way out. 28 | defer func() { 29 | l.Reconfigure(gpiocdev.AsInput) 30 | fmt.Printf("Input pin: %s:%d\n", chip, offset) 31 | l.Close() 32 | }() 33 | 34 | values := map[int]string{0: "inactive", 1: "active"} 35 | v, err := l.Value() 36 | fmt.Printf("Read pin: %s:%d %s\n", chip, offset, values[v]) 37 | 38 | l.Reconfigure(gpiocdev.AsOutput(v)) 39 | fmt.Printf("Set pin %s:%d %s\n", chip, offset, values[v]) 40 | time.Sleep(500 * time.Millisecond) 41 | v ^= 1 42 | l.SetValue(v) 43 | fmt.Printf("Set pin %s:%d %s\n", chip, offset, values[v]) 44 | } 45 | -------------------------------------------------------------------------------- /examples/select_watch_line_value/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | select_watch_line_value 5 | -------------------------------------------------------------------------------- /examples/select_watch_line_value/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that watches an input pin and reports edge events. 8 | // This is a version of the watch_line_value example that performs the watching within 9 | // a select. 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "os" 16 | "syscall" 17 | "time" 18 | 19 | "github.com/warthog618/go-gpiocdev" 20 | ) 21 | 22 | func printEvent(evt gpiocdev.LineEvent) { 23 | t := time.Now() 24 | edge := "rising" 25 | if evt.Type == gpiocdev.LineEventFallingEdge { 26 | edge = "falling" 27 | } 28 | if evt.Seqno != 0 { 29 | // only uAPI v2 populates the sequence numbers 30 | fmt.Printf("event: #%d(%d)%3d %-7s %s (%s)\n", 31 | evt.Seqno, 32 | evt.LineSeqno, 33 | evt.Offset, 34 | edge, 35 | t.Format(time.RFC3339Nano), 36 | evt.Timestamp) 37 | } else { 38 | fmt.Printf("event:%3d %-7s %s (%s)\n", 39 | evt.Offset, 40 | edge, 41 | t.Format(time.RFC3339Nano), 42 | evt.Timestamp) 43 | } 44 | } 45 | 46 | // Watches line 23 on gpiochip0 and reports when it changes state. 47 | func main() { 48 | echan := make(chan gpiocdev.LineEvent, 6) 49 | 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 51 | eh := func(evt gpiocdev.LineEvent) { 52 | select { 53 | case echan <- evt: // the expected path 54 | default: 55 | // if you want the handler to block, rather than dropping 56 | // events when the channel fills then <- ctx.Done() instead 57 | // to ensure that the handler can't be left blocked 58 | fmt.Println("event chan overflow - discarding event") 59 | } 60 | } 61 | 62 | offset := 23 63 | chip := "gpiochip0" 64 | l, err := gpiocdev.RequestLine(chip, offset, 65 | gpiocdev.WithPullUp, 66 | gpiocdev.WithBothEdges, 67 | gpiocdev.WithEventHandler(eh)) 68 | if err != nil { 69 | fmt.Printf("RequestLine returned error: %s\n", err) 70 | if err == syscall.Errno(22) { 71 | fmt.Println("Note that the WithPullUp option requires kernel V5.5 or later - check your kernel version.") 72 | } 73 | os.Exit(1) 74 | } 75 | 76 | fmt.Printf("Watching Pin %s:%d...\n", chip, offset) 77 | done := false 78 | for !done { 79 | select { 80 | // depending on the application other cases could deal with other channels 81 | case evt := <-echan: 82 | printEvent(evt) 83 | case <-ctx.Done(): 84 | fmt.Println("select_watch_line_value exiting...") 85 | l.Close() 86 | done = true 87 | } 88 | } 89 | cancel() 90 | } 91 | -------------------------------------------------------------------------------- /examples/toggle_line_value/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | toggle_line_value 5 | -------------------------------------------------------------------------------- /examples/toggle_line_value/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that toggles an output pin. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | "time" 16 | 17 | "github.com/warthog618/go-gpiocdev" 18 | ) 19 | 20 | // This example drives line 22 on gpiochip0. 21 | // The pin is toggled high and low at 1Hz with a 50% duty cycle. 22 | // DO NOT run this on a device which has this pin externally driven. 23 | func main() { 24 | offset := 22 25 | chip := "gpiochip0" 26 | v := 0 27 | l, err := gpiocdev.RequestLine(chip, offset, gpiocdev.AsOutput(v)) 28 | if err != nil { 29 | panic(err) 30 | } 31 | // revert line to input on the way out. 32 | defer func() { 33 | l.Reconfigure(gpiocdev.AsInput) 34 | fmt.Printf("Input pin %s:%d\n", chip, offset) 35 | l.Close() 36 | }() 37 | values := map[int]string{0: "inactive", 1: "active"} 38 | fmt.Printf("Set pin %s:%d %s\n", chip, offset, values[v]) 39 | 40 | // capture exit signals to ensure pin is reverted to input on exit. 41 | quit := make(chan os.Signal, 1) 42 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 43 | defer signal.Stop(quit) 44 | 45 | for { 46 | select { 47 | case <-time.After(500 * time.Millisecond): 48 | v ^= 1 49 | l.SetValue(v) 50 | fmt.Printf("Set pin %s:%d %s\n", chip, offset, values[v]) 51 | case <-quit: 52 | return 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/toggle_multiple_line_values/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | toggle_multiple_line_values 5 | -------------------------------------------------------------------------------- /examples/toggle_multiple_line_values/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that toggles multiple output pins. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "os/signal" 14 | "syscall" 15 | "time" 16 | 17 | "github.com/warthog618/go-gpiocdev" 18 | ) 19 | 20 | // This example drives lines 21 and 22 on gpiochip0. 21 | // The pins are toggled high and low opposite each other at 1Hz with a 50% duty cycle. 22 | // DO NOT run this on a device which has either of those pins externally driven. 23 | func main() { 24 | offsets := []int{21, 22} 25 | chip := "gpiochip0" 26 | vv := []int{0, 1} 27 | l, err := gpiocdev.RequestLines(chip, offsets, gpiocdev.AsOutput(vv...)) 28 | if err != nil { 29 | panic(err) 30 | } 31 | // revert lines to input on the way out. 32 | defer func() { 33 | l.Reconfigure(gpiocdev.AsInput) 34 | fmt.Printf("Input pins %s:%v\n", chip, offsets) 35 | l.Close() 36 | }() 37 | 38 | values := map[int]string{0: "inactive", 1: "active"} 39 | for i, o := range offsets { 40 | fmt.Printf("Set pin %s:%d %s\n", chip, o, values[vv[i]]) 41 | } 42 | 43 | // capture exit signals to ensure pin is reverted to input on exit. 44 | quit := make(chan os.Signal, 1) 45 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 46 | defer signal.Stop(quit) 47 | 48 | for { 49 | select { 50 | case <-time.After(500 * time.Millisecond): 51 | for i := range vv { 52 | vv[i] ^= 1 53 | } 54 | l.SetValues(vv) 55 | for i, o := range offsets { 56 | fmt.Printf("Set pin %s:%d %s\n", chip, o, values[vv[i]]) 57 | } 58 | case <-quit: 59 | return 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/watch_line_info/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | watch_line_info 5 | -------------------------------------------------------------------------------- /examples/watch_line_info/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that watches an input pin and reports edge events. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "time" 14 | 15 | "github.com/warthog618/go-gpiocdev" 16 | ) 17 | 18 | func eventHandler(evt gpiocdev.LineInfoChangeEvent) { 19 | t := time.Now() 20 | fmt.Printf("%s event: %#v\n", t.Format(time.RFC3339Nano), evt) 21 | } 22 | 23 | // Watches lines 21-23 on gpiochip0 and and reports when they change state. 24 | func main() { 25 | offsets := []int{21, 22, 23} 26 | chip := "gpiochip0" 27 | c, err := gpiocdev.NewChip(chip) 28 | if err != nil { 29 | fmt.Printf("Opening chip returned error: %s\n", err) 30 | os.Exit(1) 31 | } 32 | defer c.Close() 33 | 34 | for _, o := range offsets { 35 | info, err := c.WatchLineInfo(o, eventHandler) 36 | if err != nil { 37 | fmt.Printf("Watching line %d returned error: %s\n", o, err) 38 | os.Exit(1) 39 | } 40 | fmt.Printf("Watching Pin %s:%d: %#v\n", chip, o, info) 41 | } 42 | // In a real application the main thread would do something useful. 43 | // But we'll just run for a minute then exit. 44 | time.Sleep(time.Minute) 45 | fmt.Println("watch_line_info exiting...") 46 | } 47 | -------------------------------------------------------------------------------- /examples/watch_line_rising/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | watch_line_rising 5 | -------------------------------------------------------------------------------- /examples/watch_line_rising/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that watches an input pin and reports rising edge events. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/warthog618/go-gpiocdev" 17 | ) 18 | 19 | func eventHandler(evt gpiocdev.LineEvent) { 20 | t := time.Now() 21 | edge := "rising" 22 | if evt.Type == gpiocdev.LineEventFallingEdge { 23 | // shouldn't see any of these, but check to confirm 24 | edge = "falling" 25 | } 26 | if evt.Seqno != 0 { 27 | // only uAPI v2 populates the sequence numbers 28 | fmt.Printf("%s event: #%d(%d)%3d %-7s (%s)\n", 29 | t.Format(time.RFC3339Nano), 30 | evt.Seqno, 31 | evt.LineSeqno, 32 | evt.Offset, 33 | edge, 34 | evt.Timestamp) 35 | } else { 36 | fmt.Printf("%s event:%3d %-7s (%s)\n", 37 | t.Format(time.RFC3339Nano), 38 | evt.Offset, 39 | edge, 40 | evt.Timestamp) 41 | } 42 | } 43 | 44 | // Watches gpiochip0:23 reports when it rises. 45 | func main() { 46 | offset := 23 47 | chip := "gpiochip0" 48 | l, err := gpiocdev.RequestLine(chip, offset, 49 | gpiocdev.WithPullUp, 50 | gpiocdev.WithRisingEdge, 51 | gpiocdev.WithEventHandler(eventHandler)) 52 | if err != nil { 53 | fmt.Printf("RequestLine returned error: %s\n", err) 54 | if err == syscall.Errno(22) { 55 | fmt.Println("Note that the WithPullUp option requires Linux 5.5 or later - check your kernel version.") 56 | } 57 | os.Exit(1) 58 | } 59 | defer l.Close() 60 | 61 | // In a real application the main thread would do something useful. 62 | // But we'll just run for a minute then exit. 63 | fmt.Printf("Watching Pin %s:%d...\n", chip, offset) 64 | time.Sleep(time.Minute) 65 | fmt.Println("watch_line_rising exiting...") 66 | } 67 | -------------------------------------------------------------------------------- /examples/watch_line_value/.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2019 Kent Gibson . 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | watch_line_value 5 | -------------------------------------------------------------------------------- /examples/watch_line_value/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // A simple example that watches an input pin and reports edge events. 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "os" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/warthog618/go-gpiocdev" 17 | ) 18 | 19 | func eventHandler(evt gpiocdev.LineEvent) { 20 | t := time.Now() 21 | edge := "rising" 22 | if evt.Type == gpiocdev.LineEventFallingEdge { 23 | edge = "falling" 24 | } 25 | if evt.Seqno != 0 { 26 | // only uAPI v2 populates the sequence numbers 27 | fmt.Printf("event: #%d(%d)%3d %-7s %s (%s)\n", 28 | evt.Seqno, 29 | evt.LineSeqno, 30 | evt.Offset, 31 | edge, 32 | t.Format(time.RFC3339Nano), 33 | evt.Timestamp) 34 | } else { 35 | fmt.Printf("event:%3d %-7s %s (%s)\n", 36 | evt.Offset, 37 | edge, 38 | t.Format(time.RFC3339Nano), 39 | evt.Timestamp) 40 | } 41 | } 42 | 43 | // Watches gpiochip0:23 and reports when it changes state. 44 | func main() { 45 | offset := 23 46 | chip := "gpiochip0" 47 | l, err := gpiocdev.RequestLine(chip, offset, 48 | gpiocdev.WithPullUp, 49 | gpiocdev.WithBothEdges, 50 | gpiocdev.WithEventHandler(eventHandler)) 51 | if err != nil { 52 | fmt.Printf("RequestLine returned error: %s\n", err) 53 | if err == syscall.Errno(22) { 54 | fmt.Println("Note that the WithPullUp option requires Linux 5.5 or later - check your kernel version.") 55 | } 56 | os.Exit(1) 57 | } 58 | defer l.Close() 59 | 60 | // In a real application the main thread would do something useful. 61 | // But we'll just run for a minute then exit. 62 | fmt.Printf("Watching Pin %s:%d...\n", chip, offset) 63 | time.Sleep(time.Minute) 64 | fmt.Println("watch_line_value exiting...") 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: CC0-1.0 4 | 5 | module github.com/warthog618/go-gpiocdev 6 | 7 | go 1.19 8 | 9 | require ( 10 | github.com/stretchr/testify v1.9.0 11 | github.com/warthog618/go-gpiosim v0.1.2 12 | golang.org/x/sys v0.29.0 13 | ) 14 | 15 | require ( 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/pkg/errors v0.9.1 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 8 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | github.com/warthog618/go-gpiosim v0.1.2 h1:8RsinSKZbsG6REhfVDpBr7gdzQeJ7RoCUxLR9oUlk0M= 10 | github.com/warthog618/go-gpiosim v0.1.2/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU= 11 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 12 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /go.sum.license: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /gpiocdev_bmark_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package gpiocdev_test 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/require" 12 | "github.com/warthog618/go-gpiocdev" 13 | "github.com/warthog618/go-gpiosim" 14 | ) 15 | 16 | func BenchmarkChipNewClose(b *testing.B) { 17 | s, err := gpiosim.NewSimpleton(6) 18 | require.Nil(b, err) 19 | defer s.Close() 20 | for i := 0; i < b.N; i++ { 21 | c, _ := gpiocdev.NewChip(s.DevPath()) 22 | c.Close() 23 | } 24 | } 25 | 26 | func BenchmarkLineInfo(b *testing.B) { 27 | s, err := gpiosim.NewSimpleton(6) 28 | require.Nil(b, err) 29 | defer s.Close() 30 | c, err := gpiocdev.NewChip(s.DevPath()) 31 | require.Nil(b, err) 32 | require.NotNil(b, c) 33 | defer c.Close() 34 | for i := 0; i < b.N; i++ { 35 | c.LineInfo(3) 36 | } 37 | } 38 | 39 | func BenchmarkLineReconfigure(b *testing.B) { 40 | s, err := gpiosim.NewSimpleton(6) 41 | require.Nil(b, err) 42 | defer s.Close() 43 | c, err := gpiocdev.NewChip(s.DevPath()) 44 | require.Nil(b, err) 45 | require.NotNil(b, c) 46 | defer c.Close() 47 | l, err := c.RequestLine(3) 48 | require.Nil(b, err) 49 | require.NotNil(b, l) 50 | defer l.Close() 51 | for i := 0; i < b.N; i++ { 52 | l.Reconfigure(gpiocdev.AsActiveLow) 53 | } 54 | } 55 | 56 | func BenchmarkLineValue(b *testing.B) { 57 | s, err := gpiosim.NewSimpleton(6) 58 | require.Nil(b, err) 59 | defer s.Close() 60 | c, err := gpiocdev.NewChip(s.DevPath()) 61 | require.Nil(b, err) 62 | require.NotNil(b, c) 63 | defer c.Close() 64 | l, err := c.RequestLine(3) 65 | require.Nil(b, err) 66 | require.NotNil(b, l) 67 | defer l.Close() 68 | for i := 0; i < b.N; i++ { 69 | l.Value() 70 | } 71 | } 72 | 73 | func BenchmarkLinesValues(b *testing.B) { 74 | s, err := gpiosim.NewSimpleton(6) 75 | require.Nil(b, err) 76 | defer s.Close() 77 | c, err := gpiocdev.NewChip(s.DevPath()) 78 | require.Nil(b, err) 79 | require.NotNil(b, c) 80 | defer c.Close() 81 | l, err := c.RequestLines([]int{1, 2, 3}) 82 | require.Nil(b, err) 83 | require.NotNil(b, l) 84 | defer l.Close() 85 | vv := make([]int, len(l.Offsets())) 86 | for i := 0; i < b.N; i++ { 87 | l.Values(vv) 88 | } 89 | } 90 | func BenchmarkLineSetValue(b *testing.B) { 91 | s, err := gpiosim.NewSimpleton(6) 92 | require.Nil(b, err) 93 | defer s.Close() 94 | c, err := gpiocdev.NewChip(s.DevPath()) 95 | require.Nil(b, err) 96 | require.NotNil(b, c) 97 | defer c.Close() 98 | l, err := c.RequestLine(3, gpiocdev.AsOutput(0)) 99 | require.Nil(b, err) 100 | require.NotNil(b, l) 101 | defer l.Close() 102 | for i := 0; i < b.N; i++ { 103 | l.SetValue(1) 104 | } 105 | } 106 | 107 | func BenchmarkLinesSetValues(b *testing.B) { 108 | s, err := gpiosim.NewSimpleton(6) 109 | require.Nil(b, err) 110 | defer s.Close() 111 | c, err := gpiocdev.NewChip(s.DevPath()) 112 | require.Nil(b, err) 113 | require.NotNil(b, c) 114 | defer c.Close() 115 | ll, err := c.RequestLines([]int{1, 2}, gpiocdev.AsOutput(0)) 116 | require.Nil(b, err) 117 | require.NotNil(b, ll) 118 | defer ll.Close() 119 | vv := []int{0, 0} 120 | for i := 0; i < b.N; i++ { 121 | vv[0] = i & 1 122 | ll.SetValues(vv) 123 | } 124 | } 125 | 126 | func BenchmarkInterruptLatency(b *testing.B) { 127 | s, err := gpiosim.NewSimpleton(6) 128 | require.Nil(b, err) 129 | defer s.Close() 130 | c, err := gpiocdev.NewChip(s.DevPath()) 131 | require.Nil(b, err) 132 | require.NotNil(b, c) 133 | defer c.Close() 134 | offset := 2 135 | s.SetPull(offset, 1) 136 | ich := make(chan int) 137 | eh := func(evt gpiocdev.LineEvent) { 138 | ich <- 1 139 | } 140 | r, err := c.RequestLine(offset, 141 | gpiocdev.WithBothEdges, 142 | gpiocdev.WithEventHandler(eh)) 143 | require.Nil(b, err) 144 | require.NotNil(b, r) 145 | // absorb any pending interrupt 146 | select { 147 | case <-ich: 148 | case <-time.After(time.Millisecond): 149 | } 150 | for i := 0; i < b.N; i++ { 151 | s.SetPull(offset, i&1) 152 | <-ich 153 | } 154 | r.Close() 155 | } 156 | -------------------------------------------------------------------------------- /gpiocdev_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package gpiocdev_test 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "testing" 12 | "time" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/require" 16 | "github.com/warthog618/go-gpiocdev" 17 | "github.com/warthog618/go-gpiocdev/uapi" 18 | "github.com/warthog618/go-gpiosim" 19 | "golang.org/x/sys/unix" 20 | ) 21 | 22 | var kernelAbiVersion int 23 | 24 | func TestMain(m *testing.M) { 25 | flag.IntVar(&kernelAbiVersion, "abi", 0, "kernel uAPI version") 26 | flag.Parse() 27 | rc := m.Run() 28 | os.Exit(rc) 29 | } 30 | 31 | var ( 32 | biasKernel = uapi.Semver{5, 5} // bias flags added 33 | setConfigKernel = uapi.Semver{5, 5} // setLineConfig ioctl added 34 | infoWatchKernel = uapi.Semver{5, 7} // watchLineInfo ioctl added 35 | uapiV2Kernel = uapi.Semver{5, 10} // uapi v2 added 36 | eventClockRealtimeKernel = uapi.Semver{5, 11} // realtime event clock option added 37 | ) 38 | 39 | func TestRequestLine(t *testing.T) { 40 | var opts []gpiocdev.LineReqOption 41 | if kernelAbiVersion != 0 { 42 | opts = append(opts, gpiocdev.ABIVersionOption(kernelAbiVersion)) 43 | } 44 | 45 | s, err := gpiosim.NewSimpleton(6) 46 | require.Nil(t, err) 47 | defer s.Close() 48 | 49 | offset := 3 50 | 51 | // non-existent 52 | l, err := gpiocdev.RequestLine(s.DevPath()+"not", offset, opts...) 53 | assert.NotNil(t, err) 54 | require.Nil(t, l) 55 | 56 | // negative 57 | l, err = gpiocdev.RequestLine(s.DevPath(), -1, opts...) 58 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 59 | require.Nil(t, l) 60 | 61 | // out of range 62 | l, err = gpiocdev.RequestLine(s.DevPath(), s.Config().NumLines+1, opts...) 63 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 64 | require.Nil(t, l) 65 | 66 | // success - input 67 | l, err = gpiocdev.RequestLine(s.DevPath(), offset) 68 | assert.Nil(t, err) 69 | require.NotNil(t, l) 70 | 71 | // already requested input 72 | l2, err := gpiocdev.RequestLine(s.DevPath(), offset) 73 | assert.Equal(t, unix.EBUSY, err) 74 | require.Nil(t, l2) 75 | 76 | // already requested output 77 | l2, err = gpiocdev.RequestLine(s.DevPath(), offset, append(opts, gpiocdev.AsOutput(0))...) 78 | assert.Equal(t, unix.EBUSY, err) 79 | require.Nil(t, l2) 80 | 81 | // already requested output as event 82 | l2, err = gpiocdev.RequestLine(s.DevPath(), offset, append(opts, gpiocdev.WithBothEdges)...) 83 | assert.Equal(t, unix.EBUSY, err) 84 | require.Nil(t, l2) 85 | 86 | err = l.Close() 87 | assert.Nil(t, err) 88 | } 89 | 90 | func TestRequestLines(t *testing.T) { 91 | var opts []gpiocdev.LineReqOption 92 | if kernelAbiVersion != 0 { 93 | opts = append(opts, gpiocdev.ABIVersionOption(kernelAbiVersion)) 94 | } 95 | 96 | s, err := gpiosim.NewSimpleton(6) 97 | require.Nil(t, err) 98 | defer s.Close() 99 | 100 | offsets := []int{1, 4} 101 | // non-existent 102 | ll, err := gpiocdev.RequestLines(s.DevPath()+"not", offsets, opts...) 103 | assert.NotNil(t, err) 104 | require.Nil(t, ll) 105 | 106 | // negative 107 | ll, err = gpiocdev.RequestLines(s.DevPath(), append(offsets, -1), opts...) 108 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 109 | require.Nil(t, ll) 110 | 111 | // out of range 112 | ll, err = gpiocdev.RequestLines(s.DevPath(), append(offsets, s.Config().NumLines)) 113 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 114 | require.Nil(t, ll) 115 | 116 | // success - output 117 | ll, err = gpiocdev.RequestLines(s.DevPath(), offsets, gpiocdev.AsOutput()) 118 | assert.Nil(t, err) 119 | require.NotNil(t, ll) 120 | 121 | // already requested input 122 | ll2, err := gpiocdev.RequestLines(s.DevPath(), offsets) 123 | assert.Equal(t, unix.EBUSY, err) 124 | require.Nil(t, ll2) 125 | 126 | // already requested output 127 | ll2, err = gpiocdev.RequestLines(s.DevPath(), offsets, append(opts, gpiocdev.AsOutput())...) 128 | assert.Equal(t, unix.EBUSY, err) 129 | require.Nil(t, ll2) 130 | 131 | // already requested output as event 132 | ll2, err = gpiocdev.RequestLines(s.DevPath(), offsets, append(opts, gpiocdev.WithBothEdges)...) 133 | assert.Equal(t, unix.EBUSY, err) 134 | require.Nil(t, ll2) 135 | 136 | err = ll.Close() 137 | assert.Nil(t, err) 138 | } 139 | 140 | func TestNewChip(t *testing.T) { 141 | var chipOpts []gpiocdev.ChipOption 142 | if kernelAbiVersion != 0 { 143 | chipOpts = append(chipOpts, gpiocdev.ABIVersionOption(kernelAbiVersion)) 144 | } 145 | s, err := gpiosim.NewSimpleton(6) 146 | require.Nil(t, err) 147 | defer s.Close() 148 | 149 | // non-existent 150 | c, err := gpiocdev.NewChip(s.DevPath()+"not", chipOpts...) 151 | assert.NotNil(t, err) 152 | assert.Nil(t, c) 153 | 154 | // success 155 | c = getChip(t, s.DevPath()) 156 | err = c.Close() 157 | assert.Nil(t, err) 158 | 159 | // name 160 | c, err = gpiocdev.NewChip(s.ChipName(), chipOpts...) 161 | assert.Nil(t, err) 162 | require.NotNil(t, c) 163 | err = c.Close() 164 | assert.Nil(t, err) 165 | 166 | // option 167 | c = getChip(t, s.DevPath(), gpiocdev.WithConsumer("gpiocdev_test")) 168 | assert.Equal(t, s.ChipName(), c.Name) 169 | assert.Equal(t, s.Config().Label, c.Label) 170 | err = c.Close() 171 | assert.Nil(t, err) 172 | } 173 | 174 | func TestChips(t *testing.T) { 175 | s, err := gpiosim.NewSimpleton(6) 176 | require.Nil(t, err) 177 | defer s.Close() 178 | 179 | cc := gpiocdev.Chips() 180 | require.GreaterOrEqual(t, len(cc), 1) 181 | assert.Contains(t, cc, s.ChipName()) 182 | } 183 | 184 | func TestChipClose(t *testing.T) { 185 | s, err := gpiosim.NewSimpleton(6) 186 | require.Nil(t, err) 187 | defer s.Close() 188 | 189 | // without lines 190 | c := getChip(t, s.DevPath()) 191 | err = c.Close() 192 | assert.Nil(t, err) 193 | 194 | // closed 195 | err = c.Close() 196 | assert.Equal(t, gpiocdev.ErrClosed, err) 197 | 198 | // with lines 199 | offsets := []int{2, 5} 200 | c = getChip(t, s.DevPath()) 201 | require.NotNil(t, c) 202 | ll, err := c.RequestLines(offsets, gpiocdev.WithBothEdges) 203 | assert.Nil(t, err) 204 | err = c.Close() 205 | assert.Nil(t, err) 206 | require.NotNil(t, ll) 207 | err = ll.Close() 208 | assert.Nil(t, err) 209 | 210 | // after lines closed 211 | c = getChip(t, s.DevPath()) 212 | require.NotNil(t, c) 213 | ll, err = c.RequestLines(offsets, gpiocdev.WithBothEdges) 214 | assert.Nil(t, err) 215 | require.NotNil(t, ll) 216 | err = ll.Close() 217 | assert.Nil(t, err) 218 | err = c.Close() 219 | assert.Nil(t, err) 220 | } 221 | 222 | func TestChipFindLine(t *testing.T) { 223 | s, err := gpiosim.NewSim( 224 | gpiosim.WithName("gpiocdev_test"), 225 | gpiosim.WithBank(gpiosim.NewBank("left", 8, 226 | gpiosim.WithNamedLine(3, "CFLBUTTON1"), 227 | gpiosim.WithNamedLine(5, "CFLLED6"), 228 | )), 229 | gpiosim.WithBank(gpiosim.NewBank("right", 8, 230 | gpiosim.WithNamedLine(1, "CFLBUTTON1"), 231 | gpiosim.WithNamedLine(4, "CFLLED7"), 232 | )), 233 | ) 234 | require.Nil(t, err) 235 | defer s.Close() 236 | 237 | c, err := gpiocdev.NewChip(s.Chips[0].ChipName()) 238 | require.Nil(t, err) 239 | defer c.Close() 240 | 241 | offset, err := c.FindLine("CFLLED6") 242 | require.Nil(t, err) 243 | require.Equal(t, 5, offset) 244 | 245 | offset, err = c.FindLine("CFLLED7") 246 | require.Equal(t, gpiocdev.ErrNotFound, err) 247 | 248 | offset, err = c.FindLine("CFLLED3") 249 | require.Equal(t, gpiocdev.ErrNotFound, err) 250 | 251 | c.Close() 252 | c, err = gpiocdev.NewChip(s.Chips[1].ChipName()) 253 | require.Nil(t, err) 254 | 255 | offset, err = c.FindLine("CFLLED6") 256 | require.Equal(t, gpiocdev.ErrNotFound, err) 257 | 258 | offset, err = c.FindLine("CFLLED7") 259 | require.Nil(t, err) 260 | require.Equal(t, 4, offset) 261 | 262 | offset, err = c.FindLine("CFLLED3") 263 | require.Equal(t, gpiocdev.ErrNotFound, err) 264 | } 265 | 266 | func TestChipLineInfo(t *testing.T) { 267 | offset := 4 268 | s, err := gpiosim.NewSim( 269 | gpiosim.WithName("gpiocdev_test"), 270 | gpiosim.WithBank(gpiosim.NewBank("left", 8, 271 | gpiosim.WithNamedLine(offset, "BUTTON1"), 272 | )), 273 | ) 274 | require.Nil(t, err) 275 | defer s.Close() 276 | 277 | sc := &s.Chips[0] 278 | c := getChip(t, sc.DevPath()) 279 | xli := gpiocdev.LineInfo{} 280 | // out of range 281 | li, err := c.LineInfo(sc.Config().NumLines) 282 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 283 | assert.Equal(t, xli, li) 284 | 285 | // valid 286 | li, err = c.LineInfo(offset) 287 | assert.Nil(t, err) 288 | xli = gpiocdev.LineInfo{ 289 | Offset: offset, 290 | Name: sc.Config().Names[offset], 291 | Config: gpiocdev.LineConfig{ 292 | Direction: gpiocdev.LineDirectionInput, 293 | }, 294 | } 295 | assert.Equal(t, xli, li) 296 | 297 | // closed 298 | c.Close() 299 | li, err = c.LineInfo(1) 300 | assert.NotNil(t, err) 301 | xli = gpiocdev.LineInfo{} 302 | assert.Equal(t, xli, li) 303 | } 304 | 305 | func TestChipLines(t *testing.T) { 306 | s, err := gpiosim.NewSimpleton(6) 307 | require.Nil(t, err) 308 | defer s.Close() 309 | 310 | c := getChip(t, s.DevPath()) 311 | defer c.Close() 312 | lines := c.Lines() 313 | assert.Equal(t, s.Config().NumLines, lines) 314 | } 315 | 316 | func TestChipRequestLine(t *testing.T) { 317 | s, err := gpiosim.NewSimpleton(6) 318 | require.Nil(t, err) 319 | defer s.Close() 320 | 321 | c := getChip(t, s.DevPath()) 322 | defer c.Close() 323 | 324 | offset := 3 325 | 326 | // negative 327 | l, err := c.RequestLine(-1) 328 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 329 | require.Nil(t, l) 330 | 331 | // out of range 332 | l, err = c.RequestLine(c.Lines()) 333 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 334 | require.Nil(t, l) 335 | 336 | // success - input 337 | l, err = c.RequestLine(offset) 338 | assert.Nil(t, err) 339 | require.NotNil(t, l) 340 | 341 | // already requested input 342 | l2, err := c.RequestLine(offset) 343 | assert.Equal(t, unix.EBUSY, err) 344 | require.Nil(t, l2) 345 | 346 | // already requested output 347 | l2, err = c.RequestLine(offset, gpiocdev.AsOutput(0)) 348 | assert.Equal(t, unix.EBUSY, err) 349 | require.Nil(t, l2) 350 | 351 | // already requested output as event 352 | l2, err = c.RequestLine(offset, gpiocdev.WithBothEdges) 353 | assert.Equal(t, unix.EBUSY, err) 354 | require.Nil(t, l2) 355 | 356 | err = l.Close() 357 | assert.Nil(t, err) 358 | } 359 | 360 | func TestChipRequestLines(t *testing.T) { 361 | offsets := []int{4, 2} 362 | s, err := gpiosim.NewSimpleton(6) 363 | require.Nil(t, err) 364 | defer s.Close() 365 | 366 | c := getChip(t, s.DevPath()) 367 | defer c.Close() 368 | 369 | // negative 370 | ll, err := c.RequestLines(append(offsets, -1)) 371 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 372 | require.Nil(t, ll) 373 | 374 | // out of range 375 | ll, err = c.RequestLines(append(offsets, c.Lines())) 376 | assert.Equal(t, gpiocdev.ErrInvalidOffset, err) 377 | require.Nil(t, ll) 378 | 379 | // success - output 380 | ll, err = c.RequestLines(offsets, gpiocdev.AsOutput()) 381 | assert.Nil(t, err) 382 | require.NotNil(t, ll) 383 | 384 | // already requested input 385 | ll2, err := c.RequestLines(offsets) 386 | assert.Equal(t, unix.EBUSY, err) 387 | require.Nil(t, ll2) 388 | 389 | // already requested output 390 | ll2, err = c.RequestLines(offsets, gpiocdev.AsOutput()) 391 | assert.Equal(t, unix.EBUSY, err) 392 | require.Nil(t, ll2) 393 | 394 | // already requested output as event 395 | ll2, err = c.RequestLines(offsets, gpiocdev.WithBothEdges) 396 | assert.Equal(t, unix.EBUSY, err) 397 | require.Nil(t, ll2) 398 | 399 | err = ll.Close() 400 | assert.Nil(t, err) 401 | } 402 | 403 | func TestChipWatchLineInfo(t *testing.T) { 404 | requireKernel(t, infoWatchKernel) 405 | 406 | offset := 4 407 | s, err := gpiosim.NewSimpleton(6) 408 | require.Nil(t, err) 409 | defer s.Close() 410 | c := getChip(t, s.DevPath()) 411 | 412 | wc1 := make(chan gpiocdev.LineInfoChangeEvent, 5) 413 | watcher1 := func(info gpiocdev.LineInfoChangeEvent) { 414 | wc1 <- info 415 | } 416 | 417 | // closed 418 | c.Close() 419 | _, err = c.WatchLineInfo(offset, watcher1) 420 | require.Equal(t, gpiocdev.ErrClosed, err) 421 | 422 | c = getChip(t, s.DevPath()) 423 | defer c.Close() 424 | 425 | // unwatched 426 | _, err = c.WatchLineInfo(offset, watcher1) 427 | require.Nil(t, err) 428 | 429 | l, err := c.RequestLine(offset, gpiocdev.AsInput) 430 | assert.Nil(t, err) 431 | require.NotNil(t, l) 432 | waitInfoEvent(t, wc1, gpiocdev.LineRequested) 433 | l.Reconfigure(gpiocdev.AsActiveLow) 434 | waitInfoEvent(t, wc1, gpiocdev.LineReconfigured) 435 | l.Close() 436 | waitInfoEvent(t, wc1, gpiocdev.LineReleased) 437 | 438 | wc2 := make(chan gpiocdev.LineInfoChangeEvent, 2) 439 | 440 | // watched 441 | watcher2 := func(info gpiocdev.LineInfoChangeEvent) { 442 | wc2 <- info 443 | } 444 | _, err = c.WatchLineInfo(offset, watcher2) 445 | assert.Equal(t, unix.EBUSY, err) 446 | 447 | l, err = c.RequestLine(offset) 448 | assert.Nil(t, err) 449 | require.NotNil(t, l) 450 | waitInfoEvent(t, wc1, gpiocdev.LineRequested) 451 | waitNoInfoEvent(t, wc2) 452 | l.Close() 453 | waitInfoEvent(t, wc1, gpiocdev.LineReleased) 454 | waitNoInfoEvent(t, wc2) 455 | } 456 | 457 | func TestChipUnwatchLineInfo(t *testing.T) { 458 | requireKernel(t, infoWatchKernel) 459 | 460 | offset := 3 461 | s, err := gpiosim.NewSimpleton(6) 462 | require.Nil(t, err) 463 | defer s.Close() 464 | 465 | c := getChip(t, s.DevPath()) 466 | c.Close() 467 | 468 | // closed 469 | err = c.UnwatchLineInfo(offset) 470 | assert.Nil(t, err) 471 | 472 | c = getChip(t, s.DevPath()) 473 | defer c.Close() 474 | 475 | // Unwatched 476 | err = c.UnwatchLineInfo(offset) 477 | assert.Equal(t, unix.EBUSY, err) 478 | 479 | // Watched 480 | wc := 0 481 | watcher := func(info gpiocdev.LineInfoChangeEvent) { 482 | wc++ 483 | } 484 | _, err = c.WatchLineInfo(offset, watcher) 485 | require.Nil(t, err) 486 | err = c.UnwatchLineInfo(offset) 487 | assert.Nil(t, err) 488 | 489 | l, err := c.RequestLine(offset) 490 | assert.Nil(t, err) 491 | require.NotNil(t, l) 492 | assert.Zero(t, wc) 493 | l.Close() 494 | assert.Zero(t, wc) 495 | } 496 | 497 | func TestLineChip(t *testing.T) { 498 | offset := 3 499 | s, err := gpiosim.NewSimpleton(6) 500 | require.Nil(t, err) 501 | defer s.Close() 502 | c := getChip(t, s.DevPath()) 503 | defer c.Close() 504 | l, err := c.RequestLine(offset) 505 | assert.Nil(t, err) 506 | require.NotNil(t, l) 507 | defer l.Close() 508 | cname := l.Chip() 509 | assert.Equal(t, c.Name, cname) 510 | } 511 | 512 | func TestLineClose(t *testing.T) { 513 | offset := 3 514 | s, err := gpiosim.NewSimpleton(6) 515 | require.Nil(t, err) 516 | defer s.Close() 517 | c := getChip(t, s.DevPath()) 518 | defer c.Close() 519 | l, err := c.RequestLine(offset) 520 | assert.Nil(t, err) 521 | require.NotNil(t, l) 522 | err = l.Close() 523 | assert.Nil(t, err) 524 | 525 | err = l.Close() 526 | assert.Equal(t, gpiocdev.ErrClosed, err) 527 | } 528 | 529 | func TestLineInfo(t *testing.T) { 530 | offset := 3 531 | s, err := gpiosim.NewSimpleton(6) 532 | require.Nil(t, err) 533 | defer s.Close() 534 | c := getChip(t, s.DevPath()) 535 | defer c.Close() 536 | l, err := c.RequestLine(offset, gpiocdev.WithBothEdges) 537 | assert.Nil(t, err) 538 | require.NotNil(t, l) 539 | cli, err := c.LineInfo(offset) 540 | assert.Nil(t, err) 541 | 542 | li, err := l.Info() 543 | assert.Nil(t, err) 544 | require.NotNil(t, li) 545 | assert.Equal(t, cli, li) 546 | 547 | // cached 548 | li, err = l.Info() 549 | assert.Nil(t, err) 550 | require.NotNil(t, li) 551 | assert.Equal(t, cli, li) 552 | 553 | // closed 554 | l.Close() 555 | _, err = l.Info() 556 | assert.Equal(t, gpiocdev.ErrClosed, err) 557 | } 558 | 559 | func TestLineOffset(t *testing.T) { 560 | offset := 3 561 | s, err := gpiosim.NewSimpleton(6) 562 | require.Nil(t, err) 563 | defer s.Close() 564 | c := getChip(t, s.DevPath()) 565 | defer c.Close() 566 | l, err := c.RequestLine(offset) 567 | assert.Nil(t, err) 568 | require.NotNil(t, l) 569 | defer l.Close() 570 | lo := l.Offset() 571 | assert.Equal(t, offset, lo) 572 | } 573 | 574 | func TestLineReconfigure(t *testing.T) { 575 | requireKernel(t, setConfigKernel) 576 | 577 | offset := 3 578 | s, err := gpiosim.NewSimpleton(6) 579 | require.Nil(t, err) 580 | defer s.Close() 581 | c := getChip(t, s.DevPath(), gpiocdev.WithConsumer("TestLineReconfigure")) 582 | defer c.Close() 583 | 584 | xinf := gpiocdev.LineInfo{ 585 | Used: true, 586 | Consumer: "TestLineReconfigure", 587 | Offset: offset, 588 | Config: gpiocdev.LineConfig{ 589 | Direction: gpiocdev.LineDirectionInput, 590 | }, 591 | } 592 | l, err := c.RequestLine(offset, gpiocdev.AsInput) 593 | assert.Nil(t, err) 594 | require.NotNil(t, l) 595 | 596 | inf, err := c.LineInfo(offset) 597 | assert.Nil(t, err) 598 | xinf.Name = inf.Name // don't care about line name 599 | assert.Equal(t, xinf, inf) 600 | 601 | // no options 602 | err = l.Reconfigure() 603 | assert.Nil(t, err) 604 | inf, err = c.LineInfo(offset) 605 | assert.Nil(t, err) 606 | assert.Equal(t, xinf, inf) 607 | 608 | // an option 609 | err = l.Reconfigure(gpiocdev.AsActiveLow) 610 | assert.Nil(t, err) 611 | inf, err = c.LineInfo(offset) 612 | assert.Nil(t, err) 613 | xinf.Config.ActiveLow = true 614 | assert.Equal(t, xinf, inf) 615 | 616 | // closed 617 | l.Close() 618 | err = l.Reconfigure(gpiocdev.AsActiveLow) 619 | assert.Equal(t, gpiocdev.ErrClosed, err) 620 | 621 | // event request 622 | l, err = c.RequestLine(offset, 623 | gpiocdev.WithBothEdges, 624 | gpiocdev.WithEventHandler(func(gpiocdev.LineEvent) {})) 625 | assert.Nil(t, err) 626 | require.NotNil(t, l) 627 | 628 | inf, err = c.LineInfo(offset) 629 | assert.Nil(t, err) 630 | xinf.Config.ActiveLow = false 631 | if l.UapiAbiVersion() != 1 { 632 | // uAPI v1 does not return edge detection status in info 633 | xinf.Config.EdgeDetection = gpiocdev.LineEdgeBoth 634 | } 635 | assert.Equal(t, xinf, inf) 636 | 637 | err = l.Reconfigure(gpiocdev.AsActiveLow) 638 | switch l.UapiAbiVersion() { 639 | case 1: 640 | assert.Equal(t, unix.EINVAL, err) 641 | case 2: 642 | assert.Nil(t, err) 643 | xinf.Config.ActiveLow = true 644 | } 645 | inf, err = c.LineInfo(offset) 646 | assert.Nil(t, err) 647 | assert.Equal(t, xinf, inf) 648 | l.Close() 649 | } 650 | 651 | func TestLinesReconfigure(t *testing.T) { 652 | requireKernel(t, setConfigKernel) 653 | 654 | offsets := []int{1, 3, 0, 2} 655 | s, err := gpiosim.NewSimpleton(6) 656 | require.Nil(t, err) 657 | defer s.Close() 658 | c := getChip(t, s.DevPath(), gpiocdev.WithConsumer("TestLinesReconfigure")) 659 | defer c.Close() 660 | 661 | offset := offsets[1] 662 | ll, err := c.RequestLines(offsets, gpiocdev.AsInput) 663 | assert.Nil(t, err) 664 | require.NotNil(t, ll) 665 | 666 | xinf := gpiocdev.LineInfo{ 667 | Used: true, 668 | Consumer: "TestLinesReconfigure", 669 | Offset: offset, 670 | Config: gpiocdev.LineConfig{ 671 | Direction: gpiocdev.LineDirectionInput, 672 | }, 673 | } 674 | inf, err := c.LineInfo(offset) 675 | assert.Nil(t, err) 676 | assert.Equal(t, xinf, inf) 677 | 678 | // no options 679 | err = ll.Reconfigure() 680 | assert.Nil(t, err) 681 | inf, err = c.LineInfo(offset) 682 | assert.Nil(t, err) 683 | assert.Equal(t, xinf, inf) 684 | 685 | // one option 686 | err = ll.Reconfigure(gpiocdev.AsActiveLow) 687 | assert.Nil(t, err) 688 | inf, err = c.LineInfo(offset) 689 | assert.Nil(t, err) 690 | xinf.Config.ActiveLow = true 691 | assert.Equal(t, xinf, inf) 692 | 693 | if ll.UapiAbiVersion() != 1 { 694 | inner := []int{offsets[3], offsets[0]} 695 | 696 | // WithLines 697 | err = ll.Reconfigure( 698 | gpiocdev.WithLines(inner, gpiocdev.WithPullUp), 699 | gpiocdev.AsActiveHigh, 700 | ) 701 | assert.Nil(t, err) 702 | 703 | inf, err = c.LineInfo(offset) 704 | assert.Nil(t, err) 705 | xinf.Config.ActiveLow = false 706 | assert.Equal(t, xinf, inf) 707 | 708 | xinfi := gpiocdev.LineInfo{ 709 | Used: true, 710 | Consumer: "TestLinesReconfigure", 711 | Offset: inner[0], 712 | Config: gpiocdev.LineConfig{ 713 | ActiveLow: true, 714 | Bias: gpiocdev.LineBiasPullUp, 715 | Direction: gpiocdev.LineDirectionInput, 716 | }, 717 | } 718 | inf, err = c.LineInfo(inner[0]) 719 | assert.Nil(t, err) 720 | assert.Equal(t, xinfi, inf) 721 | 722 | inf, err = c.LineInfo(inner[1]) 723 | assert.Nil(t, err) 724 | xinfi.Offset = inner[1] 725 | assert.Equal(t, xinfi, inf) 726 | 727 | // single WithLines -> 3 distinct configs 728 | err = ll.Reconfigure( 729 | gpiocdev.WithLines(inner[:1], gpiocdev.WithPullDown), 730 | ) 731 | assert.Nil(t, err) 732 | 733 | inf, err = c.LineInfo(offset) 734 | assert.Nil(t, err) 735 | xinf.Config.ActiveLow = false 736 | assert.Equal(t, xinf, inf) 737 | 738 | inf, err = c.LineInfo(inner[1]) 739 | assert.Nil(t, err) 740 | xinfi.Offset = inner[1] 741 | assert.Equal(t, xinfi, inf) 742 | 743 | inf, err = c.LineInfo(inner[0]) 744 | assert.Nil(t, err) 745 | xinfi.Offset = inner[0] 746 | xinfi.Config.Bias = gpiocdev.LineBiasPullDown 747 | assert.Equal(t, xinfi, inf) 748 | } 749 | 750 | // closed 751 | ll.Close() 752 | err = ll.Reconfigure(gpiocdev.AsActiveLow) 753 | assert.Equal(t, gpiocdev.ErrClosed, err) 754 | 755 | // event request 756 | ll, err = c.RequestLines(offsets, 757 | gpiocdev.WithBothEdges, 758 | gpiocdev.WithEventHandler(func(gpiocdev.LineEvent) {})) 759 | assert.Nil(t, err) 760 | require.NotNil(t, ll) 761 | 762 | inf, err = c.LineInfo(offset) 763 | assert.Nil(t, err) 764 | xinf.Config.ActiveLow = false 765 | if ll.UapiAbiVersion() != 1 { 766 | // uAPI v1 does not return edge detection status in info 767 | xinf.Config.EdgeDetection = gpiocdev.LineEdgeBoth 768 | } 769 | assert.Equal(t, xinf, inf) 770 | 771 | err = ll.Reconfigure(gpiocdev.AsActiveLow) 772 | switch ll.UapiAbiVersion() { 773 | case 1: 774 | assert.Equal(t, unix.EINVAL, err) 775 | case 2: 776 | assert.Nil(t, err) 777 | xinf.Config.ActiveLow = true 778 | } 779 | inf, err = c.LineInfo(offset) 780 | assert.Nil(t, err) 781 | assert.Equal(t, xinf, inf) 782 | ll.Close() 783 | } 784 | 785 | func TestLineValue(t *testing.T) { 786 | offset := 3 787 | s, err := gpiosim.NewSimpleton(6) 788 | require.Nil(t, err) 789 | defer s.Close() 790 | c := getChip(t, s.DevPath()) 791 | defer c.Close() 792 | 793 | s.SetPull(offset, 0) 794 | l, err := c.RequestLine(offset) 795 | assert.Nil(t, err) 796 | require.NotNil(t, l) 797 | v, err := l.Value() 798 | assert.Nil(t, err) 799 | assert.Equal(t, 0, v) 800 | s.SetPull(offset, 1) 801 | v, err = l.Value() 802 | assert.Nil(t, err) 803 | assert.Equal(t, 1, v) 804 | l.Close() 805 | _, err = l.Value() 806 | assert.Equal(t, gpiocdev.ErrClosed, err) 807 | } 808 | 809 | func TestLineSetValue(t *testing.T) { 810 | offset := 0 811 | s, err := gpiosim.NewSimpleton(6) 812 | require.Nil(t, err) 813 | defer s.Close() 814 | c := getChip(t, s.DevPath()) 815 | defer c.Close() 816 | 817 | // input 818 | l, err := c.RequestLine(offset) 819 | assert.Nil(t, err) 820 | require.NotNil(t, l) 821 | err = l.SetValue(1) 822 | assert.Equal(t, gpiocdev.ErrPermissionDenied, err) 823 | l.Close() 824 | 825 | // output 826 | l, err = c.RequestLine(offset, gpiocdev.AsOutput(0)) 827 | assert.Nil(t, err) 828 | require.NotNil(t, l) 829 | err = l.SetValue(1) 830 | assert.Nil(t, err) 831 | l.Close() 832 | err = l.SetValue(1) 833 | assert.Equal(t, gpiocdev.ErrClosed, err) 834 | } 835 | 836 | func TestLinesChip(t *testing.T) { 837 | offsets := []int{5, 4, 3} 838 | s, err := gpiosim.NewSimpleton(6) 839 | require.Nil(t, err) 840 | defer s.Close() 841 | c := getChip(t, s.DevPath()) 842 | defer c.Close() 843 | l, err := c.RequestLines(offsets) 844 | assert.Nil(t, err) 845 | require.NotNil(t, l) 846 | defer l.Close() 847 | lc := l.Chip() 848 | assert.Equal(t, c.Name, lc) 849 | } 850 | 851 | func TestLinesClose(t *testing.T) { 852 | offsets := []int{5, 0, 3} 853 | s, err := gpiosim.NewSimpleton(6) 854 | require.Nil(t, err) 855 | defer s.Close() 856 | c := getChip(t, s.DevPath()) 857 | defer c.Close() 858 | l, err := c.RequestLines(offsets) 859 | assert.Nil(t, err) 860 | require.NotNil(t, l) 861 | err = l.Close() 862 | assert.Nil(t, err) 863 | 864 | err = l.Close() 865 | assert.Equal(t, gpiocdev.ErrClosed, err) 866 | } 867 | 868 | func TestLinesInfo(t *testing.T) { 869 | offsets := []int{5, 1, 3} 870 | s, err := gpiosim.NewSimpleton(6) 871 | require.Nil(t, err) 872 | defer s.Close() 873 | c := getChip(t, s.DevPath()) 874 | defer c.Close() 875 | l, err := c.RequestLines(offsets) 876 | assert.Nil(t, err) 877 | require.NotNil(t, l) 878 | 879 | // initial 880 | li, err := l.Info() 881 | assert.Nil(t, err) 882 | for i, o := range offsets { 883 | cli, err := c.LineInfo(o) 884 | assert.Nil(t, err) 885 | assert.NotNil(t, li[i]) 886 | if li[0] != nil { 887 | assert.Equal(t, cli, *li[i]) 888 | } 889 | } 890 | 891 | // cached 892 | li, err = l.Info() 893 | assert.Nil(t, err) 894 | for i, o := range offsets { 895 | cli, err := c.LineInfo(o) 896 | assert.Nil(t, err) 897 | assert.NotNil(t, li[i]) 898 | if li[0] != nil { 899 | assert.Equal(t, cli, *li[i]) 900 | } 901 | } 902 | 903 | // closed 904 | l.Close() 905 | li, err = l.Info() 906 | assert.Equal(t, gpiocdev.ErrClosed, err) 907 | assert.Nil(t, li) 908 | } 909 | 910 | func TestLineOffsets(t *testing.T) { 911 | offsets := []int{1, 4, 3} 912 | s, err := gpiosim.NewSimpleton(6) 913 | require.Nil(t, err) 914 | defer s.Close() 915 | c := getChip(t, s.DevPath()) 916 | defer c.Close() 917 | l, err := c.RequestLines(offsets) 918 | assert.Nil(t, err) 919 | require.NotNil(t, l) 920 | defer l.Close() 921 | lo := l.Offsets() 922 | assert.Equal(t, offsets, lo) 923 | } 924 | 925 | func TestLinesValues(t *testing.T) { 926 | offsets := []int{1, 2, 3, 4, 5} 927 | s, err := gpiosim.NewSimpleton(6) 928 | require.Nil(t, err) 929 | defer s.Close() 930 | c := getChip(t, s.DevPath()) 931 | defer c.Close() 932 | 933 | offset := offsets[1] 934 | // input 935 | s.SetPull(offset, 0) 936 | l, err := c.RequestLines(offsets) 937 | assert.Nil(t, err) 938 | require.NotNil(t, l) 939 | vv := make([]int, len(offsets)) 940 | err = l.Values(vv) 941 | assert.Nil(t, err) 942 | assert.Equal(t, []int{0, 0, 0, 0, 0}, vv) 943 | s.SetPull(offset, 1) 944 | err = l.Values(vv) 945 | assert.Nil(t, err) 946 | assert.Equal(t, []int{0, 1, 0, 0, 0}, vv) 947 | 948 | // subset 949 | vv = make([]int, len(offsets)-2) 950 | err = l.Values(vv) 951 | assert.Nil(t, err) 952 | assert.Equal(t, []int{0, 1, 0}, vv) 953 | 954 | l.Close() 955 | 956 | // after close 957 | err = l.Values(vv) 958 | assert.NotNil(t, err) 959 | 960 | // output 961 | l, err = c.RequestLines(offsets, gpiocdev.AsOutput(0)) 962 | assert.Nil(t, err) 963 | require.NotNil(t, l) 964 | vv = make([]int, len(offsets)) 965 | err = l.Values(vv) 966 | assert.Nil(t, err) 967 | assert.Equal(t, []int{0, 0, 0, 0, 0}, vv) 968 | 969 | l.Close() 970 | } 971 | 972 | func checkLevels(t *testing.T, s *gpiosim.Simpleton, offsets, values []int) { 973 | for i, o := range offsets { 974 | v, err := s.Level(o) 975 | assert.Nil(t, err) 976 | assert.Equal(t, values[i], v, i) 977 | } 978 | } 979 | 980 | func TestLinesSetValues(t *testing.T) { 981 | offsets := []int{2, 3, 1} 982 | s, err := gpiosim.NewSimpleton(6) 983 | require.Nil(t, err) 984 | defer s.Close() 985 | c := getChip(t, s.DevPath()) 986 | defer c.Close() 987 | 988 | // input 989 | l, err := c.RequestLines(offsets) 990 | assert.Nil(t, err) 991 | require.NotNil(t, l) 992 | err = l.SetValues([]int{0, 1}) 993 | assert.Equal(t, gpiocdev.ErrPermissionDenied, err) 994 | l.Close() 995 | 996 | // output 997 | l, err = c.RequestLines(offsets, gpiocdev.AsOutput(0)) 998 | assert.Nil(t, err) 999 | require.NotNil(t, l) 1000 | err = l.SetValues([]int{1, 0, 1}) 1001 | assert.Nil(t, err) 1002 | checkLevels(t, s, offsets, []int{1, 0, 1}) 1003 | 1004 | // subset 1005 | err = l.SetValues([]int{0, 1}) 1006 | assert.Nil(t, err) 1007 | checkLevels(t, s, offsets, []int{0, 1, 0}) 1008 | 1009 | // too many values 1010 | err = l.SetValues([]int{1, 0, 1, 0}) 1011 | assert.Nil(t, err) 1012 | checkLevels(t, s, offsets, []int{1, 0, 1}) 1013 | 1014 | // closed 1015 | l.Close() 1016 | err = l.SetValues([]int{0, 1}) 1017 | assert.Equal(t, gpiocdev.ErrClosed, err) 1018 | } 1019 | 1020 | func naturalLess(lhs, rhs string) bool { 1021 | llhs := len(lhs) 1022 | lrhs := len(rhs) 1023 | if llhs == lrhs { 1024 | return lhs < rhs 1025 | } 1026 | if llhs < lrhs { 1027 | return true 1028 | } 1029 | return false 1030 | } 1031 | 1032 | func TestFindLine(t *testing.T) { 1033 | s, err := gpiosim.NewSim( 1034 | gpiosim.WithName("gpiocdev_test"), 1035 | gpiosim.WithBank(gpiosim.NewBank("left", 8, 1036 | gpiosim.WithNamedLine(3, "FLBUTTON1"), 1037 | gpiosim.WithNamedLine(5, "FLLED6"), 1038 | )), 1039 | gpiosim.WithBank(gpiosim.NewBank("right", 8, 1040 | gpiosim.WithNamedLine(1, "FLBUTTON1"), 1041 | gpiosim.WithNamedLine(4, "FLLED7"), 1042 | )), 1043 | ) 1044 | require.Nil(t, err) 1045 | defer s.Close() 1046 | 1047 | chip, offset, err := gpiocdev.FindLine("FLLED6") 1048 | require.Nil(t, err) 1049 | require.Equal(t, s.Chips[0].ChipName(), chip) 1050 | require.Equal(t, 5, offset) 1051 | 1052 | chip, offset, err = gpiocdev.FindLine("FLLED7") 1053 | require.Nil(t, err) 1054 | require.Equal(t, s.Chips[1].ChipName(), chip) 1055 | require.Equal(t, 4, offset) 1056 | 1057 | chip, offset, err = gpiocdev.FindLine("FLLED3") 1058 | require.Equal(t, gpiocdev.ErrNotFound, err) 1059 | 1060 | chip, offset, err = gpiocdev.FindLine("FLBUTTON1") 1061 | require.Nil(t, err) 1062 | // behaviour depends on chip number assigned by gpio-sim 1063 | if naturalLess(s.Chips[0].ChipName(), s.Chips[1].ChipName()) { 1064 | require.Equal(t, s.Chips[0].ChipName(), chip) 1065 | require.Equal(t, 3, offset) 1066 | } else { 1067 | require.Equal(t, s.Chips[1].ChipName(), chip) 1068 | require.Equal(t, 1, offset) 1069 | } 1070 | } 1071 | 1072 | func TestIsChip(t *testing.T) { 1073 | // non-existent 1074 | err := gpiocdev.IsChip("/dev/nonexistent") 1075 | assert.NotNil(t, err) 1076 | 1077 | // wrong mode 1078 | err = gpiocdev.IsChip("/dev/zero") 1079 | assert.Equal(t, gpiocdev.ErrNotCharacterDevice, err) 1080 | 1081 | // no sysfs 1082 | err = gpiocdev.IsChip("/dev/null") 1083 | assert.Equal(t, gpiocdev.ErrNotCharacterDevice, err) 1084 | 1085 | // not sure how to test the remaining conditions... 1086 | } 1087 | 1088 | func waitInfoEvent(t *testing.T, ch <-chan gpiocdev.LineInfoChangeEvent, etype gpiocdev.LineInfoChangeType) { 1089 | t.Helper() 1090 | select { 1091 | case evt := <-ch: 1092 | assert.Equal(t, etype, evt.Type) 1093 | case <-time.After(time.Second): 1094 | assert.Fail(t, "timeout waiting for event") 1095 | } 1096 | } 1097 | 1098 | func waitNoInfoEvent(t *testing.T, ch <-chan gpiocdev.LineInfoChangeEvent) { 1099 | t.Helper() 1100 | select { 1101 | case evt := <-ch: 1102 | assert.Fail(t, "received unexpected event", evt) 1103 | case <-time.After(20 * time.Millisecond): 1104 | } 1105 | } 1106 | 1107 | func getChip(t *testing.T, chipPath string, chipOpts ...gpiocdev.ChipOption) *gpiocdev.Chip { 1108 | if kernelAbiVersion != 0 { 1109 | chipOpts = append(chipOpts, gpiocdev.ABIVersionOption(kernelAbiVersion)) 1110 | } 1111 | c, err := gpiocdev.NewChip(chipPath, chipOpts...) 1112 | require.Nil(t, err) 1113 | require.NotNil(t, c) 1114 | return c 1115 | } 1116 | 1117 | func requireKernel(t *testing.T, min uapi.Semver) { 1118 | t.Helper() 1119 | if err := uapi.CheckKernelVersion(min); err != nil { 1120 | t.Skip(err) 1121 | } 1122 | } 1123 | 1124 | func requireABI(t *testing.T, chip *gpiocdev.Chip, abi int) { 1125 | t.Helper() 1126 | if chip.UapiAbiVersion() != abi { 1127 | t.Skip(ErrorBadABIVersion{abi, chip.UapiAbiVersion()}) 1128 | } 1129 | } 1130 | 1131 | // ErrorBadVersion indicates the kernel version is insufficient. 1132 | type ErrorBadABIVersion struct { 1133 | Need int 1134 | Have int 1135 | } 1136 | 1137 | func (e ErrorBadABIVersion) Error() string { 1138 | return fmt.Sprintf("require kernel ABI %d, but using %d", e.Need, e.Have) 1139 | } 1140 | -------------------------------------------------------------------------------- /infowatcher.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package gpiocdev 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | 11 | "github.com/warthog618/go-gpiocdev/uapi" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | type infoWatcher struct { 16 | epfd int 17 | 18 | // eventfd to signal watcher to shutdown 19 | donefd int 20 | 21 | // the handler for detected events 22 | ch InfoChangeHandler 23 | 24 | // closed once watcher exits 25 | doneCh chan struct{} 26 | 27 | abi int 28 | } 29 | 30 | func newInfoWatcher(fd int, ch InfoChangeHandler, abi int) (iw *infoWatcher, err error) { 31 | var epfd, donefd int 32 | epfd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC) 33 | if err != nil { 34 | return 35 | } 36 | defer func() { 37 | if err != nil { 38 | unix.Close(epfd) 39 | } 40 | }() 41 | donefd, err = unix.Eventfd(0, unix.EFD_CLOEXEC) 42 | if err != nil { 43 | return 44 | } 45 | defer func() { 46 | if err != nil { 47 | unix.Close(donefd) 48 | } 49 | }() 50 | epv := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(donefd)} 51 | err = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(donefd), &epv) 52 | if err != nil { 53 | return 54 | } 55 | epv.Fd = int32(fd) 56 | err = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, fd, &epv) 57 | if err != nil { 58 | return 59 | } 60 | iw = &infoWatcher{ 61 | epfd: epfd, 62 | donefd: donefd, 63 | ch: ch, 64 | doneCh: make(chan struct{}), 65 | abi: abi, 66 | } 67 | go iw.watch() 68 | return 69 | } 70 | 71 | func (iw *infoWatcher) close() { 72 | unix.Write(iw.donefd, []byte{1, 0, 0, 0, 0, 0, 0, 0}) 73 | <-iw.doneCh 74 | unix.Close(iw.donefd) 75 | } 76 | 77 | func (iw *infoWatcher) watch() { 78 | epollEvents := make([]unix.EpollEvent, 2) 79 | defer close(iw.doneCh) 80 | for { 81 | n, err := unix.EpollWait(iw.epfd, epollEvents[:], -1) 82 | if err != nil { 83 | if err == unix.EBADF || err == unix.EINVAL { 84 | // fd closed so exit 85 | return 86 | } 87 | if err == unix.EINTR { 88 | continue 89 | } 90 | panic(fmt.Sprintf("EpollWait unexpected error: %v", err)) 91 | } 92 | for i := 0; i < n; i++ { 93 | ev := epollEvents[i] 94 | fd := ev.Fd 95 | if fd == int32(iw.donefd) { 96 | unix.Close(iw.epfd) 97 | return 98 | } 99 | if iw.abi == 1 { 100 | iw.readInfoChanged(fd) 101 | } else { 102 | iw.readInfoChangedV2(fd) 103 | } 104 | } 105 | } 106 | } 107 | 108 | func (iw *infoWatcher) readInfoChanged(fd int32) { 109 | lic, err := uapi.ReadLineInfoChanged(uintptr(fd)) 110 | if err != nil { 111 | fmt.Printf("error reading line change:%s\n", err) 112 | return 113 | } 114 | lice := LineInfoChangeEvent{ 115 | Info: newLineInfo(lic.Info), 116 | Timestamp: time.Duration(lic.Timestamp), 117 | Type: LineInfoChangeType(lic.Type), 118 | } 119 | iw.ch(lice) 120 | 121 | } 122 | 123 | func (iw *infoWatcher) readInfoChangedV2(fd int32) { 124 | lic, err := uapi.ReadLineInfoChangedV2(uintptr(fd)) 125 | if err != nil { 126 | fmt.Printf("error reading line change:%s\n", err) 127 | return 128 | } 129 | lice := LineInfoChangeEvent{ 130 | Info: newLineInfoV2(lic.Info), 131 | Timestamp: time.Duration(lic.Timestamp), 132 | Type: LineInfoChangeType(lic.Type), 133 | } 134 | iw.ch(lice) 135 | } 136 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package gpiocdev 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/warthog618/go-gpiocdev/uapi" 11 | ) 12 | 13 | // ChipOption defines the interface required to provide a Chip option. 14 | type ChipOption interface { 15 | applyChipOption(*ChipOptions) 16 | } 17 | 18 | // ChipOptions contains the options for a Chip. 19 | type ChipOptions struct { 20 | consumer string 21 | config LineConfig 22 | abi int 23 | eh EventHandler 24 | } 25 | 26 | // ConsumerOption defines the consumer label for a line. 27 | type ConsumerOption string 28 | 29 | // WithConsumer provides the consumer label for the line. 30 | // 31 | // When applied to a chip it provides the default consumer label for all lines 32 | // requested by the chip. 33 | func WithConsumer(consumer string) ConsumerOption { 34 | return ConsumerOption(consumer) 35 | } 36 | 37 | func (o ConsumerOption) applyChipOption(c *ChipOptions) { 38 | c.consumer = string(o) 39 | } 40 | 41 | func (o ConsumerOption) applyLineReqOption(l *lineReqOptions) { 42 | l.consumer = string(o) 43 | } 44 | 45 | // LineReqOption defines the interface required to provide an option for Line and 46 | // Lines as part of a line request. 47 | type LineReqOption interface { 48 | applyLineReqOption(*lineReqOptions) 49 | } 50 | 51 | // LineConfigOption defines the interface required to update an option for 52 | // Line and Lines. 53 | type LineConfigOption interface { 54 | applyLineConfigOption(*lineConfigOptions) 55 | } 56 | 57 | // SubsetLineConfigOption defines the interface required to update an option for a 58 | // subset of requested lines. 59 | type SubsetLineConfigOption interface { 60 | applySubsetLineConfigOption([]int, *lineConfigOptions) 61 | } 62 | 63 | // lineReqOptions contains the options for a Line(s) request. 64 | type lineReqOptions struct { 65 | lineConfigOptions 66 | consumer string 67 | abi int 68 | eh EventHandler 69 | eventBufferSize int 70 | } 71 | 72 | // lineConfigOptions contains the configuration options for a Line(s) reconfigure. 73 | type lineConfigOptions struct { 74 | offsets []int 75 | values map[int]int 76 | defCfg LineConfig 77 | lineCfg map[int]*LineConfig 78 | } 79 | 80 | func (lco *lineConfigOptions) lineConfig(offset int) *LineConfig { 81 | if lco.lineCfg == nil { 82 | lco.lineCfg = map[int]*LineConfig{} 83 | } 84 | lc := lco.lineCfg[offset] 85 | if lc == nil { 86 | tlc := lco.defCfg 87 | lc = &tlc 88 | lco.lineCfg[offset] = lc 89 | } 90 | return lc 91 | } 92 | 93 | func (lco lineConfigOptions) outputValues() uapi.OutputValues { 94 | ov := uapi.LineBitmap(0) 95 | for idx, val := range lco.offsets { 96 | ov = ov.Set(idx, lco.values[val]) 97 | } 98 | return uapi.OutputValues(ov) 99 | } 100 | 101 | type lineConfigAttributes []uapi.LineConfigAttribute 102 | 103 | func (lca lineConfigAttributes) append(attr uapi.LineAttribute, mask uapi.LineBitmap) lineConfigAttributes { 104 | for idx, cae := range lca { 105 | if cae.Attr.ID == attr.ID { 106 | lca[idx].Mask &^= mask 107 | } 108 | } 109 | for idx, cae := range lca { 110 | if cae.Attr == attr { 111 | lca[idx].Mask |= mask 112 | return lca 113 | } 114 | } 115 | return append(lca, uapi.LineConfigAttribute{Attr: attr, Mask: mask}) 116 | } 117 | 118 | func (lco lineConfigOptions) toULineConfig() (ulc uapi.LineConfig, err error) { 119 | 120 | mask := uapi.NewLineBitMask(len(lco.offsets)) 121 | cfgAttrs := lineConfigAttributes{ 122 | // first cfg slot reserved for default flags 123 | uapi.LineConfigAttribute{Attr: uapi.LineFlagV2(0).Encode(), Mask: mask}, 124 | } 125 | attrs := lco.defCfg.toLineAttributes() 126 | for _, attr := range attrs { 127 | if attr.ID == uapi.LineAttributeIDFlags { 128 | cfgAttrs[0].Attr = attr 129 | } else { 130 | cfgAttrs = cfgAttrs.append(attr, mask) 131 | } 132 | } 133 | 134 | var outputMask uapi.LineBitmap 135 | if lco.defCfg.Direction == LineDirectionOutput { 136 | outputMask = mask 137 | } 138 | 139 | for idx, offset := range lco.offsets { 140 | cfg := lco.lineCfg[offset] 141 | if cfg == nil { 142 | continue 143 | } 144 | mask = uapi.LineBitmap(1) << uint(idx) 145 | attrs = cfg.toLineAttributes() 146 | for _, attr := range attrs { 147 | cfgAttrs = cfgAttrs.append(attr, mask) 148 | } 149 | if cfg.Direction == LineDirectionOutput { 150 | outputMask |= mask 151 | } else { 152 | outputMask &^= mask 153 | } 154 | } 155 | var defFlags uapi.LineFlagV2 156 | defFlags.Decode(cfgAttrs[0].Attr) 157 | // replace default flags in slot 0 with outputValues 158 | cfgAttrs[0].Attr = lco.outputValues().Encode() 159 | cfgAttrs[0].Mask = outputMask 160 | 161 | // filter mask==0 entries 162 | loopAttrs := cfgAttrs 163 | cfgAttrs = cfgAttrs[:0] 164 | for _, attr := range loopAttrs { 165 | if attr.Mask != 0 { 166 | cfgAttrs = append(cfgAttrs, attr) 167 | } 168 | } 169 | 170 | if len(cfgAttrs) > 10 { 171 | err = ErrConfigOverflow 172 | return 173 | } 174 | 175 | ulc.Flags = defFlags 176 | ulc.NumAttrs = uint32(len(cfgAttrs)) 177 | copy(ulc.Attrs[:], cfgAttrs) 178 | return 179 | } 180 | 181 | // EventHandler is a receiver for line events. 182 | type EventHandler func(LineEvent) 183 | 184 | // AsIsOption indicates the line direction should be left as is. 185 | type AsIsOption int 186 | 187 | // AsIs indicates that a line be requested as neither an input or output. 188 | // 189 | // That is its direction is left as is. This option overrides and clears any 190 | // previous Input or Output options. 191 | const AsIs = AsIsOption(0) 192 | 193 | func (o AsIsOption) applyLineReqOption(l *lineReqOptions) { 194 | l.defCfg.Direction = LineDirectionUnknown 195 | } 196 | 197 | // InputOption indicates the line direction should be set to an input. 198 | type InputOption int 199 | 200 | // AsInput indicates that a line be requested as an input. 201 | // 202 | // This option overrides and clears any previous Output, OpenDrain, or 203 | // OpenSource options. 204 | const AsInput = InputOption(0) 205 | 206 | func (o InputOption) applyLineConfig(lc *LineConfig) { 207 | lc.Direction = LineDirectionInput 208 | lc.Drive = LineDrivePushPull 209 | } 210 | 211 | func (o InputOption) applyChipOption(c *ChipOptions) { 212 | c.config.Direction = LineDirectionInput 213 | } 214 | 215 | func (o InputOption) applyLineReqOption(lro *lineReqOptions) { 216 | o.applyLineConfigOption(&lro.lineConfigOptions) 217 | } 218 | 219 | func (o InputOption) applyLineConfigOption(lco *lineConfigOptions) { 220 | o.applyLineConfig(&lco.defCfg) 221 | } 222 | 223 | func (o InputOption) applySubsetLineConfigOption(offsets []int, l *lineConfigOptions) { 224 | for _, offset := range offsets { 225 | o.applyLineConfig(l.lineConfig(offset)) 226 | } 227 | } 228 | 229 | // OutputOption indicates the line direction should be set to an output. 230 | type OutputOption []int 231 | 232 | // AsOutput indicates that a line or lines be requested as an output. 233 | // 234 | // The initial active state for the line(s) can optionally be provided. 235 | // If fewer values are provided than lines then the remaining lines default to 236 | // inactive. 237 | // 238 | // This option overrides and clears any previous Input, RisingEdge, FallingEdge, 239 | // BothEdges, or Debounce options. 240 | func AsOutput(values ...int) OutputOption { 241 | vv := append([]int(nil), values...) 242 | return OutputOption(vv) 243 | } 244 | 245 | func (o OutputOption) applyLineReqOption(lro *lineReqOptions) { 246 | o.applyLineConfigOption(&lro.lineConfigOptions) 247 | } 248 | 249 | func (o OutputOption) applyLineConfig(lc *LineConfig) { 250 | lc.Direction = LineDirectionOutput 251 | lc.Debounced = false 252 | lc.DebouncePeriod = 0 253 | lc.EdgeDetection = LineEdgeNone 254 | } 255 | 256 | func (o OutputOption) applyLineConfigOption(lco *lineConfigOptions) { 257 | o.applyLineConfig(&lco.defCfg) 258 | for idx, value := range o { 259 | lco.values[lco.offsets[idx]] = value 260 | } 261 | } 262 | 263 | func (o OutputOption) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 264 | for idx, offset := range offsets { 265 | o.applyLineConfig(lco.lineConfig(offset)) 266 | lco.values[offset] = o[idx] 267 | } 268 | } 269 | 270 | // LevelOption determines the line level that is considered active. 271 | type LevelOption bool 272 | 273 | func (o LevelOption) applyChipOption(c *ChipOptions) { 274 | c.config.ActiveLow = bool(o) 275 | } 276 | 277 | func (o LevelOption) applyLineReqOption(lro *lineReqOptions) { 278 | lro.defCfg.ActiveLow = bool(o) 279 | } 280 | 281 | func (o LevelOption) applyLineConfigOption(lco *lineConfigOptions) { 282 | lco.defCfg.ActiveLow = bool(o) 283 | } 284 | 285 | func (o LevelOption) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 286 | for _, offset := range offsets { 287 | lco.lineConfig(offset).ActiveLow = bool(o) 288 | } 289 | } 290 | 291 | // AsActiveLow indicates that a line be considered active when the line level 292 | // is low. 293 | const AsActiveLow = LevelOption(true) 294 | 295 | // AsActiveHigh indicates that a line be considered active when the line level 296 | // is high. 297 | // 298 | // This is the default active level. 299 | const AsActiveHigh = LevelOption(false) 300 | 301 | func (o LineDrive) applyLineConfig(lc *LineConfig) { 302 | lc.Drive = o 303 | lc.Direction = LineDirectionOutput 304 | lc.Debounced = false 305 | lc.DebouncePeriod = 0 306 | lc.EdgeDetection = LineEdgeNone 307 | } 308 | 309 | func (o LineDrive) applyChipOption(c *ChipOptions) { 310 | o.applyLineConfig(&c.config) 311 | } 312 | 313 | func (o LineDrive) applyLineReqOption(lro *lineReqOptions) { 314 | o.applyLineConfig(&lro.defCfg) 315 | } 316 | 317 | func (o LineDrive) applyLineConfigOption(lco *lineConfigOptions) { 318 | o.applyLineConfig(&lco.defCfg) 319 | } 320 | 321 | func (o LineDrive) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 322 | for _, offset := range offsets { 323 | o.applyLineConfig(lco.lineConfig(offset)) 324 | } 325 | } 326 | 327 | // AsOpenDrain indicates that a line be driven low but left floating for high. 328 | // 329 | // This option sets the Output option and overrides and clears any previous 330 | // Input, RisingEdge, FallingEdge, BothEdges, OpenSource, or Debounce options. 331 | const AsOpenDrain = LineDriveOpenDrain 332 | 333 | // AsOpenSource indicates that a line be driven high but left floating for low. 334 | // 335 | // This option sets the Output option and overrides and clears any previous 336 | // Input, RisingEdge, FallingEdge, BothEdges, OpenDrain, or Debounce options. 337 | const AsOpenSource = LineDriveOpenSource 338 | 339 | // AsPushPull indicates that a line be driven both low and high. 340 | // 341 | // This option sets the Output option and overrides and clears any previous 342 | // Input, RisingEdge, FallingEdge, BothEdges, OpenDrain, OpenSource or Debounce 343 | // options. 344 | const AsPushPull = LineDrivePushPull 345 | 346 | func (o LineBias) applyChipOption(c *ChipOptions) { 347 | c.config.Bias = o 348 | } 349 | 350 | func (o LineBias) applyLineReqOption(lro *lineReqOptions) { 351 | lro.defCfg.Bias = o 352 | } 353 | 354 | func (o LineBias) applyLineConfigOption(lco *lineConfigOptions) { 355 | lco.defCfg.Bias = o 356 | } 357 | 358 | func (o LineBias) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 359 | for _, offset := range offsets { 360 | lco.lineConfig(offset).Bias = o 361 | } 362 | } 363 | 364 | // WithBiasAsIs indicates that a line have its internal bias left unchanged. 365 | // 366 | // This option corresponds to the default bias configuration and its only useful 367 | // application is to clear any previous bias option in a chain of LineOptions, 368 | // before that configuration is applied. 369 | // 370 | // Requires Linux 5.5 or later. 371 | const WithBiasAsIs = LineBiasUnknown 372 | 373 | // WithBiasDisabled indicates that a line have its internal bias disabled. 374 | // 375 | // This option overrides and clears any previous bias options. 376 | // 377 | // Requires Linux 5.5 or later. 378 | const WithBiasDisabled = LineBiasDisabled 379 | 380 | // WithPullDown indicates that a line have its internal pull-down enabled. 381 | // 382 | // This option overrides and clears any previous bias options. 383 | // 384 | // Requires Linux 5.5 or later. 385 | const WithPullDown = LineBiasPullDown 386 | 387 | // WithPullUp indicates that a line have its internal pull-up enabled. 388 | // 389 | // This option overrides and clears any previous bias options. 390 | // 391 | // Requires Linux 5.5 or later. 392 | const WithPullUp = LineBiasPullUp 393 | 394 | func (o EventHandler) applyChipOption(c *ChipOptions) { 395 | c.eh = o 396 | } 397 | 398 | func (o EventHandler) applyLineReqOption(lro *lineReqOptions) { 399 | lro.eh = o 400 | } 401 | 402 | // WithEventHandler indicates that a line will generate events when its active 403 | // state transitions from high to low. 404 | // 405 | // Events are forwarded to the provided handler function. 406 | // 407 | // To maintain event ordering, the event handler is called serially for each 408 | // event from the requested lines. To minimize the possibility of overflowing 409 | // the queue of events in the kernel, the event handler should handle or 410 | // hand-off the event and return as soon as possible. 411 | // 412 | // Note that calling Close on the requested line from within the event handler 413 | // will result in deadlock, as the Close waits for the event handler to 414 | // return. Therefore the Close must be called from a different goroutine. 415 | func WithEventHandler(e EventHandler) EventHandler { 416 | return e 417 | } 418 | 419 | func (o LineEdge) applyLineConfig(lc *LineConfig) { 420 | lc.EdgeDetection = o 421 | lc.Direction = LineDirectionInput 422 | } 423 | 424 | func (o LineEdge) applyLineReqOption(lro *lineReqOptions) { 425 | o.applyLineConfig(&lro.defCfg) 426 | } 427 | 428 | func (o LineEdge) applyLineConfigOption(lco *lineConfigOptions) { 429 | o.applyLineConfig(&lco.defCfg) 430 | } 431 | 432 | func (o LineEdge) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 433 | for _, offset := range offsets { 434 | o.applyLineConfig(lco.lineConfig(offset)) 435 | } 436 | } 437 | 438 | // WithFallingEdge indicates that a line will generate events when its active 439 | // state transitions from high to low. 440 | // 441 | // Events are forwarded to the provided handler function. 442 | // 443 | // This option sets the Input option and overrides and clears any previous 444 | // Output, OpenDrain, or OpenSource options. 445 | const WithFallingEdge = LineEdgeFalling 446 | 447 | // WithRisingEdge indicates that a line will generate events when its active 448 | // state transitions from low to high. 449 | // 450 | // Events are forwarded to the provided handler function. 451 | // 452 | // This option sets the Input option and overrides and clears any previous 453 | // Output, OpenDrain, or OpenSource options. 454 | const WithRisingEdge = LineEdgeRising 455 | 456 | // WithBothEdges indicates that a line will generate events when its active 457 | // state transitions from low to high and from high to low. 458 | // 459 | // Events are forwarded to the provided handler function. 460 | // 461 | // This option sets the Input option and overrides and clears any previous 462 | // Output, OpenDrain, or OpenSource options. 463 | const WithBothEdges = LineEdgeBoth 464 | 465 | // WithoutEdges indicates that a line will not generate events due to active 466 | // state transitions. 467 | // 468 | // This is the default for line requests, but allows the removal of edge 469 | // detection by reconfigure. 470 | // 471 | // This option sets the Input option and overrides and clears any previous 472 | // Output, OpenDrain, or OpenSource options. 473 | // 474 | // The WithoutEdges option requires Linux 5.10 or later. 475 | const WithoutEdges = LineEdgeNone 476 | 477 | func (o LineEventClock) applyChipOption(c *ChipOptions) { 478 | c.config.EventClock = LineEventClock(o) 479 | } 480 | 481 | func (o LineEventClock) applyLineReqOption(lro *lineReqOptions) { 482 | lro.defCfg.EventClock = LineEventClock(o) 483 | } 484 | 485 | func (o LineEventClock) applyLineConfigOption(lco *lineConfigOptions) { 486 | lco.defCfg.EventClock = LineEventClock(o) 487 | } 488 | 489 | func (o LineEventClock) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 490 | for _, offset := range offsets { 491 | lco.lineConfig(offset).EventClock = LineEventClock(o) 492 | } 493 | } 494 | 495 | // WithMonotonicEventClock specifies that the edge event timestamps are sourced 496 | // from CLOCK_MONOTONIC. 497 | // 498 | // This option corresponds to the default event clock configuration and its only 499 | // useful application is to clear any previous event clock option in a chain of 500 | // LineOptions, before that configuration is applied. 501 | const WithMonotonicEventClock = LineEventClockMonotonic 502 | 503 | // WithRealtimeEventClock specifies that the edge event timestamps are sourced 504 | // from CLOCK_REALTIME. 505 | // 506 | // Requires Linux 5.11 or later. 507 | const WithRealtimeEventClock = LineEventClockRealtime 508 | 509 | // DebounceOption indicates that a line will be debounced. 510 | // 511 | // The DebounceOption requires Linux 5.10 or later. 512 | type DebounceOption time.Duration 513 | 514 | func (o DebounceOption) applyLineConfig(lc *LineConfig) { 515 | lc.Direction = LineDirectionInput 516 | lc.Debounced = true 517 | lc.DebouncePeriod = time.Duration(o) 518 | } 519 | 520 | func (o DebounceOption) applyLineReqOption(lro *lineReqOptions) { 521 | o.applyLineConfig(&lro.defCfg) 522 | } 523 | 524 | func (o DebounceOption) applyLineConfigOption(lco *lineConfigOptions) { 525 | o.applyLineConfig(&lco.defCfg) 526 | } 527 | 528 | func (o DebounceOption) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 529 | for _, offset := range offsets { 530 | o.applyLineConfig(lco.lineConfig(offset)) 531 | } 532 | } 533 | 534 | // WithDebounce indicates that a line will be debounced with the specified 535 | // debounce period. 536 | // 537 | // This option sets the Input option and overrides and clears any previous 538 | // Output, OpenDrain, or OpenSource options. 539 | // 540 | // Requires Linux 5.10 or later. 541 | func WithDebounce(period time.Duration) DebounceOption { 542 | return DebounceOption(period) 543 | } 544 | 545 | // ABIVersionOption selects the version of the GPIO ioctl commands to use. 546 | // 547 | // The default is to use the latest version supported by the kernel. 548 | type ABIVersionOption int 549 | 550 | func (o ABIVersionOption) applyChipOption(c *ChipOptions) { 551 | c.abi = int(o) 552 | } 553 | 554 | func (o ABIVersionOption) applyLineReqOption(l *lineReqOptions) { 555 | l.abi = int(o) 556 | } 557 | 558 | // WithABIVersion indicates the version of the GPIO ioctls to use. 559 | // 560 | // The default is to use the latest version supported by the kernel. 561 | // 562 | // ABI version 2 requires Linux 5.10 or later. 563 | func WithABIVersion(version int) ABIVersionOption { 564 | return ABIVersionOption(version) 565 | } 566 | 567 | // LinesOption specifies line options that are to be applied to a subset of 568 | // the lines in a request. 569 | type LinesOption struct { 570 | offsets []int 571 | options []SubsetLineConfigOption 572 | } 573 | 574 | func (o LinesOption) applyLineReqOption(lro *lineReqOptions) { 575 | o.applyLineConfigOption(&lro.lineConfigOptions) 576 | } 577 | 578 | func (o LinesOption) applyLineConfigOption(lco *lineConfigOptions) { 579 | for _, option := range o.options { 580 | option.applySubsetLineConfigOption(o.offsets, lco) 581 | } 582 | } 583 | 584 | // WithLines specifies line options to be applied to a subset of the lines in a 585 | // request. 586 | // 587 | // The offsets should be a strict subset of the offsets provided to 588 | // RequestLines(). 589 | // Any offsets outside that set are ignored. 590 | func WithLines(offsets []int, options ...SubsetLineConfigOption) LinesOption { 591 | return LinesOption{offsets, options} 592 | } 593 | 594 | // DefaultedOption resets the configuration to default values. 595 | type DefaultedOption int 596 | 597 | func (o DefaultedOption) applyLineReqOption(lro *lineReqOptions) { 598 | o.applyLineConfigOption(&lro.lineConfigOptions) 599 | } 600 | 601 | func (o DefaultedOption) applyLineConfigOption(lco *lineConfigOptions) { 602 | lco.defCfg = LineConfig{} 603 | lco.values = map[int]int{} 604 | } 605 | 606 | func (o DefaultedOption) applySubsetLineConfigOption(offsets []int, lco *lineConfigOptions) { 607 | if len(offsets) == 0 { 608 | lco.lineCfg = nil 609 | } 610 | for _, offset := range offsets { 611 | delete(lco.values, offset) 612 | delete(lco.lineCfg, offset) 613 | } 614 | } 615 | 616 | // Defaulted resets all configuration options to default values. 617 | // 618 | // This option provides the means to simply reset all configuration options to 619 | // their default values. This is rarely necessary but is made available for 620 | // completeness. 621 | // 622 | // When applied within WithLines() it resets the configuration of the lines to 623 | // the default for the request, effectively clearing all previous WithLines() 624 | // options for the specified offsets. If no offsets are specified then the 625 | // configuration for all offsets is reset to the request default. 626 | // 627 | // When applied outside WithLines() it resets the default configuration for the 628 | // request itself to default values but leaves any configuration set within 629 | // WithLines() unchanged. 630 | const Defaulted = DefaultedOption(0) 631 | 632 | // EventBufferSizeOption provides a suggested minimum number of events the 633 | // kernel will buffer for the line request. 634 | // 635 | // The EventBufferSizeOption requires Linux 5.10 or later. 636 | type EventBufferSizeOption int 637 | 638 | func (o EventBufferSizeOption) applyLineReqOption(lro *lineReqOptions) { 639 | lro.eventBufferSize = int(o) 640 | } 641 | 642 | // WithEventBufferSize suggests a minimum number of events the kernel will 643 | // buffer for the line request. 644 | // 645 | // Note that the value is only a suggestion, and the kernel may set higher 646 | // values or place a cap on the buffer size. 647 | // 648 | // A zero value (the default) indicates that the kernel should use its default 649 | // buffer size (the number of requested lines * 16). 650 | // 651 | // Requires Linux 5.10 or later. 652 | func WithEventBufferSize(size int) EventBufferSizeOption { 653 | return EventBufferSizeOption(size) 654 | } 655 | -------------------------------------------------------------------------------- /uapi/README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # uapi 8 | 9 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/warthog618/go-gpiocdev/uapi)](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi) 10 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/warthog618/go-gpiocdev/blob/master/LICENSE) 11 | 12 | GPIOCDEV UAPI is a thin layer over the system ioctl calls that comprise the Linux GPIO UAPI. 13 | 14 | This library is used by **[gpiocdev](https://github.com/warthog618/go-gpiocdev)** to interact with the Linux kernel. 15 | 16 | The library is exposed to allow for testing of the UAPI with the minimal amount of Go in the way. 17 | 18 | **gpiocdev** provides a higher level of abstraction, so for general use you probably want to be using that. 19 | 20 | ## API 21 | 22 | Both versions of the GPIO UAPI are supported; the current v2 and the deprecated v1. 23 | 24 | ### V2 25 | 26 | The GPIO UAPI v2 comprises eight ioctls (two of which are unchanged from v1): 27 | 28 | IOCTL | Scope | Description 29 | ---|--- | --- 30 | [GetChipInfo](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetChipInfo) | chip | Return information about the chip itself. 31 | [GetLineInfoV2](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLineInfoV2) | chip | Return information about a particular line on the chip. 32 | [GetLine](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLine) | chip | Request a set of lines, and returns a file handle for ioctl commands. The set may be any subset of the lines supported by the chip, including a single line. This may be used for both input and output lines. The lines remain reserved by the caller until the returned fd is closed. 33 | [GetLineValuesV2](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLineValuesV2) | line | Return the current value of a set of lines in an existing line request. 34 | [SetLineValuesV2](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#SetLineValuesV2) | line | Set the current value of a set of lines in an existing line request. 35 | [SetLineConfigV2](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#SetLineConfigV2) | line | Update the configuration of the lines in an existing line request. 36 | [WatchLineInfoV2](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#WatchLineInfoV2) | chip | Add a watch for changes to the info of a particular line on the chip. 37 | [UnwatchLineInfo](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#UnwatchLineInfo) | chip | Remove a watch for changes to the info of a particular line on the chip. 38 | 39 | ### V1 40 | 41 | The GPIO UAPI v1 comprises nine ioctls: 42 | 43 | IOCTL | Scope | Description 44 | ---|--- | --- 45 | [GetChipInfo](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetChipInfo) | chip | Return information about the chip itself. 46 | [GetLineInfo](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLineInfo) | chip | Return information about a particular line on the chip. 47 | [GetLineHandle](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLineHandle) | chip | Request a set of lines, and returns a file handle for ioctl commands. The set may be any subset of the lines supported by the chip, including a single line. This may be used for both input and output lines. The lines remain reserved by the caller until the returned fd is closed. 48 | [GetLineEvent](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLineEvent) | chip | Request an individual input line with edge detection enabled, and returns a file handle for ioctl commands and to return edge events. Events can only be requested on input lines. The line remains reserved by the caller until the returned fd is closed. 49 | [GetLineValues](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#GetLineValues) | line | Return the current value of a set of lines in an existing handle or event request. 50 | [SetLineValues](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#SetLineValues) | line | Set the current value of a set of lines in an existing handle request. 51 | [SetLineConfig](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#SetLineConfig) | line | Update the configuration of the lines in an existing handle request. 52 | [WatchLineInfo](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#WatchLineInfo) | chip | Add a watch for changes to the info of a particular line on the chip. 53 | [UnwatchLineInfo](https://pkg.go.dev/github.com/warthog618/go-gpiocdev/uapi#UnwatchLineInfo) | chip | Remove a watch for changes to the info of a particular line on the chip. 54 | 55 | ## Usage 56 | 57 | The following is a brief example of the usage of the major functions using v2 of the UAPI: 58 | 59 | ```go 60 | f, _ := os.OpenFile("/dev/gpiochip0", unix.O_CLOEXEC, unix.O_RDONLY) 61 | 62 | // get chip info 63 | ci, _ := uapi.GetChipInfo(f.Fd()) 64 | fmt.Print(ci) 65 | 66 | // get line info 67 | li, _ := uapi.GetLineInfo(f.Fd(), offset) 68 | fmt.Print(li) 69 | 70 | // request a line 71 | lr := uapi.LineRequest{ 72 | Lines: uint32(len(offsets)), 73 | Config: uapi.LineConfig{ 74 | Flags: uapi.LineFlagV2Output, 75 | }, 76 | // initialise Offsets, OutputValues and Consumer... 77 | } 78 | err := uapi.GetLine(f.Fd(), &lr) 79 | 80 | // request a line with events 81 | lr = uapi.LineRequest{ 82 | Lines: uint32(len(offsets)), 83 | Config: uapi.LineConfig{ 84 | Flags: uapi.LineFlagV2Input | uapi.LineFlagV2ActiveLow | uapi.LineFlagV2EdgeBoth, 85 | }, 86 | // initialise Offsets and Consumer... 87 | } 88 | err = uapi.GetLine(f.Fd(), &lr) 89 | if err != nil { 90 | // wait on lr.fd for events... 91 | 92 | // read event 93 | evt, _ := uapi.ReadLineEvent(uintptr(lr.Fd)) 94 | fmt.Print(evt) 95 | } 96 | 97 | // get values 98 | var values uapi.LineValues 99 | err = uapi.GetLineValuesV2(uintptr(lr.Fd), &values) 100 | 101 | // set values 102 | err = uapi.SetLineValuesV2(uintptr(lr.Fd), values) 103 | 104 | // update line config - change to outputs 105 | err = uapi.SetLineConfigV2(uintptr(lr.Fd), &uapi.LineConfig{ 106 | Flags: uapi.LineFlagV2Output, 107 | NumAttrs: 1, 108 | // initialise OutputValues... 109 | }) 110 | 111 | ``` 112 | 113 | Error handling and other tedious bits, such as initialising the arrays in the requests, omitted for brevity. 114 | 115 | Refer to **[gpiocdev](https://github.com/warthog618/go-gpiocdev)** for a concrete example of uapi usage. 116 | 117 | This is essentially the same example using v1 of the UAPI: 118 | 119 | ```go 120 | f, _ := os.OpenFile("/dev/gpiochip0", unix.O_CLOEXEC, unix.O_RDONLY) 121 | 122 | // get chip info 123 | ci, _ := uapi.GetChipInfo(f.Fd()) 124 | fmt.Print(ci) 125 | 126 | // get line info 127 | li, _ := uapi.GetLineInfo(f.Fd(), offset) 128 | fmt.Print(li) 129 | 130 | // request a line 131 | hr := uapi.HandleRequest{ 132 | Lines: uint32(len(offsets)), 133 | Flags: uapi.HandleRequestOutput, 134 | // initialise Offsets, DefaultValues and Consumer... 135 | } 136 | err := uapi.GetLineHandle(f.Fd(), &hr) 137 | 138 | // request a line with events 139 | er := uapi.EventRequest{ 140 | Offset: uint32(offset), 141 | HandleFlags: uapi.HandleRequestActiveLow, 142 | EventFlags: uapi.EventRequestBothEdges, 143 | // initialise Consumer... 144 | } 145 | err = uapi.GetLineEvent(f.Fd(), &er) 146 | if err != nil { 147 | // wait on er.fd for events... 148 | 149 | // read event 150 | evt, _ := uapi.ReadEvent(uintptr(er.Fd)) 151 | fmt.Print(evt) 152 | } 153 | 154 | // get values 155 | var values uapi.HandleData 156 | err = uapi.GetLineValues(uintptr(er.Fd), &values) 157 | 158 | // set values 159 | values[0] = uint8(value) 160 | err = uapi.SetLineValues(uintptr(hr.Fd), values) 161 | 162 | // update line config - change to active low 163 | err = uapi.SetLineConfig(uintptr(hr.Fd), &uapi.HandleConfig{ 164 | Flags: uapi.HandleRequestInput, 165 | }) 166 | 167 | ``` 168 | -------------------------------------------------------------------------------- /uapi/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/env sh 2 | export CGO_ENABLED=0 3 | go test -c --cover . 4 | go test -c -o uapi_x86_64.test . 5 | GOARCH=386 go test -c -o uapi_x86.test . 6 | GOARCH=mips go test -c -o uapi_mips32.test . 7 | GOARCH=mipsle go test -c -o uapi_mips32le.test . 8 | GOARCH=mips64 go test -c -o uapi_mips64.test . 9 | GOARCH=arm GOARM=6 go test -c -o uapi_arm32.test . 10 | GOARCH=arm64 go test -c -o uapi_aarch64.test . 11 | #GOARCH=riscv go test -c -o uapi_riscv.test . 12 | GOARCH=riscv64 go test -c -o uapi_riscv64.test . 13 | 14 | -------------------------------------------------------------------------------- /uapi/endian.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // don't build on platforms with fixed endianness 6 | //go:build !amd64 && !386 7 | 8 | package uapi 9 | 10 | import ( 11 | "encoding/binary" 12 | "unsafe" 13 | ) 14 | 15 | // endian to use to decode reads from the local kernel. 16 | var nativeEndian binary.ByteOrder 17 | 18 | func init() { 19 | // the standard hack to determine native Endianness. 20 | buf := [2]byte{} 21 | *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) 22 | switch buf { 23 | case [2]byte{0xCD, 0xAB}: 24 | nativeEndian = binary.LittleEndian 25 | case [2]byte{0xAB, 0xCD}: 26 | nativeEndian = binary.BigEndian 27 | default: 28 | panic("Could not determine native endianness.") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /uapi/endian_intel.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build 386 || amd64 6 | 7 | package uapi 8 | 9 | import ( 10 | "encoding/binary" 11 | ) 12 | 13 | // endian to use to decode reads from the local kernel. 14 | var nativeEndian binary.ByteOrder = binary.LittleEndian 15 | -------------------------------------------------------------------------------- /uapi/eventdata.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux && !386 6 | 7 | // Package uapi provides the Linux GPIO UAPI definitions for gpiocdev. 8 | package uapi 9 | 10 | // EventData contains the details of a particular line event. 11 | // 12 | // This is returned via the event request fd in response to events. 13 | type EventData struct { 14 | // The time the event was detected. 15 | Timestamp uint64 16 | 17 | // The type of event detected. 18 | ID EventFlag 19 | 20 | // pad to workaround 64-bit padding 21 | _ uint32 22 | } 23 | -------------------------------------------------------------------------------- /uapi/eventdata_386.go: -------------------------------------------------------------------------------- 1 | /// SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // Package uapi provides the Linux GPIO UAPI definitions for gpiocdev. 8 | package uapi 9 | 10 | // EventData contains the details of a particular line event. 11 | // 12 | // This is returned via the event request fd in response to events. 13 | type EventData struct { 14 | // The time the event was detected. 15 | Timestamp uint64 16 | 17 | // The type of event detected. 18 | ID EventFlag 19 | 20 | // No pad required for i386. 21 | } 22 | -------------------------------------------------------------------------------- /uapi/ioctl.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | package uapi 8 | 9 | // ioctl constants defined in ioctl_XXX 10 | 11 | func ior(t, nr, size uintptr) ioctl { 12 | return ioctl((iocRead << iocDirShift) | 13 | (size << iocSizeShift) | 14 | (t << iocTypeShift) | 15 | (nr << iocNRShift)) 16 | } 17 | 18 | func iorw(t, nr, size uintptr) ioctl { 19 | return ioctl(((iocRead | iocWrite) << iocDirShift) | 20 | (size << iocSizeShift) | 21 | (t << iocTypeShift) | 22 | (nr << iocNRShift)) 23 | } 24 | 25 | func iow(t, nr, size uintptr) ioctl { 26 | return ioctl((iocWrite << iocDirShift) | 27 | (size << iocSizeShift) | 28 | (t << iocTypeShift) | 29 | (nr << iocNRShift)) 30 | } 31 | -------------------------------------------------------------------------------- /uapi/ioctl_default.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build arm || arm64 || 386 || amd64 || riscv64 6 | 7 | package uapi 8 | 9 | // ioctl constants 10 | const ( 11 | iocNRBits = 8 12 | iocTypeBits = 8 13 | iocDirBits = 2 14 | iocSizeBits = 14 15 | iocNRShift = 0 16 | iocTypeShift = iocNRShift + iocNRBits 17 | iocSizeShift = iocTypeShift + iocTypeBits 18 | iocDirShift = iocSizeShift + iocSizeBits 19 | iocWrite = 1 20 | iocRead = 2 21 | ) 22 | -------------------------------------------------------------------------------- /uapi/ioctl_mips32.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build mips || mipsle || mips64 || mips64le || ppc64 || ppc64le || sparc || sparc64 6 | 7 | package uapi 8 | 9 | // ioctl constants 10 | const ( 11 | iocNRBits = 8 12 | iocTypeBits = 8 13 | iocDirBits = 3 14 | iocSizeBits = 13 15 | iocNRShift = 0 16 | iocTypeShift = iocNRShift + iocNRBits 17 | iocSizeShift = iocTypeShift + iocTypeBits 18 | iocDirShift = iocSizeShift + iocSizeBits 19 | iocWrite = 4 20 | iocRead = 2 21 | // iocNone = 1 22 | ) 23 | -------------------------------------------------------------------------------- /uapi/test/reflectsize.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | 8 | "github.com/warthog618/go-gpiocdev/uapi" 9 | ) 10 | 11 | // sizeof returns the size >= 0 of variables for the given type or -1 if the type is not acceptable. 12 | func sizeof(t reflect.Type) int { 13 | switch t.Kind() { 14 | case reflect.Array: 15 | if s := sizeof(t.Elem()); s >= 0 { 16 | return s * t.Len() 17 | } 18 | 19 | case reflect.Struct: 20 | sum := 0 21 | for i, n := 0, t.NumField(); i < n; i++ { 22 | s := sizeof(t.Field(i).Type) 23 | if s < 0 { 24 | return -1 25 | } 26 | sum += s 27 | } 28 | return sum 29 | 30 | case reflect.Bool, 31 | reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 32 | reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 33 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 34 | return int(t.Size()) 35 | } 36 | 37 | return -1 38 | } 39 | 40 | // dataSize returns the number of bytes the actual data represented by v occupies in memory. 41 | // For compound structures, it sums the sizes of the elements. Thus, for instance, for a slice 42 | // it returns the length of the slice times the element size and does not count the memory 43 | // occupied by the header. If the type of v is not acceptable, dataSize returns -1. 44 | func dataSize(v reflect.Value) int { 45 | if v.Kind() == reflect.Slice { 46 | if s := sizeof(v.Type().Elem()); s >= 0 { 47 | return s * v.Len() 48 | } 49 | return -1 50 | } 51 | return sizeof(v.Type()) 52 | } 53 | 54 | func main() { 55 | var lic uapi.LineInfoChanged 56 | 57 | fmt.Printf("unsafe.sizeof: %d\n", unsafe.Sizeof(lic)) 58 | 59 | v := reflect.ValueOf(lic) 60 | size := dataSize(v) 61 | fmt.Printf("reflect size: %d\n", size) 62 | 63 | fmt.Printf("unsafe.sizeof: %d\n", unsafe.Sizeof(lic.Info)) 64 | v = reflect.ValueOf(lic.Info) 65 | size = dataSize(v) 66 | fmt.Printf("reflect size: %d\n", size) 67 | } 68 | -------------------------------------------------------------------------------- /uapi/uapi.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | // Package uapi provides the Linux GPIO UAPI definitions for gpiocdev. 8 | package uapi 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | "fmt" 14 | "regexp" 15 | "strconv" 16 | "unsafe" 17 | 18 | "golang.org/x/sys/unix" 19 | ) 20 | 21 | // GetChipInfo returns the ChipInfo for the GPIO character device. 22 | // 23 | // The fd is an open GPIO character device. 24 | func GetChipInfo(fd uintptr) (ChipInfo, error) { 25 | var ci ChipInfo 26 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 27 | fd, 28 | uintptr(getChipInfoIoctl), 29 | uintptr(unsafe.Pointer(&ci))) 30 | if errno != 0 { 31 | return ci, errno 32 | } 33 | return ci, nil 34 | } 35 | 36 | // GetLineInfo returns the LineInfo for one line from the GPIO character device. 37 | // 38 | // The fd is an open GPIO character device. 39 | // The offset is zero based. 40 | func GetLineInfo(fd uintptr, offset int) (LineInfo, error) { 41 | li := LineInfo{Offset: uint32(offset)} 42 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 43 | fd, 44 | uintptr(getLineInfoIoctl), 45 | uintptr(unsafe.Pointer(&li))) 46 | if errno != 0 { 47 | return LineInfo{}, errno 48 | } 49 | return li, nil 50 | } 51 | 52 | // GetLineEvent requests a line from the GPIO character device with event 53 | // reporting enabled. 54 | // 55 | // The fd is an open GPIO character device. 56 | // The line must be an input and must not already be requested. 57 | // If successful, the fd for the line is returned in the request.fd. 58 | func GetLineEvent(fd uintptr, request *EventRequest) error { 59 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 60 | fd, 61 | uintptr(getLineEventIoctl), 62 | uintptr(unsafe.Pointer(request))) 63 | if errno != 0 { 64 | return errno 65 | } 66 | return nil 67 | } 68 | 69 | // GetLineHandle requests a line from the GPIO character device. 70 | // 71 | // This request is without event reporting. 72 | // The fd is an open GPIO character device. 73 | // The lines must not already be requested. 74 | // The flags in the request will be applied to all lines in the request. 75 | // If successful, the fd for the line is returned in the request.fd. 76 | func GetLineHandle(fd uintptr, request *HandleRequest) error { 77 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 78 | fd, 79 | uintptr(getLineHandleIoctl), 80 | uintptr(unsafe.Pointer(request))) 81 | if errno != 0 { 82 | return errno 83 | } 84 | return nil 85 | } 86 | 87 | // GetLineValues returns the values of a set of requested lines. 88 | // 89 | // The fd is a requested line, as returned by GetLineHandle or GetLineEvent. 90 | func GetLineValues(fd uintptr, values *HandleData) error { 91 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 92 | fd, 93 | uintptr(getLineValuesIoctl), 94 | uintptr(unsafe.Pointer(&values[0]))) 95 | if errno != 0 { 96 | return errno 97 | } 98 | return nil 99 | } 100 | 101 | // SetLineValues sets the values of a set of requested lines. 102 | // 103 | // The fd is a requested line, as returned by GetLineHandle or GetLineEvent. 104 | func SetLineValues(fd uintptr, values HandleData) error { 105 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 106 | fd, 107 | uintptr(setLineValuesIoctl), 108 | uintptr(unsafe.Pointer(&values[0]))) 109 | if errno != 0 { 110 | return errno 111 | } 112 | return nil 113 | } 114 | 115 | // SetLineConfig sets the config of an existing handle request. 116 | // 117 | // The config flags in the request will be applied to all lines in the handle 118 | // request. 119 | func SetLineConfig(fd uintptr, config *HandleConfig) error { 120 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 121 | fd, 122 | uintptr(setLineConfigIoctl), 123 | uintptr(unsafe.Pointer(config))) 124 | if errno != 0 { 125 | return errno 126 | } 127 | return nil 128 | } 129 | 130 | // WatchLineInfo sets a watch on info of a line. 131 | // 132 | // A watch is set on the line indicated by info.Offset. If successful the 133 | // current line info is returned, else an error is returned. 134 | func WatchLineInfo(fd uintptr, info *LineInfo) error { 135 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 136 | fd, 137 | uintptr(watchLineInfoIoctl), 138 | uintptr(unsafe.Pointer(info))) 139 | if errno != 0 { 140 | return errno 141 | } 142 | return nil 143 | } 144 | 145 | // UnwatchLineInfo clears a watch on info of a line. 146 | // 147 | // Disables the watch on info for the line. 148 | func UnwatchLineInfo(fd uintptr, offset uint32) error { 149 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 150 | fd, 151 | uintptr(unwatchLineInfoIoctl), 152 | uintptr(unsafe.Pointer(&offset))) 153 | if errno != 0 { 154 | return errno 155 | } 156 | return nil 157 | } 158 | 159 | // BytesToString is a helper function that converts strings stored in byte 160 | // arrays, as returned by GetChipInfo and GetLineInfo, into strings. 161 | func BytesToString(a []byte) string { 162 | n := bytes.IndexByte(a, 0) 163 | if n == -1 { 164 | return string(a) 165 | } 166 | return string(a[:n]) 167 | } 168 | 169 | type fdReader int 170 | 171 | func (fd fdReader) Read(b []byte) (int, error) { 172 | return unix.Read(int(fd), b[:]) 173 | } 174 | 175 | // ReadEvent reads a single event from a requested line. 176 | // 177 | // The fd is a requested line, as returned by GetLineEvent. 178 | // 179 | // This function is blocking and should only be called when the fd is known to 180 | // be ready to read. 181 | func ReadEvent(fd uintptr) (EventData, error) { 182 | var ed EventData 183 | err := binary.Read(fdReader(fd), nativeEndian, &ed) 184 | return ed, err 185 | } 186 | 187 | // ReadLineInfoChanged reads a line info changed event from a chip. 188 | // 189 | // The fd is an open GPIO character device. 190 | // 191 | // This function is blocking and should only be called when the fd is known to 192 | // be ready to read. 193 | func ReadLineInfoChanged(fd uintptr) (LineInfoChanged, error) { 194 | var lic LineInfoChanged 195 | err := binary.Read(fdReader(fd), nativeEndian, &lic) 196 | return lic, err 197 | } 198 | 199 | // IOCTL command codes 200 | type ioctl uintptr 201 | 202 | var ( 203 | getChipInfoIoctl ioctl 204 | getLineInfoIoctl ioctl 205 | getLineHandleIoctl ioctl 206 | getLineEventIoctl ioctl 207 | getLineValuesIoctl ioctl 208 | setLineValuesIoctl ioctl 209 | setLineConfigIoctl ioctl 210 | watchLineInfoIoctl ioctl 211 | unwatchLineInfoIoctl ioctl 212 | ) 213 | 214 | // Size of name and consumer strings. 215 | const nameSize = 32 216 | 217 | func init() { 218 | // ioctls require struct sizes which are only available at runtime. 219 | var ci ChipInfo 220 | getChipInfoIoctl = ior(0xB4, 0x01, unsafe.Sizeof(ci)) 221 | var li LineInfo 222 | getLineInfoIoctl = iorw(0xB4, 0x02, unsafe.Sizeof(li)) 223 | var hr HandleRequest 224 | getLineHandleIoctl = iorw(0xB4, 0x03, unsafe.Sizeof(hr)) 225 | var le EventRequest 226 | getLineEventIoctl = iorw(0xB4, 0x04, unsafe.Sizeof(le)) 227 | var hd HandleData 228 | getLineValuesIoctl = iorw(0xB4, 0x08, unsafe.Sizeof(hd)) 229 | setLineValuesIoctl = iorw(0xB4, 0x09, unsafe.Sizeof(hd)) 230 | var hc HandleConfig 231 | setLineConfigIoctl = iorw(0xB4, 0x0A, unsafe.Sizeof(hc)) 232 | watchLineInfoIoctl = iorw(0xB4, 0x0B, unsafe.Sizeof(li)) 233 | unwatchLineInfoIoctl = iorw(0xB4, 0x0C, unsafe.Sizeof(li.Offset)) 234 | } 235 | 236 | // ChipInfo contains the details of a GPIO chip. 237 | type ChipInfo struct { 238 | // The system name of the device. 239 | Name [nameSize]byte 240 | 241 | // An identifying label added by the device driver. 242 | Label [nameSize]byte 243 | 244 | // The number of lines supported by this chip. 245 | Lines uint32 246 | } 247 | 248 | // LineInfo contains the details of a single line of a GPIO chip. 249 | type LineInfo struct { 250 | // The offset of the line within the chip. 251 | Offset uint32 252 | 253 | // The line flags applied to this line. 254 | Flags LineFlag 255 | 256 | // The system name for this line. 257 | Name [nameSize]byte 258 | 259 | // If requested, a string added by the requester to identify the 260 | // owner of the request. 261 | Consumer [nameSize]byte 262 | } 263 | 264 | // LineInfoChanged contains the details of a change to line info. 265 | // 266 | // This is returned via the chip fd in response to changes to watched lines. 267 | type LineInfoChanged struct { 268 | // The updated info. 269 | Info LineInfo 270 | 271 | // The time the change occurred. 272 | Timestamp uint64 273 | 274 | // The type of change. 275 | Type ChangeType 276 | 277 | // reserved for future use. 278 | _ [5]uint32 279 | } 280 | 281 | // ChangeType indicates the type of change that has occurred to a line. 282 | type ChangeType uint32 283 | 284 | const ( 285 | _ ChangeType = iota 286 | 287 | // LineChangedRequested indicates the line has been requested. 288 | LineChangedRequested 289 | 290 | // LineChangedReleased indicates the line has been released. 291 | LineChangedReleased 292 | 293 | // LineChangedConfig indicates the line configuration has changed. 294 | LineChangedConfig 295 | ) 296 | 297 | // LineFlag are the flags for a line. 298 | type LineFlag uint32 299 | 300 | const ( 301 | // LineFlagUsed indicates that the line has been requested. 302 | // It may have been requested by this process or another process. 303 | // The line cannot be requested again until this flag is clear. 304 | LineFlagUsed LineFlag = 1 << iota 305 | 306 | // LineFlagIsOut indicates that the line is an output. 307 | LineFlagIsOut 308 | 309 | // LineFlagActiveLow indicates that the line is active low. 310 | LineFlagActiveLow 311 | 312 | // LineFlagOpenDrain indicates that the line will pull low when set low but 313 | // float when set high. This flag only applies to output lines. 314 | // An output cannot be both open drain and open source. 315 | LineFlagOpenDrain 316 | 317 | // LineFlagOpenSource indicates that the line will pull high when set high 318 | // but float when set low. This flag only applies to output lines. 319 | // An output cannot be both open drain and open source. 320 | LineFlagOpenSource 321 | 322 | // LineFlagPullUp indicates that the internal line pull up is enabled. 323 | LineFlagPullUp 324 | 325 | // LineFlagPullDown indicates that the internal line pull down is enabled. 326 | LineFlagPullDown 327 | 328 | // LineFlagBiasDisabled indicates that the internal line bias is disabled. 329 | LineFlagBiasDisabled 330 | ) 331 | 332 | // IsUsed returns true if the line is requested. 333 | func (f LineFlag) IsUsed() bool { 334 | return f&LineFlagUsed != 0 335 | } 336 | 337 | // IsOut returns true if the line is an output. 338 | func (f LineFlag) IsOut() bool { 339 | return f&LineFlagIsOut != 0 340 | } 341 | 342 | // IsActiveLow returns true if the line is active low. 343 | func (f LineFlag) IsActiveLow() bool { 344 | return f&LineFlagActiveLow != 0 345 | } 346 | 347 | // IsOpenDrain returns true if the line is open-drain. 348 | func (f LineFlag) IsOpenDrain() bool { 349 | return f&LineFlagOpenDrain != 0 350 | } 351 | 352 | // IsOpenSource returns true if the line is open-source. 353 | func (f LineFlag) IsOpenSource() bool { 354 | return f&LineFlagOpenSource != 0 355 | } 356 | 357 | // IsBiasDisable returns true if the line has bias disabled. 358 | func (f LineFlag) IsBiasDisable() bool { 359 | return f&LineFlagBiasDisabled != 0 360 | } 361 | 362 | // IsPullDown returns true if the line has pull-down enabled. 363 | func (f LineFlag) IsPullDown() bool { 364 | return f&LineFlagPullDown != 0 365 | } 366 | 367 | // IsPullUp returns true if the line has pull-up enabled. 368 | func (f LineFlag) IsPullUp() bool { 369 | return f&LineFlagPullUp != 0 370 | } 371 | 372 | // HandleConfig is a request to change the config of an existing request. 373 | // 374 | // Can be applied to both handle and event requests. 375 | // Event requests cannot be reconfigured to outputs. 376 | type HandleConfig struct { 377 | // The flags to be applied to the lines. 378 | Flags HandleFlag 379 | 380 | // The default values to be applied to output lines (when 381 | // HandleRequestOutput is set in the Flags). 382 | DefaultValues [HandlesMax]uint8 383 | 384 | // reserved for future use. 385 | _ [4]uint32 386 | } 387 | 388 | // HandleRequest is a request for control of a set of lines. 389 | // The lines must all be on the same GPIO chip. 390 | type HandleRequest struct { 391 | // The lines to be requested. 392 | Offsets [HandlesMax]uint32 393 | 394 | // The flags to be applied to the lines. 395 | Flags HandleFlag 396 | 397 | // The default values to be applied to output lines. 398 | DefaultValues [HandlesMax]uint8 399 | 400 | // The string identifying the requester to be applied to the lines. 401 | Consumer [nameSize]byte 402 | 403 | // The number of lines being requested. 404 | Lines uint32 405 | 406 | // The file handle for the requested lines. 407 | // Set if the request is successful. 408 | Fd int32 409 | } 410 | 411 | // HandleFlag contains the 412 | type HandleFlag uint32 413 | 414 | const ( 415 | // HandleRequestInput requests the line as an input. 416 | // 417 | // This is ignored if Output is also set. 418 | HandleRequestInput HandleFlag = 1 << iota 419 | 420 | // HandleRequestOutput requests the line as an output. 421 | // 422 | // This takes precedence over Input, if both are set. 423 | HandleRequestOutput 424 | 425 | // HandleRequestActiveLow requests the line be made active low. 426 | HandleRequestActiveLow 427 | 428 | // HandleRequestOpenDrain requests the line be made open drain. 429 | // 430 | // This option requires the line to be requested as an Output. 431 | // This cannot be set at the same time as OpenSource. 432 | HandleRequestOpenDrain 433 | 434 | // HandleRequestOpenSource requests the line be made open source. 435 | // 436 | // This option requires the line to be requested as an Output. 437 | // This cannot be set at the same time as OpenDrain. 438 | HandleRequestOpenSource 439 | 440 | // HandleRequestPullUp requests the line have pull-up enabled. 441 | HandleRequestPullUp 442 | 443 | // HandleRequestPullDown requests the line have pull-down enabled. 444 | HandleRequestPullDown 445 | 446 | // HandleRequestBiasDisable requests the line have bias disabled. 447 | HandleRequestBiasDisable 448 | 449 | // HandlesMax is the maximum number of lines that can be requested in a 450 | // single request. 451 | HandlesMax = 64 452 | ) 453 | 454 | // IsInput returns true if the line is requested as an input. 455 | func (f HandleFlag) IsInput() bool { 456 | return f&HandleRequestInput != 0 457 | } 458 | 459 | // IsOutput returns true if the line is requested as an output. 460 | func (f HandleFlag) IsOutput() bool { 461 | return f&HandleRequestOutput != 0 462 | } 463 | 464 | // IsActiveLow returns true if the line is requested as a active low. 465 | func (f HandleFlag) IsActiveLow() bool { 466 | return f&HandleRequestActiveLow != 0 467 | } 468 | 469 | // IsOpenDrain returns true if the line is requested as an open drain. 470 | func (f HandleFlag) IsOpenDrain() bool { 471 | return f&HandleRequestOpenDrain != 0 472 | } 473 | 474 | // IsOpenSource returns true if the line is requested as an open source. 475 | func (f HandleFlag) IsOpenSource() bool { 476 | return f&HandleRequestOpenSource != 0 477 | } 478 | 479 | // HasBiasFlag returns true if any bias flags are set. 480 | func (f HandleFlag) HasBiasFlag() bool { 481 | return f&(HandleRequestBiasDisable|HandleRequestPullDown|HandleRequestPullUp) != 0 482 | 483 | } 484 | 485 | // IsBiasDisable returns true if the line is requested with bias disabled. 486 | func (f HandleFlag) IsBiasDisable() bool { 487 | return f&HandleRequestBiasDisable != 0 488 | } 489 | 490 | // IsPullDown returns true if the line is requested with pull-down enabled. 491 | func (f HandleFlag) IsPullDown() bool { 492 | return f&HandleRequestPullDown != 0 493 | } 494 | 495 | // IsPullUp returns true if the line is requested with pull-up enabled. 496 | func (f HandleFlag) IsPullUp() bool { 497 | return f&HandleRequestPullUp != 0 498 | } 499 | 500 | // HandleData contains the logical value for each line. 501 | // Zero is a logical low and any other value is a logical high. 502 | type HandleData [HandlesMax]uint8 503 | 504 | // EventRequest is a request for control of a line with event reporting enabled. 505 | type EventRequest struct { 506 | // The line to be requested. 507 | Offset uint32 508 | 509 | // The line flags applied to this line. 510 | HandleFlags HandleFlag 511 | 512 | // The type of events to report. 513 | EventFlags EventFlag 514 | 515 | // The string identifying the requester to be applied to the line. 516 | Consumer [nameSize]byte 517 | 518 | // The file handle for the requested line. 519 | // Set if the request is successful. 520 | Fd int32 521 | } 522 | 523 | // EventFlag indicates the types of events that will be reported. 524 | type EventFlag uint32 525 | 526 | const ( 527 | // EventRequestRisingEdge requests rising edge events. 528 | // This means a transition from a low logical state to a high logical state. 529 | // For active high lines (the default) this means a transition from a 530 | // physical low to a physical high. 531 | // Note that for active low lines this means a transition from a physical 532 | // high to a physical low. 533 | EventRequestRisingEdge EventFlag = 1 << iota 534 | 535 | // EventRequestFallingEdge requests falling edge events. 536 | // This means a transition from a high logical state to a low logical state. 537 | // For active high lines (the default) this means a transition from a 538 | // physical high to a physical low. 539 | // Note that for active low lines this means a transition from a physical 540 | // low to a physical high. 541 | EventRequestFallingEdge 542 | 543 | // EventRequestBothEdges requests both rising and falling edge events. 544 | // This is equivalent to requesting both EventRequestRisingEdge and 545 | // EventRequestRisingEdge. 546 | EventRequestBothEdges = EventRequestRisingEdge | EventRequestFallingEdge 547 | ) 548 | 549 | // IsRisingEdge returns true if rising edge events have been requested. 550 | func (f EventFlag) IsRisingEdge() bool { 551 | return f&EventRequestRisingEdge != 0 552 | } 553 | 554 | // IsFallingEdge returns true if falling edge events have been requested. 555 | func (f EventFlag) IsFallingEdge() bool { 556 | return f&EventRequestFallingEdge != 0 557 | } 558 | 559 | // IsBothEdges returns true if both rising and falling edge events have been 560 | // requested. 561 | func (f EventFlag) IsBothEdges() bool { 562 | return f&EventRequestBothEdges == EventRequestBothEdges 563 | } 564 | 565 | // KernelVersion returns the running kernel version. 566 | func KernelVersion() (semver []byte, err error) { 567 | uname := unix.Utsname{} 568 | err = unix.Uname(&uname) 569 | if err != nil { 570 | return 571 | } 572 | release := string(uname.Release[:]) 573 | r := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`) 574 | vers := r.FindStringSubmatch(release) 575 | if len(vers) != 4 { 576 | err = fmt.Errorf("can't parse uname: %s", release) 577 | return 578 | } 579 | v := []byte{0, 0, 0} 580 | for i, vf := range vers[1:] { 581 | var vfi uint64 582 | vfi, err = strconv.ParseUint(vf, 10, 64) 583 | if err != nil { 584 | return 585 | } 586 | v[i] = byte(vfi) 587 | } 588 | semver = v 589 | return 590 | } 591 | 592 | // CheckKernelVersion returns an error if the kernel version is less than the 593 | // min. 594 | func CheckKernelVersion(min Semver) error { 595 | kv, err := KernelVersion() 596 | if err != nil { 597 | return err 598 | } 599 | n := bytes.Compare(kv, min[:]) 600 | if n < 0 { 601 | return ErrorBadVersion{Need: min, Have: kv} 602 | } 603 | return nil 604 | } 605 | 606 | // Semver is 3 part version, Major, Minor, Patch. 607 | type Semver []byte 608 | 609 | func (v Semver) String() string { 610 | if len(v) == 0 { 611 | return "" 612 | } 613 | vstr := fmt.Sprintf("%d", v[0]) 614 | for i := 1; i < len(v); i++ { 615 | vstr += fmt.Sprintf(".%d", v[i]) 616 | } 617 | return vstr 618 | } 619 | 620 | // ErrorBadVersion indicates the kernel version is insufficient. 621 | type ErrorBadVersion struct { 622 | Need Semver 623 | Have Semver 624 | } 625 | 626 | func (e ErrorBadVersion) Error() string { 627 | return fmt.Sprintf("require kernel %s or later, but running %s", e.Need, e.Have) 628 | } 629 | -------------------------------------------------------------------------------- /uapi/uapi_bmark_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright © 2020 Kent Gibson . 4 | 5 | //go:build linux 6 | 7 | package uapi_test 8 | 9 | import ( 10 | "os" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | "github.com/warthog618/go-gpiocdev/uapi" 15 | "github.com/warthog618/go-gpiosim" 16 | "golang.org/x/sys/unix" 17 | ) 18 | 19 | func BenchmarkChipOpenClose(b *testing.B) { 20 | s, err := gpiosim.NewSimpleton(4) 21 | require.Nil(b, err) 22 | defer s.Close() 23 | for i := 0; i < b.N; i++ { 24 | f, _ := os.Open(s.DevPath()) 25 | f.Close() 26 | } 27 | } 28 | 29 | func BenchmarkLineInfo(b *testing.B) { 30 | s, err := gpiosim.NewSimpleton(4) 31 | require.Nil(b, err) 32 | defer s.Close() 33 | f, err := os.Open(s.DevPath()) 34 | require.Nil(b, err) 35 | require.NotNil(b, f) 36 | defer f.Close() 37 | for i := 0; i < b.N; i++ { 38 | uapi.GetLineInfo(f.Fd(), 0) 39 | } 40 | } 41 | 42 | func BenchmarkGetLineHandle(b *testing.B) { 43 | s, err := gpiosim.NewSimpleton(4) 44 | require.Nil(b, err) 45 | defer s.Close() 46 | f, err := os.Open(s.DevPath()) 47 | require.Nil(b, err) 48 | require.NotNil(b, f) 49 | defer f.Close() 50 | hr := uapi.HandleRequest{Lines: 1} 51 | for i := 0; i < b.N; i++ { 52 | uapi.GetLineHandle(f.Fd(), &hr) 53 | unix.Close(int(hr.Fd)) 54 | } 55 | } 56 | 57 | func BenchmarkGetLineEvent(b *testing.B) { 58 | s, err := gpiosim.NewSimpleton(4) 59 | require.Nil(b, err) 60 | defer s.Close() 61 | f, err := os.Open(s.DevPath()) 62 | require.Nil(b, err) 63 | require.NotNil(b, f) 64 | defer f.Close() 65 | er := uapi.EventRequest{ 66 | HandleFlags: uapi.HandleRequestInput, 67 | EventFlags: uapi.EventRequestBothEdges, 68 | } 69 | for i := 0; i < b.N; i++ { 70 | uapi.GetLineEvent(f.Fd(), &er) 71 | unix.Close(int(er.Fd)) 72 | } 73 | } 74 | 75 | func BenchmarkGetLineValues(b *testing.B) { 76 | s, err := gpiosim.NewSimpleton(4) 77 | require.Nil(b, err) 78 | defer s.Close() 79 | f, err := os.Open(s.DevPath()) 80 | require.Nil(b, err) 81 | require.NotNil(b, f) 82 | defer f.Close() 83 | hr := uapi.HandleRequest{Lines: 1} 84 | err = uapi.GetLineHandle(f.Fd(), &hr) 85 | require.Nil(b, err) 86 | require.NotNil(b, f) 87 | defer unix.Close(int(hr.Fd)) 88 | var hd uapi.HandleData 89 | for i := 0; i < b.N; i++ { 90 | uapi.GetLineValues(uintptr(hr.Fd), &hd) 91 | } 92 | } 93 | 94 | func BenchmarkSetLineValues(b *testing.B) { 95 | s, err := gpiosim.NewSimpleton(4) 96 | require.Nil(b, err) 97 | defer s.Close() 98 | f, err := os.Open(s.DevPath()) 99 | require.Nil(b, err) 100 | require.NotNil(b, f) 101 | defer f.Close() 102 | hr := uapi.HandleRequest{Lines: 1, Flags: uapi.HandleRequestOutput} 103 | err = uapi.GetLineHandle(f.Fd(), &hr) 104 | require.Nil(b, err) 105 | require.NotNil(b, f) 106 | defer unix.Close(int(hr.Fd)) 107 | var hd uapi.HandleData 108 | for i := 0; i < b.N; i++ { 109 | uapi.SetLineValues(uintptr(hr.Fd), hd) 110 | } 111 | } 112 | 113 | func BenchmarkSetLineConfig(b *testing.B) { 114 | s, err := gpiosim.NewSimpleton(4) 115 | require.Nil(b, err) 116 | defer s.Close() 117 | f, err := os.Open(s.DevPath()) 118 | require.Nil(b, err) 119 | require.NotNil(b, f) 120 | defer f.Close() 121 | hr := uapi.HandleRequest{Lines: 1} 122 | err = uapi.GetLineHandle(f.Fd(), &hr) 123 | require.Nil(b, err) 124 | require.NotNil(b, f) 125 | defer unix.Close(int(hr.Fd)) 126 | var hc uapi.HandleConfig 127 | for i := 0; i < b.N; i++ { 128 | uapi.SetLineConfig(uintptr(hr.Fd), &hc) 129 | } 130 | } 131 | 132 | func BenchmarkWatchLineInfo(b *testing.B) { 133 | s, err := gpiosim.NewSimpleton(4) 134 | require.Nil(b, err) 135 | defer s.Close() 136 | f, err := os.Open(s.DevPath()) 137 | require.Nil(b, err) 138 | require.NotNil(b, f) 139 | defer f.Close() 140 | var li uapi.LineInfo 141 | for i := 0; i < b.N; i++ { 142 | uapi.WatchLineInfo(f.Fd(), &li) 143 | uapi.UnwatchLineInfo(f.Fd(), 0) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /uapi/uapi_v2.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | package uapi 8 | 9 | import ( 10 | "encoding/binary" 11 | "time" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/unix" 15 | ) 16 | 17 | // GetLineInfoV2 returns the LineInfoV2 for one line from the GPIO character device. 18 | // 19 | // The fd is an open GPIO character device. 20 | // The offset is zero based. 21 | func GetLineInfoV2(fd uintptr, offset int) (LineInfoV2, error) { 22 | li := LineInfoV2{Offset: uint32(offset)} 23 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 24 | fd, 25 | uintptr(getLineInfoV2Ioctl), 26 | uintptr(unsafe.Pointer(&li))) 27 | if errno != 0 { 28 | return LineInfoV2{}, errno 29 | } 30 | return li, nil 31 | } 32 | 33 | // GetLine requests a line from the GPIO character device. 34 | // 35 | // The fd is an open GPIO character device. 36 | // The lines must not already be requested. 37 | // The flags in the request will be applied to all lines in the request. 38 | // If successful, the fd for the line is returned in the request.fd. 39 | func GetLine(fd uintptr, request *LineRequest) error { 40 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 41 | fd, 42 | uintptr(getLineIoctl), 43 | uintptr(unsafe.Pointer(request))) 44 | if errno != 0 { 45 | return errno 46 | } 47 | return nil 48 | } 49 | 50 | // GetLineValuesV2 returns the values of a set of requested lines. 51 | // 52 | // The fd is a requested line, as returned by GetLine. 53 | // 54 | // The values returned are the logical values, with inactive being 0. 55 | func GetLineValuesV2(fd uintptr, values *LineValues) error { 56 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 57 | fd, 58 | uintptr(getLineValuesV2Ioctl), 59 | uintptr(unsafe.Pointer(values))) 60 | if errno != 0 { 61 | return errno 62 | } 63 | return nil 64 | } 65 | 66 | // SetLineValuesV2 sets the values of a set of requested lines. 67 | // 68 | // The fd is a requested line, as returned by GetLine. 69 | func SetLineValuesV2(fd uintptr, values LineValues) error { 70 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 71 | fd, 72 | uintptr(setLineValuesV2Ioctl), 73 | uintptr(unsafe.Pointer(&values))) 74 | if errno != 0 { 75 | return errno 76 | } 77 | return nil 78 | } 79 | 80 | // SetLineConfigV2 sets the config of an existing handle request. 81 | // 82 | // The config flags in the request will be applied to all lines in the request. 83 | func SetLineConfigV2(fd uintptr, config *LineConfig) error { 84 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 85 | fd, 86 | uintptr(setLineConfigV2Ioctl), 87 | uintptr(unsafe.Pointer(config))) 88 | if errno != 0 { 89 | return errno 90 | } 91 | return nil 92 | } 93 | 94 | // WatchLineInfoV2 sets a watch on info of a line. 95 | // 96 | // A watch is set on the line indicated by info.Offset. If successful the 97 | // current line info is returned, else an error is returned. 98 | func WatchLineInfoV2(fd uintptr, info *LineInfoV2) error { 99 | _, _, errno := unix.Syscall(unix.SYS_IOCTL, 100 | fd, 101 | uintptr(watchLineInfoV2Ioctl), 102 | uintptr(unsafe.Pointer(info))) 103 | if errno != 0 { 104 | return errno 105 | } 106 | return nil 107 | } 108 | 109 | // ReadLineEvent reads a single event from a requested line. 110 | // 111 | // The fd is a requested line, as returned by GetLine. 112 | // 113 | // This function is blocking and should only be called when the fd is known to 114 | // be ready to read. 115 | func ReadLineEvent(fd uintptr) (LineEvent, error) { 116 | var le LineEvent 117 | err := binary.Read(fdReader(fd), nativeEndian, &le) 118 | return le, err 119 | } 120 | 121 | // ReadLineInfoChangedV2 reads a line info changed event from a chip. 122 | // 123 | // The fd is an open GPIO character device. 124 | // 125 | // This function is blocking and should only be called when the fd is known to 126 | // be ready to read. 127 | func ReadLineInfoChangedV2(fd uintptr) (LineInfoChangedV2, error) { 128 | var lic LineInfoChangedV2 129 | err := binary.Read(fdReader(fd), nativeEndian, &lic) 130 | return lic, err 131 | } 132 | 133 | var ( 134 | getLineInfoV2Ioctl ioctl 135 | getLineIoctl ioctl 136 | getLineValuesV2Ioctl ioctl 137 | setLineValuesV2Ioctl ioctl 138 | setLineConfigV2Ioctl ioctl 139 | watchLineInfoV2Ioctl ioctl 140 | ) 141 | 142 | func init() { 143 | // ioctls require struct sizes which are only available at runtime. 144 | var liv2 LineInfoV2 145 | getLineInfoV2Ioctl = iorw(0xB4, 0x05, unsafe.Sizeof(liv2)) 146 | watchLineInfoV2Ioctl = iorw(0xB4, 0x06, unsafe.Sizeof(liv2)) 147 | var lr LineRequest 148 | getLineIoctl = iorw(0xB4, 0x07, unsafe.Sizeof(lr)) 149 | var lc LineConfig 150 | setLineConfigV2Ioctl = iorw(0xB4, 0x0D, unsafe.Sizeof(lc)) 151 | var lv LineValues 152 | getLineValuesV2Ioctl = iorw(0xB4, 0x0E, unsafe.Sizeof(lv)) 153 | setLineValuesV2Ioctl = iorw(0xB4, 0x0F, unsafe.Sizeof(lv)) 154 | } 155 | 156 | // LineInfoV2 contains the details of a single line of a GPIO chip. 157 | type LineInfoV2 struct { 158 | // The system name for this line. 159 | Name [nameSize]byte 160 | 161 | // If requested, a string added by the requester to identify the 162 | // owner of the request. 163 | Consumer [nameSize]byte 164 | 165 | // The offset of the line within the chip. 166 | Offset uint32 167 | 168 | NumAttrs uint32 169 | 170 | Flags LineFlagV2 171 | 172 | Attrs [10]LineAttribute 173 | 174 | // reserved for future use. 175 | Padding [lineInfoV2PadSize]uint32 176 | } 177 | 178 | // LineInfoChangedV2 contains the details of a change to line info. 179 | // 180 | // This is returned via the chip fd in response to changes to watched lines. 181 | type LineInfoChangedV2 struct { 182 | // The updated info. 183 | Info LineInfoV2 184 | 185 | // The time the change occurred. 186 | Timestamp uint64 187 | 188 | // The type of change. 189 | Type ChangeType 190 | 191 | // reserved for future use. 192 | Padding [lineInfoChangedV2PadSize]uint32 193 | } 194 | 195 | // LineFlagV2 are the flags for a line. 196 | type LineFlagV2 uint64 197 | 198 | const ( 199 | // LineFlagV2Used indicates that the line is already in use. 200 | // It may have been requested by this process or another process, 201 | // or may be reserved by the kernel. 202 | // 203 | // The line cannot be requested until this flag is clear. 204 | LineFlagV2Used LineFlagV2 = 1 << iota 205 | 206 | // LineFlagV2ActiveLow indicates that the line is active low. 207 | LineFlagV2ActiveLow 208 | 209 | // LineFlagV2Input indicates that the line direction is an input. 210 | LineFlagV2Input 211 | 212 | // LineFlagV2Output indicates that the line direction is an output. 213 | LineFlagV2Output 214 | 215 | // LineFlagV2EdgeRising indicates that edge detection is enabled for rising 216 | // edges. 217 | LineFlagV2EdgeRising 218 | 219 | // LineFlagV2EdgeFalling indicates that edge detection is enabled for 220 | // falling edges. 221 | LineFlagV2EdgeFalling 222 | 223 | // LineFlagV2OpenDrain indicates that the line drive is open drain. 224 | LineFlagV2OpenDrain 225 | 226 | // LineFlagV2OpenSource indicates that the line drive is open source. 227 | LineFlagV2OpenSource 228 | 229 | // LineFlagV2BiasPullUp indicates that the line bias is pull-up. 230 | LineFlagV2BiasPullUp 231 | 232 | // LineFlagV2BiasPullDown indicates that the line bias is set pull-down. 233 | LineFlagV2BiasPullDown 234 | 235 | // LineFlagV2BiasDisabled indicates that the line bias is disabled. 236 | LineFlagV2BiasDisabled 237 | 238 | // LineFlagV2EventClockRealtime indicates that the CLOCK_REALTIME will be 239 | // the source for event timestamps. 240 | LineFlagV2EventClockRealtime 241 | 242 | // LineFlagV2DirectionMask is a mask for all direction flags. 243 | LineFlagV2DirectionMask = LineFlagV2Input | LineFlagV2Output 244 | 245 | // LineFlagV2EdgeMask is a mask for all edge flags. 246 | LineFlagV2EdgeMask = LineFlagV2EdgeRising | LineFlagV2EdgeFalling 247 | 248 | // LineFlagV2EdgeBoth is a helper value for selecting edge detection on 249 | // both edges. 250 | LineFlagV2EdgeBoth = LineFlagV2EdgeMask 251 | 252 | // LineFlagV2DriveMask is a mask for all drive flags. 253 | LineFlagV2DriveMask = LineFlagV2OpenDrain | LineFlagV2OpenSource 254 | 255 | // LineFlagV2BiasMask is a mask for all bias flags. 256 | LineFlagV2BiasMask = LineFlagV2BiasDisabled | LineFlagV2BiasPullUp | LineFlagV2BiasPullDown 257 | ) 258 | 259 | // IsAvailable returns true if the line is available to be requested. 260 | func (f LineFlagV2) IsAvailable() bool { 261 | return f&LineFlagV2Used == 0 262 | } 263 | 264 | // IsUsed returns true if the line is not available to be requested. 265 | func (f LineFlagV2) IsUsed() bool { 266 | return f&LineFlagV2Used != 0 267 | } 268 | 269 | // IsActiveLow returns true if the line is active low. 270 | func (f LineFlagV2) IsActiveLow() bool { 271 | return f&LineFlagV2ActiveLow != 0 272 | } 273 | 274 | // IsInput returns true if the line is an input. 275 | func (f LineFlagV2) IsInput() bool { 276 | return f&LineFlagV2Input != 0 277 | } 278 | 279 | // IsOutput returns true if the line is an output. 280 | func (f LineFlagV2) IsOutput() bool { 281 | return f&LineFlagV2Output != 0 282 | } 283 | 284 | // IsOpenDrain returns true if the line is an open drain. 285 | func (f LineFlagV2) IsOpenDrain() bool { 286 | return f&LineFlagV2OpenDrain != 0 287 | } 288 | 289 | // IsOpenSource returns true if the line is an open source. 290 | func (f LineFlagV2) IsOpenSource() bool { 291 | return f&LineFlagV2OpenSource != 0 292 | } 293 | 294 | // IsRisingEdge returns true if the line has edge detection on the rising edge. 295 | func (f LineFlagV2) IsRisingEdge() bool { 296 | return f&LineFlagV2EdgeRising != 0 297 | } 298 | 299 | // IsFallingEdge returns true if the line has edge detection on the falling edge. 300 | func (f LineFlagV2) IsFallingEdge() bool { 301 | return f&LineFlagV2EdgeFalling != 0 302 | } 303 | 304 | // IsBothEdges returns true if the line has edge detection on both edges. 305 | func (f LineFlagV2) IsBothEdges() bool { 306 | return f&LineFlagV2EdgeBoth == LineFlagV2EdgeBoth 307 | } 308 | 309 | // IsBiasDisabled returns true if the line has bias disabled. 310 | func (f LineFlagV2) IsBiasDisabled() bool { 311 | return f&LineFlagV2BiasDisabled != 0 312 | } 313 | 314 | // IsBiasPullUp returns true if the line has pull-up bias enabled. 315 | func (f LineFlagV2) IsBiasPullUp() bool { 316 | return f&LineFlagV2BiasPullUp != 0 317 | } 318 | 319 | // IsBiasPullDown returns true if the line has pull-down bias enabled. 320 | func (f LineFlagV2) IsBiasPullDown() bool { 321 | return f&LineFlagV2BiasPullDown != 0 322 | } 323 | 324 | // HasRealtimeEventClock returns true if the line events will contain real-time 325 | // timestamps. 326 | func (f LineFlagV2) HasRealtimeEventClock() bool { 327 | return f&LineFlagV2EventClockRealtime != 0 328 | } 329 | 330 | // Encode creates a LineAttribute with the value from the LineFlagV2. 331 | func (f LineFlagV2) Encode() (la LineAttribute) { 332 | la.Encode64(LineAttributeIDFlags, uint64(f)) 333 | return 334 | } 335 | 336 | // Decode populates the LineFlagV2 with value from the LineAttribute. 337 | func (f *LineFlagV2) Decode(la LineAttribute) { 338 | *f = LineFlagV2(la.Value64()) 339 | } 340 | 341 | const ( 342 | // LinesMax is the maximum number of lines that can be requested in a single 343 | // request. 344 | LinesMax int = 64 345 | 346 | // the pad sizes of each struct 347 | lineConfigPadSize int = 5 348 | lineRequestPadSize int = 5 349 | lineEventPadSize int = 6 350 | lineInfoV2PadSize int = 4 351 | lineInfoChangedV2PadSize int = 5 352 | ) 353 | 354 | // LineAttribute defines a configuration attribute for a line. 355 | type LineAttribute struct { 356 | ID LineAttributeID 357 | 358 | Padding [1]uint32 359 | 360 | Value [8]byte 361 | } 362 | 363 | // Encode32 populates the LineAttribute using the id and 32-bit value. 364 | func (la *LineAttribute) Encode32(id LineAttributeID, value uint32) { 365 | la.ID = id 366 | nativeEndian.PutUint32(la.Value[:], value) 367 | } 368 | 369 | // Encode64 populates the LineAttribute using the id and 64-bit value. 370 | func (la *LineAttribute) Encode64(id LineAttributeID, value uint64) { 371 | la.ID = id 372 | nativeEndian.PutUint64(la.Value[:], value) 373 | } 374 | 375 | // Value32 returns the 32-bit value from the LineAttribute. 376 | func (la LineAttribute) Value32() uint32 { 377 | return nativeEndian.Uint32(la.Value[:]) 378 | } 379 | 380 | // Value64 returns the 64-bit value from the LineAttribute. 381 | func (la LineAttribute) Value64() uint64 { 382 | return nativeEndian.Uint64(la.Value[:]) 383 | } 384 | 385 | // LineAttributeID identifies the type of a configuration attribute. 386 | type LineAttributeID uint32 387 | 388 | const ( 389 | // LineAttributeIDFlags indicates the attribute contains LineFlagV2 flags. 390 | LineAttributeIDFlags LineAttributeID = iota + 1 391 | 392 | // LineAttributeIDOutputValues indicates the attribute contains line output values. 393 | LineAttributeIDOutputValues 394 | 395 | // LineAttributeIDDebounce indicates the attribute contains a debounce period. 396 | LineAttributeIDDebounce 397 | ) 398 | 399 | // DebouncePeriod specifies the time the line must be stable before a level 400 | // transition is recognized. 401 | type DebouncePeriod time.Duration 402 | 403 | // Encode creates a LineAttribute with the value from the DebouncePeriod. 404 | func (d DebouncePeriod) Encode() (la LineAttribute) { 405 | la.Encode32(LineAttributeIDDebounce, uint32(d/1000)) 406 | return 407 | } 408 | 409 | // Decode populates the DebouncePeriod with value from the LineAttribute. 410 | func (d *DebouncePeriod) Decode(la LineAttribute) { 411 | *d = DebouncePeriod(la.Value32() * 1000) 412 | } 413 | 414 | // OutputValues specify the active level of output lines. 415 | type OutputValues LineBitmap 416 | 417 | // Encode creates a LineAttribute with the values from the OutputValues. 418 | func (ov OutputValues) Encode() (la LineAttribute) { 419 | la.Encode64(LineAttributeIDOutputValues, uint64(ov)) 420 | return 421 | } 422 | 423 | // Decode populates the OutputValues with values from the LineAttribute. 424 | func (ov *OutputValues) Decode(la LineAttribute) { 425 | *ov = OutputValues(la.Value64()) 426 | } 427 | 428 | // LineConfigAttribute associates a configuration attribute with one or more 429 | // requested lines. 430 | type LineConfigAttribute struct { 431 | // Attr contains the configuration attribute. 432 | Attr LineAttribute 433 | 434 | // Mask identifies the lines to which this attribute applies. 435 | // 436 | // This is a bitmap of lines in LineRequest.Offsets. 437 | Mask LineBitmap 438 | } 439 | 440 | // LineConfig contains the configuration of a line. 441 | type LineConfig struct { 442 | // The flags to be applied to the lines. 443 | Flags LineFlagV2 444 | 445 | NumAttrs uint32 446 | 447 | // reserved for future use. 448 | Padding [lineConfigPadSize]uint32 449 | 450 | Attrs [10]LineConfigAttribute 451 | } 452 | 453 | // AddAttribute adds an attribute to the configuration. 454 | // 455 | // This is an unconditional add - it performs no filtering or consistency 456 | // checking other than limiting the number of attributes. 457 | func (lc *LineConfig) AddAttribute(lca LineConfigAttribute) { 458 | if lc.NumAttrs < 10 { 459 | lc.Attrs[lc.NumAttrs] = lca 460 | lc.NumAttrs++ 461 | } 462 | } 463 | 464 | // RemoveAttribute removes an attribute from the configuration. 465 | func (lc *LineConfig) RemoveAttribute(lca LineConfigAttribute) { 466 | d := 0 467 | for s := 0; s < int(lc.NumAttrs); s++ { 468 | if lc.Attrs[s] != lca { 469 | if d != s { 470 | lc.Attrs[d] = lc.Attrs[s] 471 | } 472 | d++ 473 | } 474 | } 475 | lc.NumAttrs = uint32(d) 476 | } 477 | 478 | // RemoveAttributeID removes all attributes with a given ID from the configuration. 479 | func (lc *LineConfig) RemoveAttributeID(id LineAttributeID) { 480 | d := 0 481 | for s := 0; s < int(lc.NumAttrs); s++ { 482 | if lc.Attrs[s].Attr.ID != id { 483 | if d != s { 484 | lc.Attrs[d] = lc.Attrs[s] 485 | } 486 | d++ 487 | } 488 | } 489 | lc.NumAttrs = uint32(d) 490 | } 491 | 492 | // LineRequest is a request for control of a set of lines. 493 | // The lines must all be on the same GPIO chip. 494 | type LineRequest struct { 495 | // The lines to be requested. 496 | Offsets [LinesMax]uint32 497 | 498 | // The string identifying the requester to be applied to the lines. 499 | Consumer [nameSize]byte 500 | 501 | // The configuration for the requested lines 502 | Config LineConfig 503 | 504 | // The number of lines being requested. 505 | Lines uint32 506 | 507 | // Minimum size of the event buffer. 508 | EventBufferSize uint32 509 | 510 | // reserved for future use. 511 | Padding [lineRequestPadSize]uint32 512 | 513 | // The file handle for the requested lines. 514 | // Set if the request is successful. 515 | Fd int32 516 | } 517 | 518 | // LineBitmap is a bitmap containing a bit for each line. 519 | type LineBitmap uint64 520 | 521 | // NewLineBits creates a new LineBitmap from an array of bit numbers. 522 | func NewLineBits(vv ...int) LineBitmap { 523 | var lb LineBitmap 524 | for _, bit := range vv { 525 | lb = lb.Set(bit, 1) 526 | } 527 | return lb 528 | } 529 | 530 | // NewLineBitmap creates a bitmap from an array of bit values. 531 | func NewLineBitmap(vv ...int) LineBitmap { 532 | var lb LineBitmap 533 | for i, v := range vv { 534 | lb = lb.Set(i, v) 535 | } 536 | return lb 537 | } 538 | 539 | // NewLineBitMask returns a mask of n bits. 540 | func NewLineBitMask(n int) LineBitmap { 541 | if n >= LinesMax { 542 | n = LinesMax 543 | } 544 | if n == LinesMax { 545 | return 0xffffffffffffffff 546 | } 547 | return (LineBitmap(1) << uint(n)) - 1 548 | } 549 | 550 | // Get returns the value of the nth bit. 551 | func (lb LineBitmap) Get(n int) int { 552 | mask := LineBitmap(1) << uint(n) 553 | if lb&mask != 0 { 554 | return 1 555 | } 556 | return 0 557 | } 558 | 559 | // Set sets the value of the nth bit. 560 | func (lb LineBitmap) Set(n, v int) LineBitmap { 561 | mask := LineBitmap(1) << uint(n) 562 | if v == 0 { 563 | return lb &^ mask 564 | } 565 | return lb | mask 566 | } 567 | 568 | // LineValues contains the output values for a set of lines. 569 | type LineValues struct { 570 | // Bits contains the logical value of the the lines. 571 | // 572 | // Zero is a logical low (inactive) and 1 is a logical high (active). 573 | // 574 | // This is a bitmap of lines in LineRequest.Offsets. 575 | Bits LineBitmap 576 | 577 | // Mask identifies the lines to which this attribute applies. 578 | // 579 | // This is a bitmap of lines in LineRequest.Offsets. 580 | Mask LineBitmap 581 | } 582 | 583 | // Get returns the value of the nth bit. 584 | func (lv LineValues) Get(n int) int { 585 | mask := LineBitmap(1) << uint(n) 586 | if lv.Bits&mask != 0 { 587 | return 1 588 | } 589 | return 0 590 | } 591 | 592 | // LineEventID indicates the type of event detected. 593 | type LineEventID uint32 594 | 595 | const ( 596 | // LineEventRisingEdge indicates the event is a rising edge. 597 | LineEventRisingEdge LineEventID = iota + 1 598 | 599 | // LineEventFallingEdge indicates the event is a falling edge. 600 | LineEventFallingEdge 601 | ) 602 | 603 | // LineEvent contains the details of a particular line event. 604 | // 605 | // This is returned via the event request fd in response to events. 606 | type LineEvent struct { 607 | // The time the event was detected. 608 | Timestamp uint64 609 | 610 | // The type of event detected. 611 | ID LineEventID 612 | 613 | // The line that triggered the event. 614 | Offset uint32 615 | 616 | // The sequence number for this event in all events on all lines in this line request. 617 | Seqno uint32 618 | 619 | // The sequence number for this event in all events in this line. 620 | LineSeqno uint32 621 | 622 | // reserved for future use 623 | Padding [lineEventPadSize]uint32 624 | } 625 | -------------------------------------------------------------------------------- /uapi/uapi_v2_bmark_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | //go:build linux 6 | 7 | package uapi_test 8 | 9 | import ( 10 | "os" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | "github.com/warthog618/go-gpiocdev/uapi" 15 | "github.com/warthog618/go-gpiosim" 16 | "golang.org/x/sys/unix" 17 | ) 18 | 19 | func BenchmarkLineInfoV2(b *testing.B) { 20 | s, err := gpiosim.NewSimpleton(4) 21 | require.Nil(b, err) 22 | defer s.Close() 23 | f, err := os.Open(s.DevPath()) 24 | require.Nil(b, err) 25 | require.NotNil(b, f) 26 | defer f.Close() 27 | for i := 0; i < b.N; i++ { 28 | uapi.GetLineInfoV2(f.Fd(), 0) 29 | } 30 | } 31 | 32 | func BenchmarkGetLine(b *testing.B) { 33 | s, err := gpiosim.NewSimpleton(4) 34 | require.Nil(b, err) 35 | defer s.Close() 36 | f, err := os.Open(s.DevPath()) 37 | require.Nil(b, err) 38 | require.NotNil(b, f) 39 | defer f.Close() 40 | lr := uapi.LineRequest{ 41 | Lines: 1, 42 | } 43 | for i := 0; i < b.N; i++ { 44 | uapi.GetLine(f.Fd(), &lr) 45 | unix.Close(int(lr.Fd)) 46 | } 47 | } 48 | 49 | func BenchmarkGetLineWithEdges(b *testing.B) { 50 | s, err := gpiosim.NewSimpleton(4) 51 | require.Nil(b, err) 52 | defer s.Close() 53 | f, err := os.Open(s.DevPath()) 54 | require.Nil(b, err) 55 | require.NotNil(b, f) 56 | defer f.Close() 57 | lr := uapi.LineRequest{ 58 | Lines: 1, 59 | Config: uapi.LineConfig{ 60 | Flags: uapi.LineFlagV2Input | uapi.LineFlagV2EdgeBoth, 61 | }, 62 | } 63 | for i := 0; i < b.N; i++ { 64 | uapi.GetLine(f.Fd(), &lr) 65 | unix.Close(int(lr.Fd)) 66 | } 67 | } 68 | 69 | func BenchmarkGetLineValuesV2(b *testing.B) { 70 | s, err := gpiosim.NewSimpleton(4) 71 | require.Nil(b, err) 72 | defer s.Close() 73 | f, err := os.Open(s.DevPath()) 74 | require.Nil(b, err) 75 | require.NotNil(b, f) 76 | defer f.Close() 77 | lr := uapi.LineRequest{Lines: 1} 78 | err = uapi.GetLine(f.Fd(), &lr) 79 | require.Nil(b, err) 80 | require.NotNil(b, f) 81 | defer unix.Close(int(lr.Fd)) 82 | lv := uapi.LineValues{Mask: 1} 83 | for i := 0; i < b.N; i++ { 84 | uapi.GetLineValuesV2(uintptr(lr.Fd), &lv) 85 | } 86 | } 87 | 88 | func BenchmarkSetLineValuesV2(b *testing.B) { 89 | s, err := gpiosim.NewSimpleton(4) 90 | require.Nil(b, err) 91 | defer s.Close() 92 | f, err := os.Open(s.DevPath()) 93 | require.Nil(b, err) 94 | require.NotNil(b, f) 95 | defer f.Close() 96 | lr := uapi.LineRequest{ 97 | Lines: 1, 98 | Config: uapi.LineConfig{ 99 | Flags: uapi.LineFlagV2Output, 100 | }, 101 | } 102 | err = uapi.GetLine(f.Fd(), &lr) 103 | require.Nil(b, err) 104 | require.NotNil(b, f) 105 | defer unix.Close(int(lr.Fd)) 106 | lv := uapi.LineValues{Mask: 1} 107 | for i := 0; i < b.N; i++ { 108 | uapi.SetLineValuesV2(uintptr(lr.Fd), lv) 109 | } 110 | } 111 | 112 | func BenchmarkSetLineValuesV2Sparse(b *testing.B) { 113 | s, err := gpiosim.NewSimpleton(4) 114 | require.Nil(b, err) 115 | defer s.Close() 116 | f, err := os.Open(s.DevPath()) 117 | require.Nil(b, err) 118 | require.NotNil(b, f) 119 | defer f.Close() 120 | lr := uapi.LineRequest{ 121 | Lines: 4, 122 | Offsets: [uapi.LinesMax]uint32{0, 1, 2, 3}, 123 | Config: uapi.LineConfig{ 124 | Flags: uapi.LineFlagV2Output, 125 | }, 126 | } 127 | err = uapi.GetLine(f.Fd(), &lr) 128 | require.Nil(b, err) 129 | require.NotNil(b, f) 130 | defer unix.Close(int(lr.Fd)) 131 | lv := uapi.LineValues{Mask: 0x0a} 132 | for i := 0; i < b.N; i++ { 133 | uapi.SetLineValuesV2(uintptr(lr.Fd), lv) 134 | } 135 | } 136 | 137 | func BenchmarkSetLineConfigV2(b *testing.B) { 138 | s, err := gpiosim.NewSimpleton(4) 139 | require.Nil(b, err) 140 | defer s.Close() 141 | f, err := os.Open(s.DevPath()) 142 | require.Nil(b, err) 143 | require.NotNil(b, f) 144 | defer f.Close() 145 | lr := uapi.LineRequest{Lines: 1} 146 | err = uapi.GetLine(f.Fd(), &lr) 147 | require.Nil(b, err) 148 | require.NotNil(b, f) 149 | defer unix.Close(int(lr.Fd)) 150 | var lc uapi.LineConfig 151 | for i := 0; i < b.N; i++ { 152 | uapi.SetLineConfigV2(uintptr(lr.Fd), &lc) 153 | } 154 | } 155 | 156 | func BenchmarkWatchLineInfoV2(b *testing.B) { 157 | s, err := gpiosim.NewSimpleton(4) 158 | require.Nil(b, err) 159 | defer s.Close() 160 | f, err := os.Open(s.DevPath()) 161 | require.Nil(b, err) 162 | require.NotNil(b, f) 163 | defer f.Close() 164 | var li uapi.LineInfoV2 165 | for i := 0; i < b.N; i++ { 166 | uapi.WatchLineInfoV2(f.Fd(), &li) 167 | uapi.UnwatchLineInfo(f.Fd(), 0) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /watcher.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2019 Kent Gibson 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | package gpiocdev 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | 11 | "github.com/warthog618/go-gpiocdev/uapi" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | type watcher struct { 16 | epfd int 17 | 18 | // eventfd to signal watcher to shutdown 19 | donefd int 20 | 21 | // the handler for detected events 22 | eh EventHandler 23 | 24 | // closed once watcher exits 25 | doneCh chan struct{} 26 | } 27 | 28 | func newWatcher(fd int32, eh EventHandler) (w *watcher, err error) { 29 | var epfd, donefd int 30 | epfd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC) 31 | if err != nil { 32 | return 33 | } 34 | defer func() { 35 | if err != nil { 36 | unix.Close(epfd) 37 | } 38 | }() 39 | donefd, err = unix.Eventfd(0, unix.EFD_CLOEXEC) 40 | if err != nil { 41 | return 42 | } 43 | defer func() { 44 | if err != nil { 45 | unix.Close(donefd) 46 | } 47 | }() 48 | epv := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(donefd)} 49 | err = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(donefd), &epv) 50 | if err != nil { 51 | return 52 | } 53 | epv.Fd = int32(fd) 54 | err = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(fd), &epv) 55 | if err != nil { 56 | return 57 | } 58 | w = &watcher{ 59 | epfd: epfd, 60 | donefd: donefd, 61 | eh: eh, 62 | doneCh: make(chan struct{}), 63 | } 64 | go w.watch() 65 | return 66 | } 67 | 68 | func (w *watcher) Close() error { 69 | unix.Write(w.donefd, []byte{1, 0, 0, 0, 0, 0, 0, 0}) 70 | <-w.doneCh 71 | unix.Close(w.donefd) 72 | return nil 73 | } 74 | 75 | func (w *watcher) watch() { 76 | epollEvents := make([]unix.EpollEvent, 2) 77 | defer close(w.doneCh) 78 | for { 79 | n, err := unix.EpollWait(w.epfd, epollEvents[:], -1) 80 | if err != nil { 81 | if err == unix.EBADF || err == unix.EINVAL { 82 | // fd closed so exit 83 | return 84 | } 85 | if err == unix.EINTR { 86 | continue 87 | } 88 | panic(fmt.Sprintf("EpollWait unexpected error: %v", err)) 89 | } 90 | for i := 0; i < n; i++ { 91 | ev := epollEvents[i] 92 | fd := ev.Fd 93 | if fd == int32(w.donefd) { 94 | unix.Close(w.epfd) 95 | return 96 | } 97 | evt, err := uapi.ReadLineEvent(uintptr(fd)) 98 | if err != nil { 99 | continue 100 | } 101 | le := LineEvent{ 102 | Offset: int(evt.Offset), 103 | Timestamp: time.Duration(evt.Timestamp), 104 | Type: LineEventType(evt.ID), 105 | Seqno: evt.Seqno, 106 | LineSeqno: evt.LineSeqno, 107 | } 108 | w.eh(le) 109 | } 110 | } 111 | } 112 | 113 | type watcherV1 struct { 114 | watcher 115 | 116 | // fd to offset mapping 117 | evtfds map[int]int 118 | } 119 | 120 | func newWatcherV1(fds map[int]int, eh EventHandler) (w *watcherV1, err error) { 121 | var epfd, donefd int 122 | epfd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC) 123 | if err != nil { 124 | return 125 | } 126 | defer func() { 127 | if err != nil { 128 | unix.Close(epfd) 129 | } 130 | }() 131 | donefd, err = unix.Eventfd(0, unix.EFD_CLOEXEC) 132 | if err != nil { 133 | return 134 | } 135 | defer func() { 136 | if err != nil { 137 | unix.Close(donefd) 138 | } 139 | }() 140 | epv := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(donefd)} 141 | err = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(donefd), &epv) 142 | if err != nil { 143 | return 144 | } 145 | for fd := range fds { 146 | epv.Fd = int32(fd) 147 | err = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, fd, &epv) 148 | if err != nil { 149 | return 150 | } 151 | } 152 | w = &watcherV1{ 153 | watcher: watcher{ 154 | epfd: epfd, 155 | donefd: donefd, 156 | eh: eh, 157 | doneCh: make(chan struct{}), 158 | }, 159 | evtfds: fds, 160 | } 161 | go w.watch() 162 | return 163 | } 164 | 165 | func (w *watcherV1) Close() error { 166 | unix.Write(w.donefd, []byte{1, 0, 0, 0, 0, 0, 0, 0}) 167 | <-w.doneCh 168 | for fd := range w.evtfds { 169 | unix.Close(fd) 170 | } 171 | unix.Close(w.donefd) 172 | return nil 173 | } 174 | 175 | func (w *watcherV1) watch() { 176 | epollEvents := make([]unix.EpollEvent, len(w.evtfds)) 177 | defer close(w.doneCh) 178 | for { 179 | n, err := unix.EpollWait(w.epfd, epollEvents[:], -1) 180 | if err != nil { 181 | if err == unix.EBADF || err == unix.EINVAL { 182 | // fd closed so exit 183 | return 184 | } 185 | if err == unix.EINTR { 186 | continue 187 | } 188 | panic(fmt.Sprintf("EpollWait unexpected error: %v", err)) 189 | } 190 | for i := 0; i < n; i++ { 191 | ev := epollEvents[i] 192 | fd := ev.Fd 193 | if fd == int32(w.donefd) { 194 | unix.Close(w.epfd) 195 | return 196 | } 197 | evt, err := uapi.ReadEvent(uintptr(fd)) 198 | if err != nil { 199 | continue 200 | } 201 | le := LineEvent{ 202 | Offset: w.evtfds[int(fd)], 203 | Timestamp: time.Duration(evt.Timestamp), 204 | Type: LineEventType(evt.ID), 205 | } 206 | w.eh(le) 207 | } 208 | } 209 | } 210 | --------------------------------------------------------------------------------