// Copyright 2021 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package eventfd wraps Linux's eventfd(2) syscall. package eventfd import ( "fmt" "io" "github.com/sagernet/gvisor/pkg/hostarch" "github.com/sagernet/gvisor/pkg/rawfile" "github.com/sagernet/gvisor/pkg/safecopy" "golang.org/x/sys/unix" ) const sizeofUint64 = 8 // Eventfd represents a Linux eventfd object. type Eventfd struct { fd int mmioAddr uintptr mmioCtrl MMIOController } // Create returns an initialized eventfd. func Create() (Eventfd, error) { fd, _, err := unix.RawSyscall(unix.SYS_EVENTFD2, 0, 0, 0) if err != 0 { return Eventfd{}, fmt.Errorf("failed to create eventfd: %v", error(err)) } if err := unix.SetNonblock(int(fd), true); err != nil { unix.Close(int(fd)) return Eventfd{}, err } return Eventfd{fd: int(fd)}, nil } // Wrap returns an initialized Eventfd using the provided fd. func Wrap(fd int) Eventfd { return Eventfd{fd: fd} } // Close closes the eventfd, after which it should not be used. func (ev Eventfd) Close() error { if ev.mmioCtrl != nil { ev.mmioCtrl.Close(ev) } return unix.Close(ev.fd) } // Dup copies the eventfd, calling dup(2) on the underlying file descriptor. func (ev Eventfd) Dup() (Eventfd, error) { other, err := unix.Dup(ev.fd) if err != nil { return Eventfd{}, fmt.Errorf("failed to dup: %v", other) } return Eventfd{fd: other}, nil } // Notify alerts other users of the eventfd. Users can receive alerts by // calling Wait or Read. func (ev Eventfd) Notify() error { return ev.Write(1) } // Write writes a specific value to the eventfd. func (ev Eventfd) Write(val uint64) error { var buf [sizeofUint64]byte hostarch.ByteOrder.PutUint64(buf[:], val) if ev.mmioAddr != 0 && ev.mmioCtrl.Enabled() { if _, err := safecopy.CopyOut(ev.mmioPtr(), buf[:]); err == nil { return nil } // Fall back to using a syscall. } for { n, err := nonBlockingWrite(ev.fd, buf[:]) if err == unix.EINTR { continue } if err != nil || n != sizeofUint64 { panic(fmt.Sprintf("bad write to eventfd: got %d bytes, wanted %d with error %v", n, sizeofUint64, err)) } return err } } // MMIOWrite is equivalent to Write, but returns an error if the write cannot be // implemented by writing to the address set by EnableMMIO. This is primarily // useful for testing. func (ev Eventfd) MMIOWrite(val uint64) error { var buf [sizeofUint64]byte hostarch.ByteOrder.PutUint64(buf[:], val) if ev.mmioAddr == 0 { return fmt.Errorf("no MMIO address set") } if !ev.mmioCtrl.Enabled() { return fmt.Errorf("MMIO is temporarily disabled") } _, err := safecopy.CopyOut(ev.mmioPtr(), buf[:]) return err } // Wait blocks until eventfd is non-zero (i.e. someone calls Notify or Write). func (ev Eventfd) Wait() error { _, err := ev.Read() return err } // Read blocks until eventfd is non-zero (i.e. someone calls Notify or Write) // and returns the value read. func (ev Eventfd) Read() (uint64, error) { var tmp [sizeofUint64]byte n, errno := rawfile.BlockingRead(ev.fd, tmp[:]) if errno != 0 { return 0, errno } if n == 0 { return 0, io.EOF } if n != sizeofUint64 { panic(fmt.Sprintf("short read from eventfd: got %d bytes, wanted %d", n, sizeofUint64)) } return hostarch.ByteOrder.Uint64(tmp[:]), nil } // FD returns the underlying file descriptor. Use with care, as this breaks the // Eventfd abstraction. func (ev Eventfd) FD() int { return ev.fd } // MMIOController controls eventfd memory-mapped I/O. type MMIOController interface { // Enabled returns true if writing to the associated MMIO address can // succeed. This is inherently racy, so if the memory-mapped write faults, // the eventfd will fall back to writing using a syscall. Enabled() bool // Close is called when the associated Eventfd is closed. Close(ev Eventfd) } // EnableMMIO causes future calls to ev.Write() to use memory-mapped writes to // addr, subject to ctrl. EnableMMIO cannot be called concurrently with Write, // MMIOWrite, or MMIOAddr. // // This feature is used to support KVM ioeventfds. Since this requires that // addr is mapped read-only or with no permissions in the host virtual address // space (so that writes in host mode fault), it cannot reasonably be // Go-managed memory, so it's safe to type as uintptr rather than a pointer. func (ev *Eventfd) EnableMMIO(addr uintptr, ctrl MMIOController) { ev.mmioAddr = addr ev.mmioCtrl = ctrl } // DisableMMIO undoes the effect of a previous call to EnableMMIO. DisableMMIO // cannot be called concurrently with Write, MMIOWrite, or MMIOAddr. func (ev *Eventfd) DisableMMIO() { ev.mmioAddr = 0 ev.mmioCtrl = nil } // MMIOAddr returns the address set by the last call to EnableMMIO. func (ev Eventfd) MMIOAddr() uintptr { return ev.mmioAddr }