// Copyright 2017 The Periph Authors. All rights reserved. // Use of this source code is governed under the Apache License, Version 2.0 // that can be found in the LICENSE file. package sysfs import ( "errors" "os" "strconv" "strings" "sync" "sync/atomic" "syscall" "time" ) // syscall.EpollCtl() commands. // // These are defined here so we don't need to import golang.org/x/sys/unix. // // http://man7.org/linux/man-pages/man2/epoll_ctl.2.html const ( epollCTLAdd = 1 // EPOLL_CTL_ADD epollCTLDel = 2 // EPOLL_CTL_DEL epollCTLMod = 3 // EPOLL_CTL_MOD ) // Bitmask for field syscall.EpollEvent.Events. // // These are defined here so we don't need to import golang.org/x/sys/unix. // // http://man7.org/linux/man-pages/man2/epoll_ctl.2.html type epollEvent uint32 const ( epollIN epollEvent = 0x1 // EPOLLIN: available for read epollOUT epollEvent = 0x4 // EPOLLOUT: available for write epollPRI epollEvent = 0x2 // EPOLLPRI: exceptional urgent condition epollERR epollEvent = 0x8 // EPOLLERR: error epollHUP epollEvent = 0x10 // EPOLLHUP: hangup epollET epollEvent = 0x80000000 // EPOLLET: Edge Triggered behavior epollONESHOT epollEvent = 0x40000000 // EPOLLONESHOT: One shot epollWAKEUP epollEvent = 0x20000000 // EPOLLWAKEUP: disable system sleep; kernel >=3.5 epollEXCLUSIVE epollEvent = 0x10000000 // EPOLLEXCLUSIVE: only wake one; kernel >=4.5 ) var bitmaskString = [...]struct { e epollEvent s string }{ {epollIN, "IN"}, {epollOUT, "OUT"}, {epollPRI, "PRI"}, {epollERR, "ERR"}, {epollHUP, "HUP"}, {epollET, "ET"}, {epollONESHOT, "ONESHOT"}, {epollWAKEUP, "WAKEUP"}, {epollEXCLUSIVE, "EXCLUSIVE"}, } // String is useful for debugging. func (e epollEvent) String() string { var out []string for _, b := range bitmaskString { if e&b.e != 0 { out = append(out, b.s) e &^= b.e } } if e != 0 { out = append(out, "0x"+strconv.FormatUint(uint64(e), 16)) } if len(out) == 0 { out = []string{"0"} } return strings.Join(out, "|") } // eventsListener listens for events for multiple files as a single system call. // // One OS thread is needed for all the events. This is more efficient on single // core system. type eventsListener struct { // Atomic value set to one once fully initialized. initialized int32 // Mapping of file descriptors to wait on with their corresponding channels. mu sync.Mutex // File descriptor of the epoll handle itself. epollFd int // Pipes to wake up the EpollWait() system call inside loop(). r, w *os.File // Return channel to confirm that EpollWait() was woken up. wakeUp <-chan time.Time // Map of file handles to user listening channel. fds map[int32]chan<- time.Time } // init must be called on a fresh instance. func (e *eventsListener) init() error { if atomic.LoadInt32(&e.initialized) != 0 { // Was already initialized. return nil } e.mu.Lock() if atomic.LoadInt32(&e.initialized) != 0 { // Was already initialized, but this was done concurrently with another // thread. e.mu.Unlock() return nil } err := e.initLocked() atomic.StoreInt32(&e.initialized, 1) if err != nil { e.mu.Unlock() return err } wakeUp := make(chan time.Time) e.wakeUp = wakeUp e.fds = map[int32]chan<- time.Time{} e.fds[int32(e.r.Fd())] = wakeUp // The mutex is still held after this function exits, it's loop() that will // release the mutex. // // This forces loop() to be started before addFd() can be called by users. go e.loop() return nil } func (e *eventsListener) initLocked() error { var err error e.epollFd, err = syscall.EpollCreate(1) switch { case err == nil: break case err.Error() == "function not implemented": // Some arch (arm64) do not implement EpollCreate(). if e.epollFd, err = syscall.EpollCreate1(0); err != nil { return err } default: return err } e.r, e.w, err = os.Pipe() if err != nil { return err } // Only need epollIN. epollPRI has no effect on pipes. return e.addFdInner(e.r.Fd(), epollET|epollIN) } // loop is the main event loop. func (e *eventsListener) loop() { var events []syscall.EpollEvent type lookup struct { c chan<- time.Time event epollEvent } var lookups []lookup for first := true; ; { if !first { e.mu.Lock() } if len(events) < len(e.fds) { events = make([]syscall.EpollEvent, len(e.fds)) } e.mu.Unlock() first = false if len(events) == 0 { panic("internal error: there's should be at least one pipe") } // http://man7.org/linux/man-pages/man2/epoll_wait.2.html n, err := syscall.EpollWait(e.epollFd, events, -1) if n <= 0 { // -1 if an error occurred (EBADF, EFAULT, EINVAL) or the call was // interrupted by a signal (EINTR). // 0 is the timeout occurred. In this case there's no timeout specified. // Still handle this explicitly in case a timeout could be triggered by // external events, like system sleep. continue } if err != nil { // TODO(maruel): It'd be nice to be able to surface this. // This may cause a busy loop. Hopefully the user will notice and will // fix their code. // This can happen when removeFd() is called, in this case silently // ignore the error. continue } now := time.Now() // Create a look up table with the lock, so that then the channel can be // pushed to without the lock. if cap(lookups) < n { lookups = make([]lookup, 0, n) } else { lookups = lookups[:0] } e.mu.Lock() for _, ev := range events[:n] { ep := epollEvent(ev.Events) // Skip over file descriptors that are not present. c, ok := e.fds[ev.Fd] if !ok { // That's a race condition where the file descriptor was removed by // removeFd() but it still triggered. Ignore this event. continue } // Look at the event to determine if it's worth sending a pulse. It's // maybe not worth it. Ignore epollERR, since it's always set for GPIO // sysfs. // Pipe and socket trigger epollIN and epollOUT, but GPIO sysfs triggers // epollPRI. if ep&(epollPRI|epollIN|epollOUT) != 0 { lookups = append(lookups, lookup{c: c, event: ep}) } } e.mu.Unlock() // Once the lock is released, send the timestamps. for _, t := range lookups { t.c <- now } } } // addFd starts listening to events generated by file descriptor |fd|. // // fd is the OS file descriptor. In practice, it must fit a int32 value. It // works on pipes, sockets and sysfs objects like GPIO but not on standard // files. // // c is the channel to send events to. Unbuffered channel will block the event // loop, which may mean lost events, especially if multiple files are listened // to simultaneously. // // flags is the events to listen to. No need to specify epollERR and epollHUP, // they are sent anyway. // // addFd lazy initializes eventsListener if it was not initialized yet. // // It can fail due to various reasons, a few are: // ENOSPC: /proc/sys/fs/epoll/max_user_watches limit was exceeded // ENOMEM: No memory available // EPERM: fd is a regular file or directory func (e *eventsListener) addFd(fd uintptr, c chan<- time.Time, flags epollEvent) error { if c == nil { return errors.New("fd: addFd requires a valid channel") } if err := e.init(); err != nil { return err } if err := e.addFdInner(fd, flags); err != nil { return err } e.mu.Lock() e.fds[int32(fd)] = c e.mu.Unlock() // Wake up the poller so it notices there's one new file. e.wakeUpLoop(nil) return nil } func (e *eventsListener) addFdInner(fd uintptr, flags epollEvent) error { ev := syscall.EpollEvent{Events: uint32(flags), Fd: int32(fd)} return syscall.EpollCtl(e.epollFd, epollCTLAdd, int(fd), &ev) } // removeFd stop listening to events on this file descriptor. func (e *eventsListener) removeFd(fd uintptr) error { if err := syscall.EpollCtl(e.epollFd, epollCTLDel, int(fd), nil); err != nil { return err } e.mu.Lock() delete(e.fds, int32(fd)) e.mu.Unlock() // Wake up the poller so it notices there's one less file. e.wakeUpLoop(nil) return nil } // wakeUpLoop wakes up the poller and waits for it. // // Must not be called with the lock held. func (e *eventsListener) wakeUpLoop(c <-chan time.Time) time.Time { // TODO(maruel): Figure out a way to wake up that doesn't require emptying. var b [1]byte _, _ = e.w.Write(b[:]) var t time.Time if c != nil { // To prevent deadlock, also empty c. for { select { case <-c: case t = <-e.wakeUp: goto out } } out: } else { t = <-e.wakeUp } // Don't forget to empty the pipe. Sadly, this will wake up the loop a second // time. _, _ = e.r.Read(b[:]) return t } // events is the global events listener. // // It uses a single global goroutine lazily initialized to call // syscall.EpollWait() to listen to many file descriptors at once. var events eventsListener