github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/pkg/ipmi/ipmi.go (about)

     1  // Copyright 2019 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 ipmi implements functions to communicate with the OpenIPMI driver
     6  // interface.
     7  // For a detailed description of OpenIPMI, see
     8  // http://openipmi.sourceforge.net/IPMI.pdf
     9  package ipmi
    10  
    11  import (
    12  	"bytes"
    13  	"encoding/binary"
    14  	"errors"
    15  	"fmt"
    16  	"log"
    17  	"math/rand"
    18  	"os"
    19  	"runtime"
    20  	"syscall"
    21  	"time"
    22  	"unsafe"
    23  
    24  	"github.com/vtolstov/go-ioctl"
    25  	"golang.org/x/sys/unix"
    26  )
    27  
    28  const (
    29  	_IPMI_BMC_CHANNEL                = 0xf
    30  	_IPMI_BUF_SIZE                   = 1024
    31  	_IPMI_IOC_MAGIC                  = 'i'
    32  	_IPMI_OPENIPMI_READ_TIMEOUT      = 15
    33  	_IPMI_SYSTEM_INTERFACE_ADDR_TYPE = 0x0c
    34  
    35  	// Net functions
    36  	_IPMI_NETFN_CHASSIS   NetFn = 0x0
    37  	_IPMI_NETFN_APP       NetFn = 0x6
    38  	_IPMI_NETFN_STORAGE   NetFn = 0xA
    39  	_IPMI_NETFN_TRANSPORT NetFn = 0xC
    40  
    41  	// IPM Device "Global" Commands
    42  	BMC_GET_DEVICE_ID Command = 0x01
    43  
    44  	// BMC Device and Messaging Commands
    45  	BMC_SET_WATCHDOG_TIMER     Command = 0x24
    46  	BMC_GET_WATCHDOG_TIMER     Command = 0x25
    47  	BMC_SET_GLOBAL_ENABLES     Command = 0x2E
    48  	BMC_GET_GLOBAL_ENABLES     Command = 0x2F
    49  	SET_SYSTEM_INFO_PARAMETERS Command = 0x58
    50  	BMC_ADD_SEL                Command = 0x44
    51  
    52  	// Chassis Device Commands
    53  	BMC_GET_CHASSIS_STATUS Command = 0x01
    54  
    55  	// SEL device Commands
    56  	BMC_GET_SEL_INFO Command = 0x40
    57  
    58  	//LAN Device Commands
    59  	BMC_GET_LAN_CONFIG Command = 0x02
    60  
    61  	IPM_WATCHDOG_NO_ACTION    = 0x00
    62  	IPM_WATCHDOG_SMS_OS       = 0x04
    63  	IPM_WATCHDOG_CLEAR_SMS_OS = 0x10
    64  
    65  	ADTL_SEL_DEVICE         = 0x04
    66  	EN_SYSTEM_EVENT_LOGGING = 0x08
    67  
    68  	// SEL
    69  	// STD_TYPE  = 0x02
    70  	OEM_NTS_TYPE = 0xFB
    71  
    72  	_SYSTEM_INFO_BLK_SZ = 16
    73  
    74  	_SYSTEM_FW_VERSION = 1
    75  
    76  	_ASCII = 0
    77  
    78  	// Set 62 Bytes (4 sets) as the maximal string length
    79  	strlenMax = 62
    80  )
    81  
    82  var (
    83  	_IPMICTL_RECEIVE_MSG  = ioctl.IOWR(_IPMI_IOC_MAGIC, 12, uintptr(unsafe.Sizeof(recv{})))
    84  	_IPMICTL_SEND_COMMAND = ioctl.IOR(_IPMI_IOC_MAGIC, 13, uintptr(unsafe.Sizeof(req{})))
    85  
    86  	timeout = 30 * time.Second
    87  )
    88  
    89  // IPMI represents access to the IPMI interface.
    90  type IPMI struct {
    91  	*os.File
    92  }
    93  
    94  // Command is the command code for a given message.
    95  type Command byte
    96  
    97  // NetFn is the network function of the class of message being sent.
    98  type NetFn byte
    99  
   100  // Msg is the full IPMI message to be sent.
   101  type Msg struct {
   102  	Netfn   NetFn
   103  	Cmd     Command
   104  	DataLen uint16
   105  	Data    unsafe.Pointer
   106  }
   107  
   108  type req struct {
   109  	addr    *systemInterfaceAddr
   110  	addrLen uint32
   111  	msgid   int64 //nolint:structcheck
   112  	msg     Msg
   113  }
   114  
   115  type recv struct {
   116  	recvType int32 //nolint:structcheck
   117  	addr     *systemInterfaceAddr
   118  	addrLen  uint32
   119  	msgid    int64 //nolint:structcheck
   120  	msg      Msg
   121  }
   122  
   123  type systemInterfaceAddr struct {
   124  	addrType int32
   125  	channel  int16
   126  	lun      byte //nolint:unused
   127  }
   128  
   129  // StandardEvent is a standard systemevent.
   130  //
   131  // The data in this event should follow IPMI spec
   132  type StandardEvent struct {
   133  	Timestamp    uint32
   134  	GenID        uint16
   135  	EvMRev       uint8
   136  	SensorType   uint8
   137  	SensorNum    uint8
   138  	EventTypeDir uint8
   139  	EventData    [3]uint8
   140  }
   141  
   142  // OEMTsEvent is a timestamped OEM-custom event.
   143  //
   144  // It holds 6 bytes of OEM-defined arbitrary data.
   145  type OEMTsEvent struct {
   146  	Timestamp        uint32
   147  	ManfID           [3]uint8
   148  	OEMTsDefinedData [6]uint8
   149  }
   150  
   151  // OEMNonTsEvent is a non-timestamped OEM-custom event.
   152  //
   153  // It holds 13 bytes of OEM-defined arbitrary data.
   154  type OEMNontsEvent struct {
   155  	OEMNontsDefinedData [13]uint8
   156  }
   157  
   158  // Event is included three kinds of events, Standard, OEM timestamped and OEM non-timestamped
   159  //
   160  // The record type decides which event should be used
   161  type Event struct {
   162  	RecordID   uint16
   163  	RecordType uint8
   164  	StandardEvent
   165  	OEMTsEvent
   166  	OEMNontsEvent
   167  }
   168  
   169  type setSystemInfoReq struct {
   170  	paramSelector byte
   171  	setSelector   byte
   172  	strData       [_SYSTEM_INFO_BLK_SZ]byte
   173  }
   174  
   175  type DevID struct {
   176  	DeviceID          byte
   177  	DeviceRevision    byte
   178  	FwRev1            byte
   179  	FwRev2            byte
   180  	IpmiVersion       byte
   181  	AdtlDeviceSupport byte
   182  	ManufacturerID    [3]byte
   183  	ProductID         [2]byte
   184  	AuxFwRev          [4]byte
   185  }
   186  
   187  type ChassisStatus struct {
   188  	CurrentPowerState byte
   189  	LastPowerEvent    byte
   190  	MiscChassisState  byte
   191  	FrontPanelButton  byte
   192  }
   193  
   194  type SELInfo struct {
   195  	Version     byte
   196  	Entries     uint16
   197  	FreeSpace   uint16
   198  	LastAddTime uint32
   199  	LastDelTime uint32
   200  	OpSupport   byte
   201  }
   202  
   203  func fdSet(fd uintptr, p *syscall.FdSet) {
   204  	p.Bits[fd/64] |= 1 << (uint(fd) % 64)
   205  }
   206  
   207  func ioctlSetReq(fd, name uintptr, req *req) error {
   208  	_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, name, uintptr(unsafe.Pointer(req)))
   209  	runtime.KeepAlive(req)
   210  	if err != 0 {
   211  		return err
   212  	}
   213  	return nil
   214  }
   215  
   216  func ioctlGetRecv(fd, name uintptr, recv *recv) error {
   217  	_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, name, uintptr(unsafe.Pointer(recv)))
   218  	runtime.KeepAlive(recv)
   219  	if err != 0 {
   220  		return err
   221  	}
   222  	return nil
   223  }
   224  
   225  // SendRecv sends the IPMI message, receives the response, and returns the
   226  // response data. This is recommended for use unless the user must be able to
   227  // specify the data pointer and length on their own.
   228  func (i *IPMI) SendRecv(netfn NetFn, cmd Command, data []byte) ([]byte, error) {
   229  	var dataPtr unsafe.Pointer
   230  	if data != nil {
   231  		dataPtr = unsafe.Pointer(&data[0])
   232  	}
   233  	msg := Msg{
   234  		Netfn:   netfn,
   235  		Cmd:     cmd,
   236  		Data:    dataPtr,
   237  		DataLen: uint16(len(data)),
   238  	}
   239  	return i.RawSendRecv(msg)
   240  }
   241  
   242  // RawSendRecv sends the IPMI message, receives the response, and returns the
   243  // response data.
   244  func (i *IPMI) RawSendRecv(msg Msg) ([]byte, error) {
   245  	addr := &systemInterfaceAddr{
   246  		addrType: _IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
   247  		channel:  _IPMI_BMC_CHANNEL,
   248  	}
   249  	req := &req{
   250  		addr:    addr,
   251  		addrLen: uint32(unsafe.Sizeof(addr)),
   252  		msgid:   rand.Int63(),
   253  		msg:     msg,
   254  	}
   255  
   256  	if err := ioctlSetReq(i.Fd(), _IPMICTL_SEND_COMMAND, req); err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	set := &syscall.FdSet{}
   261  	fdSet(i.Fd(), set)
   262  	timeval := &syscall.Timeval{
   263  		Sec:  _IPMI_OPENIPMI_READ_TIMEOUT,
   264  		Usec: 0,
   265  	}
   266  	if _, err := syscall.Select(int(i.Fd()+1), set, nil, nil, timeval); err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	buf := make([]byte, _IPMI_BUF_SIZE)
   271  	recvMsg := Msg{
   272  		Data:    unsafe.Pointer(&buf[0]),
   273  		DataLen: _IPMI_BUF_SIZE,
   274  	}
   275  	recv := &recv{
   276  		addr:    req.addr,
   277  		addrLen: req.addrLen,
   278  		msg:     recvMsg,
   279  	}
   280  
   281  	t := time.After(timeout)
   282  	for {
   283  		if err := ioctlGetRecv(i.Fd(), _IPMICTL_RECEIVE_MSG, recv); err != nil {
   284  			return nil, err
   285  		}
   286  		if recv.msgid != req.msgid {
   287  			log.Printf("Received wrong message. Trying again.")
   288  		} else {
   289  			break
   290  		}
   291  		select {
   292  		case <-t:
   293  			return nil, fmt.Errorf("timeout waiting for response")
   294  		}
   295  	}
   296  
   297  	if recv.msg.DataLen >= _IPMI_BUF_SIZE {
   298  		return nil, fmt.Errorf("data length received too large: %d > %d", recv.msg.DataLen, _IPMI_BUF_SIZE)
   299  	}
   300  
   301  	return buf[:recv.msg.DataLen:recv.msg.DataLen], nil
   302  }
   303  
   304  func (i *IPMI) WatchdogRunning() (bool, error) {
   305  	recv, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_WATCHDOG_TIMER, nil)
   306  	if err != nil {
   307  		return false, err
   308  	}
   309  
   310  	if len(recv) > 2 && (recv[1]&0x40) != 0 {
   311  		return true, nil
   312  	}
   313  
   314  	return false, nil
   315  }
   316  
   317  func (i *IPMI) ShutoffWatchdog() error {
   318  	var data [6]byte
   319  	data[0] = IPM_WATCHDOG_SMS_OS
   320  	data[1] = IPM_WATCHDOG_NO_ACTION
   321  	data[2] = 0x00 // pretimeout interval
   322  	data[3] = IPM_WATCHDOG_CLEAR_SMS_OS
   323  	data[4] = 0xb8 // countdown lsb (100 ms/count)
   324  	data[5] = 0x0b // countdown msb - 5 mins
   325  
   326  	_, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_WATCHDOG_TIMER, data[:6])
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	return nil
   332  }
   333  
   334  // marshall converts the Event struct to binary data and the content of returned data is based on the record type
   335  func (e *Event) marshall() ([]byte, error) {
   336  	buf := &bytes.Buffer{}
   337  
   338  	if err := binary.Write(buf, binary.LittleEndian, *e); err != nil {
   339  		return nil, err
   340  	}
   341  
   342  	data := make([]byte, 16)
   343  
   344  	// system event record
   345  	if buf.Bytes()[2] == 0x2 {
   346  		copy(data[:], buf.Bytes()[:16])
   347  	}
   348  
   349  	// OEM timestamped
   350  	if buf.Bytes()[2] >= 0xC0 && buf.Bytes()[2] <= 0xDF {
   351  		copy(data[0:3], buf.Bytes()[0:3])
   352  		copy(data[3:16], buf.Bytes()[16:29])
   353  	}
   354  
   355  	// OEM non-timestamped
   356  	if buf.Bytes()[2] >= 0xE0 && buf.Bytes()[2] <= 0xFF {
   357  		copy(data[0:3], buf.Bytes()[0:3])
   358  		copy(data[3:16], buf.Bytes()[29:42])
   359  	}
   360  
   361  	return data, nil
   362  }
   363  
   364  // LogSystemEvent adds an SEL (System Event Log) entry.
   365  func (i *IPMI) LogSystemEvent(e *Event) error {
   366  	data, err := e.marshall()
   367  
   368  	if err != nil {
   369  		return err
   370  	}
   371  	_, err = i.SendRecv(_IPMI_NETFN_STORAGE, BMC_ADD_SEL, data)
   372  	return err
   373  }
   374  
   375  func (i *IPMI) setsysinfo(data *setSystemInfoReq) error {
   376  	msg := Msg{
   377  		Cmd:     SET_SYSTEM_INFO_PARAMETERS,
   378  		Netfn:   _IPMI_NETFN_APP,
   379  		Data:    unsafe.Pointer(data),
   380  		DataLen: 18,
   381  	}
   382  
   383  	if _, err := i.RawSendRecv(msg); err != nil {
   384  		return err
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  func strcpyPadded(dst []byte, src string) {
   391  	dstLen := len(dst)
   392  	if copied := copy(dst, src); copied < dstLen {
   393  		padding := make([]byte, dstLen-copied)
   394  		copy(dst[copied:], padding)
   395  	}
   396  }
   397  
   398  // SetSystemFWVersion sets the provided system firmware version to BMC via IPMI.
   399  func (i *IPMI) SetSystemFWVersion(version string) error {
   400  	len := len(version)
   401  
   402  	if len == 0 {
   403  		return fmt.Errorf("Version length is 0")
   404  	} else if len > strlenMax {
   405  		len = strlenMax
   406  	}
   407  
   408  	var data setSystemInfoReq
   409  	var index int
   410  	data.paramSelector = _SYSTEM_FW_VERSION
   411  	data.setSelector = 0
   412  	for len > index {
   413  		if data.setSelector == 0 { // the fisrt block of string data
   414  			data.strData[0] = _ASCII
   415  			data.strData[1] = byte(len)
   416  			strcpyPadded(data.strData[2:], version)
   417  			index += _SYSTEM_INFO_BLK_SZ - 2
   418  		} else {
   419  			strcpyPadded(data.strData[:], version)
   420  			index += _SYSTEM_INFO_BLK_SZ
   421  		}
   422  
   423  		if err := i.setsysinfo(&data); err != nil {
   424  			return err
   425  		}
   426  		data.setSelector++
   427  	}
   428  
   429  	return nil
   430  }
   431  
   432  func (i *IPMI) GetDeviceID() (*DevID, error) {
   433  	data, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_DEVICE_ID, nil)
   434  
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  
   439  	buf := bytes.NewReader(data[1:])
   440  	mcInfo := DevID{}
   441  
   442  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo); err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	return &mcInfo, nil
   447  }
   448  
   449  func (i *IPMI) setGlobalEnables(enables byte) error {
   450  	_, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_GLOBAL_ENABLES, []byte{enables})
   451  	return err
   452  }
   453  
   454  func (i *IPMI) getGlobalEnables() ([]byte, error) {
   455  	return i.SendRecv(_IPMI_NETFN_APP, BMC_GET_GLOBAL_ENABLES, nil)
   456  }
   457  
   458  func (i *IPMI) EnableSEL() (bool, error) {
   459  	// Check if SEL device is supported or not
   460  	mcInfo, err := i.GetDeviceID()
   461  
   462  	if err != nil {
   463  		return false, err
   464  	} else if (mcInfo.AdtlDeviceSupport & ADTL_SEL_DEVICE) == 0 {
   465  		return false, nil
   466  	}
   467  
   468  	data, err := i.getGlobalEnables()
   469  
   470  	if err != nil {
   471  		return false, err
   472  	}
   473  
   474  	if (data[1] & EN_SYSTEM_EVENT_LOGGING) == 0 {
   475  		// SEL is not enabled, enable SEL
   476  		if err = i.setGlobalEnables(data[1] | EN_SYSTEM_EVENT_LOGGING); err != nil {
   477  			return false, err
   478  		}
   479  	}
   480  
   481  	return true, nil
   482  }
   483  
   484  func (i *IPMI) GetChassisStatus() (*ChassisStatus, error) {
   485  	data, err := i.SendRecv(_IPMI_NETFN_CHASSIS, BMC_GET_CHASSIS_STATUS, nil)
   486  	if err != nil {
   487  		return nil, err
   488  	}
   489  
   490  	buf := bytes.NewReader(data[1:])
   491  
   492  	var status ChassisStatus
   493  	if err := binary.Read(buf, binary.LittleEndian, &status); err != nil {
   494  		return nil, err
   495  	}
   496  	return &status, nil
   497  }
   498  
   499  func (i *IPMI) GetSELInfo() (*SELInfo, error) {
   500  	data, err := i.SendRecv(_IPMI_NETFN_STORAGE, BMC_GET_SEL_INFO, nil)
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  
   505  	buf := bytes.NewReader(data[1:])
   506  
   507  	var info SELInfo
   508  	if err := binary.Read(buf, binary.LittleEndian, &info); err != nil {
   509  		return nil, err
   510  	}
   511  	return &info, nil
   512  }
   513  
   514  func (i *IPMI) GetLanConfig(channel byte, param byte) ([]byte, error) {
   515  	var data [4]byte
   516  	data[0] = channel
   517  	data[1] = param
   518  	data[2] = 0
   519  	data[3] = 0
   520  
   521  	return i.SendRecv(_IPMI_NETFN_TRANSPORT, BMC_GET_LAN_CONFIG, data[:4])
   522  }
   523  
   524  func (i *IPMI) RawCmd(param []byte) ([]byte, error) {
   525  	if len(param) < 2 {
   526  		return nil, errors.New("Not enough parameters given")
   527  	}
   528  
   529  	msg := Msg{
   530  		Netfn: NetFn(param[0]),
   531  		Cmd:   Command(param[1]),
   532  	}
   533  	if len(param) > 2 {
   534  		msg.Data = unsafe.Pointer(&param[2])
   535  	}
   536  
   537  	msg.DataLen = uint16(len(param) - 2)
   538  
   539  	return i.RawSendRecv(msg)
   540  }