github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/watchdog/watchdog.go (about)

     1  // Copyright 2021 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package watchdog provides functions for interacting with the Linux watchdog.
     6  //
     7  // The basic usage is:
     8  //
     9  //	wd, err := watchdog.Open(watchdog.Dev)
    10  //	while running {
    11  //	    wd.KeepAlive()
    12  //	}
    13  //	wd.MagicClose()
    14  //
    15  // Open() arms the watchdog. MagicClose() disarms the watchdog.
    16  //
    17  // Note not every watchdog driver supports every function!
    18  //
    19  // For more, see:
    20  // https://www.kernel.org/doc/Documentation/watchdog/watchdog-api.txt
    21  package watchdog
    22  
    23  import (
    24  	"fmt"
    25  	"os"
    26  	"time"
    27  	"unsafe"
    28  
    29  	"golang.org/x/sys/unix"
    30  )
    31  
    32  // Dev is the name of the first watchdog. If there are multiple watchdogs, they
    33  // are named /dev/watchdog0, /dev/watchdog1, ...
    34  const Dev = "/dev/watchdog"
    35  
    36  // Various ioctl numbers.
    37  const (
    38  	wdiocGetSupport    = 0x80285700
    39  	wdiocGetStatus     = 0x80045701
    40  	wdiocGetBootStatus = 0x80045702
    41  	wdiocGetTemp       = 0x80045703
    42  	wdiocSetOptions    = 0x80045704
    43  	wdiocKeepAlive     = 0x80045705
    44  	wdiocSetTimeout    = 0xc0045706
    45  	wdiocGetTimeout    = 0x80045707
    46  	wdiocSetPreTimeout = 0xc0045708
    47  	wdiocGetPreTimeout = 0x80045709
    48  	wdiocGetTimeLeft   = 0x8004570a
    49  )
    50  
    51  // Status contains flags returned by Status() and BootStatus(). These are the
    52  // same flags used for Support()'s options field.
    53  type Status int32
    54  
    55  // Bitset of possible flags for the Status() type.
    56  const (
    57  	// Unknown flag error
    58  	StatusUnknown Status = -1
    59  	// Reset due to CPU overheat
    60  	StatusOverheat Status = 0x0001
    61  	// Fan failed
    62  	StatusFanFault Status = 0x0002
    63  	// External relay 1
    64  	StatusExtern1 Status = 0x0004
    65  	// ExStatusl relay 2
    66  	StatusExtern2 Status = 0x0008
    67  	// Power bad/power fault
    68  	StatusPowerUnder Status = 0x0010
    69  	// Card previously reset the CPU
    70  	StatusCardReset Status = 0x0020
    71  	// Power over voltage
    72  	StatusPowerOver Status = 0x0040
    73  	// Set timeout (in seconds)
    74  	StatusSetTimeout Status = 0x0080
    75  	// Supports magic close char
    76  	StatusMagicClose Status = 0x0100
    77  	// Pretimeout (in seconds), get/set
    78  	StatusPreTimeout Status = 0x0200
    79  	// Watchdog triggers a management or other external alarm not a reboot
    80  	StatusAlarmOnly Status = 0x0400
    81  	// Keep alive ping reply
    82  	StatusKeepAlivePing Status = 0x8000
    83  )
    84  
    85  // Option are options passed to SetOptions().
    86  type Option int32
    87  
    88  // Bitset of possible flags for the Option type.
    89  const (
    90  	// Unknown status error
    91  	OptionUnknown Option = -1
    92  	// Turn off the watchdog timer
    93  	OptionDisableCard Option = 0x0001
    94  	// Turn on the watchdog timer
    95  	OptionEnableCard Option = 0x0002
    96  	// Kernel panic on temperature trip
    97  	OptionTempPanic Option = 0x0004
    98  )
    99  
   100  type syscalls interface {
   101  	unixSyscall(uintptr, uintptr, uintptr, unsafe.Pointer) (uintptr, uintptr, unix.Errno)
   102  	unixIoctlGetUint32(int, uint) (uint32, error)
   103  }
   104  
   105  // Watchdog holds the descriptor of an open watchdog driver.
   106  type Watchdog struct {
   107  	*os.File
   108  	syscalls
   109  }
   110  
   111  type realSyscalls struct{}
   112  
   113  func (r *realSyscalls) unixSyscall(trap, a1, a2 uintptr, a3 unsafe.Pointer) (uintptr, uintptr, unix.Errno) {
   114  	return unix.Syscall(trap, a1, a2, uintptr(a3))
   115  }
   116  
   117  func (r *realSyscalls) unixIoctlGetUint32(fd int, req uint) (uint32, error) {
   118  	return unix.IoctlGetUint32(fd, req)
   119  }
   120  
   121  // Open arms the watchdog.
   122  func Open(dev string) (*Watchdog, error) {
   123  	f, err := os.OpenFile(dev, os.O_RDWR, 0)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	return &Watchdog{
   128  			File:     f,
   129  			syscalls: &realSyscalls{},
   130  		},
   131  		nil
   132  }
   133  
   134  // Close closes the device without disarming the watchdog.
   135  func (w *Watchdog) Close() error {
   136  	return w.File.Close()
   137  }
   138  
   139  // MagicClose disarms the watchdog. However if the kernel is compiled with
   140  // CONFIG_WATCHDOG_NOWAYOUT=y, there may be no way to disarm the watchdog.
   141  func (w *Watchdog) MagicClose() error {
   142  	if _, err := w.File.Write([]byte("V")); err != nil {
   143  		w.File.Close()
   144  		return err
   145  	}
   146  	return w.File.Close()
   147  }
   148  
   149  // Support returns the WatchdogInfo struct.
   150  func (w *Watchdog) Support() (*unix.WatchdogInfo, error) {
   151  	var wi unix.WatchdogInfo
   152  	if _, _, err := w.unixSyscall(unix.SYS_IOCTL, w.File.Fd(), wdiocGetSupport, unsafe.Pointer(&wi)); err != 0 {
   153  		return nil, err
   154  	}
   155  	return &wi, nil
   156  }
   157  
   158  // Status returns the current status.
   159  func (w *Watchdog) Status() (Status, error) {
   160  	flags, err := w.unixIoctlGetUint32(int(w.File.Fd()), wdiocGetStatus)
   161  	if err != nil {
   162  		return StatusUnknown, err
   163  	}
   164  	return Status(flags), nil
   165  }
   166  
   167  // BootStatus returns the status at the last reboot.
   168  func (w *Watchdog) BootStatus() (Status, error) {
   169  	flags, err := w.unixIoctlGetUint32(int(w.File.Fd()), wdiocGetBootStatus)
   170  	if err != nil {
   171  		return StatusUnknown, err
   172  	}
   173  	return Status(flags), nil
   174  }
   175  
   176  // SetOptions can be used to control some aspects of the cards operation.
   177  func (w *Watchdog) SetOptions(options Option) error {
   178  	if _, _, err := w.unixSyscall(unix.SYS_IOCTL, w.File.Fd(), wdiocSetOptions, unsafe.Pointer(&options)); err != 0 {
   179  		return err
   180  	}
   181  	return nil
   182  }
   183  
   184  // KeepAlive pets the watchdog.
   185  func (w *Watchdog) KeepAlive() error {
   186  	_, err := w.File.WriteString("1")
   187  	return err
   188  }
   189  
   190  // SetTimeout sets the watchdog timeout on the fly. It returns an error if the
   191  // timeout gets set to the wrong value. timeout must be a multiple of seconds;
   192  // otherwise, an error is returned.
   193  func (w *Watchdog) SetTimeout(timeout time.Duration) error {
   194  	to := timeout / time.Second
   195  	if _, _, err := w.unixSyscall(unix.SYS_IOCTL, w.File.Fd(), wdiocSetTimeout, unsafe.Pointer(&timeout)); err != 0 {
   196  		return err
   197  	}
   198  	gotTimeout := to * time.Second
   199  	if gotTimeout != timeout {
   200  		return fmt.Errorf("Watchdog timeout set to %v, wanted %v", gotTimeout, timeout)
   201  	}
   202  	return nil
   203  }
   204  
   205  // Timeout returns the current watchdog timeout.
   206  func (w *Watchdog) Timeout() (time.Duration, error) {
   207  	timeout, err := w.unixIoctlGetUint32(int(w.File.Fd()), wdiocGetTimeout)
   208  	if err != nil {
   209  		return 0, err
   210  	}
   211  	return time.Duration(timeout) * time.Second, nil
   212  }
   213  
   214  // SetPreTimeout sets the watchdog pretimeout on the fly. The pretimeout is the
   215  // duration before triggering the preaction (such as an NMI, interrupt, ...).
   216  // timeout must be a multiple of seconds; otherwise, an error is returned.
   217  func (w *Watchdog) SetPreTimeout(timeout time.Duration) error {
   218  	to := timeout / time.Second
   219  	if _, _, err := w.unixSyscall(unix.SYS_IOCTL, w.File.Fd(), wdiocSetPreTimeout, unsafe.Pointer(&timeout)); err != 0 {
   220  		return err
   221  	}
   222  	gotTimeout := to * time.Second
   223  	if gotTimeout != timeout {
   224  		return fmt.Errorf("Watchdog pretimeout set to %v, wanted %v", gotTimeout, timeout)
   225  	}
   226  	return nil
   227  }
   228  
   229  // PreTimeout returns the current watchdog pretimeout.
   230  func (w *Watchdog) PreTimeout() (time.Duration, error) {
   231  	timeout, err := w.unixIoctlGetUint32(int(w.File.Fd()), wdiocGetPreTimeout)
   232  	if err != nil {
   233  		return 0, err
   234  	}
   235  	return time.Duration(timeout) * time.Second, nil
   236  }
   237  
   238  // TimeLeft returns the duration before the reboot (to the nearest second).
   239  func (w *Watchdog) TimeLeft() (time.Duration, error) {
   240  	left, err := w.unixIoctlGetUint32(int(w.File.Fd()), wdiocGetTimeLeft)
   241  	if err != nil {
   242  		return 0, err
   243  	}
   244  	return time.Duration(left) * time.Second, nil
   245  }