-
Notifications
You must be signed in to change notification settings - Fork 9
/
interrupt.go
325 lines (284 loc) · 7.19 KB
/
interrupt.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// Copyright © 2017 Kent Gibson <warthog618@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// Interrupt capabilities for DIO Pins.
//go:build linux
// +build linux
package gpio
import (
"errors"
"fmt"
"os"
"strconv"
"sync"
"time"
"golang.org/x/sys/unix"
)
const (
// MaxGPIOInterrupt is the maximum pin number.
MaxGPIOInterrupt = MaxGPIOPin
)
// Edge represents the change in Pin level that triggers an interrupt.
type Edge string
const (
// EdgeNone indicates no level transitions will trigger an interrupt
EdgeNone Edge = "none"
// EdgeRising indicates an interrupt is triggered when the pin transitions from low to high.
EdgeRising Edge = "rising"
// EdgeFalling indicates an interrupt is triggered when the pin transitions from high to low.
EdgeFalling Edge = "falling"
// EdgeBoth indicates an interrupt is triggered when the pin changes level.
EdgeBoth Edge = "both"
)
type interrupt struct {
pin *Pin
handler func(*Pin)
valueFile *os.File
}
// Watcher monitors the pins for level transitions that trigger interrupts.
type Watcher struct {
// Guards the following, and sysfs interactions.
sync.Mutex
epfd int
// Map from pin to value Fd.
interruptFds map[int]int
// Map from pin Fd to interrupt
interrupts map[int]*interrupt
// closed when the watcher exits.
doneCh chan struct{}
// fds of the pipe for the shutdown handshake.
donefds []int
// true once the Watcher has been closed.
closed bool
}
var defaultWatcher *Watcher
func getDefaultWatcher() *Watcher {
memlock.Lock()
if defaultWatcher == nil {
defaultWatcher = NewWatcher()
}
memlock.Unlock()
return defaultWatcher
}
// NewWatcher creates a goroutine that watches Pins for transitions that trigger
// interrupts.
func NewWatcher() *Watcher {
epfd, err := unix.EpollCreate1(0)
if err != nil {
panic(fmt.Sprintf("Unable to create epoll: %v", err))
}
p := []int{0, 0}
err = unix.Pipe2(p, unix.O_CLOEXEC)
if err != nil {
panic(fmt.Sprintf("Unable to create pipe: %v", err))
}
epv := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(p[0])}
unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(p[0]), &epv)
w := &Watcher{
epfd: epfd,
interruptFds: make(map[int]int),
interrupts: make(map[int]*interrupt),
doneCh: make(chan struct{}),
donefds: p,
}
go w.watch()
return w
}
func (w *Watcher) watch() {
var epollEvents [MaxGPIOInterrupt]unix.EpollEvent
defer close(w.doneCh)
for {
n, err := unix.EpollWait(w.epfd, epollEvents[:], -1)
if err != nil {
if err == unix.EBADF || err == unix.EINVAL {
// fd closed so exit
return
}
if err == unix.EINTR {
continue
}
panic(fmt.Sprintf("EpollWait error: %v", err))
}
for i := 0; i < n; i++ {
event := epollEvents[i]
if event.Fd == int32(w.donefds[0]) {
unix.Close(w.epfd)
unix.Close(w.donefds[0])
return
}
w.Lock()
irq, ok := w.interrupts[int(event.Fd)]
w.Unlock()
if ok {
go irq.handler(irq.pin)
}
}
}
}
func closeInterrupts() {
watcher := defaultWatcher
if watcher == nil {
return
}
defaultWatcher = nil
watcher.Close()
}
// Close - His watch has ended.
func (w *Watcher) Close() {
w.Lock()
if w.closed {
w.Unlock()
return
}
w.closed = true
unix.Write(w.donefds[1], []byte("bye"))
for fd := range w.interrupts {
intr := w.interrupts[fd]
intr.valueFile.Close()
unexport(intr.pin)
}
w.interrupts = nil
w.interruptFds = nil
w.Unlock()
<-w.doneCh
unix.Close(w.donefds[1])
}
// RegisterPin creates a watch on the given pin.
//
// The pin can only be registered once. Subsequent registers,
// without an Unregister, will return an error.
func (w *Watcher) RegisterPin(pin *Pin, edge Edge, handler func(*Pin)) (err error) {
w.Lock()
defer w.Unlock()
_, ok := w.interruptFds[pin.pin]
if ok {
return ErrBusy
}
if err = export(pin); err != nil {
return err
}
defer func() {
if err != nil {
unexport(pin)
}
}()
if err = setEdge(pin, edge); err != nil {
return err
}
valueFile, err := openValue(pin)
if err != nil {
return err
}
pinFd := int(valueFile.Fd())
event := unix.EpollEvent{Events: unix.EPOLLET & 0xffffffff}
if err = unix.SetNonblock(pinFd, true); err != nil {
return err
}
event.Fd = int32(pinFd)
if err := unix.EpollCtl(w.epfd, unix.EPOLL_CTL_ADD, pinFd, &event); err != nil {
return err
}
w.interruptFds[pin.pin] = pinFd
w.interrupts[pinFd] = &interrupt{pin: pin, handler: handler, valueFile: valueFile}
return nil
}
// UnregisterPin removes any watch on the Pin.
func (w *Watcher) UnregisterPin(pin *Pin) {
w.Lock()
defer w.Unlock()
pinFd, ok := w.interruptFds[pin.pin]
if !ok {
return
}
delete(w.interruptFds, pin.pin)
unix.EpollCtl(w.epfd, unix.EPOLL_CTL_DEL, pinFd, nil)
unix.SetNonblock(pinFd, false)
intr, ok := w.interrupts[pinFd]
if ok {
delete(w.interrupts, pinFd)
intr.valueFile.Close()
}
unexport(pin)
}
// Watch the pin for changes to level.
//
// The handler is called immediately, to allow the handler to initialise its state
// with the current level, and then on the specified edges.
// The edge determines which edge to watch.
// There can only be one watcher on the pin at a time.
func (p *Pin) Watch(edge Edge, handler func(*Pin)) error {
watcher := getDefaultWatcher()
return watcher.RegisterPin(p, edge, handler)
}
// Unwatch removes any watch from the pin.
func (p *Pin) Unwatch() {
watcher := getDefaultWatcher()
watcher.UnregisterPin(p)
}
func waitWriteable(path string) error {
try := 0
for unix.Access(path, unix.W_OK) != nil {
try++
if try > 10 {
return ErrTimeout
}
time.Sleep(50 * time.Millisecond)
}
return nil
}
func export(p *Pin) error {
file, err := os.OpenFile("/sys/class/gpio/export", os.O_WRONLY, os.ModeExclusive)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(strconv.Itoa(int(p.pin)))
if e, ok := err.(*os.PathError); ok && e.Err == unix.EBUSY {
return ErrBusy
}
if err != nil {
return err
}
// wait for pin to be exported on sysfs - can take > 100ms on older Pis
return waitExported(p)
}
func openValue(p *Pin) (*os.File, error) {
path := fmt.Sprintf("/sys/class/gpio/gpio%v/value", p.pin)
return os.OpenFile(path, os.O_RDWR, os.ModeExclusive)
}
func setEdge(p *Pin, edge Edge) error {
path := fmt.Sprintf("/sys/class/gpio/gpio%v/edge", p.pin)
file, err := os.OpenFile(path, os.O_RDWR, os.ModeExclusive)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write([]byte(edge))
return err
}
func unexport(p *Pin) error {
file, err := os.OpenFile("/sys/class/gpio/unexport", os.O_WRONLY, os.ModeExclusive)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(strconv.Itoa(int(p.pin)))
return err
}
// Wait for the sysfs GPIO files to become writable.
func waitExported(p *Pin) error {
path := fmt.Sprintf("/sys/class/gpio/gpio%v/value", p.pin)
if err := waitWriteable(path); err != nil {
return err
}
path = fmt.Sprintf("/sys/class/gpio/gpio%v/edge", p.pin)
return waitWriteable(path)
}
var (
// ErrTimeout indicates the operation could not be performed within the
// expected time.
ErrTimeout = errors.New("timeout")
// ErrBusy indicates the operation is already active on the pin.
ErrBusy = errors.New("pin already in use")
)