github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/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_TRUNC = ioctl.IOWR(_IPMI_IOC_MAGIC, 11, uintptr(unsafe.Sizeof(recv{})))
    84  	_IPMICTL_SEND_COMMAND      = ioctl.IOR(_IPMI_IOC_MAGIC, 13, uintptr(unsafe.Sizeof(req{})))
    85  
    86  	timeout = time.Second * 10
    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  	// Send request.
   257  	for {
   258  		switch err := ioctlSetReq(i.Fd(), _IPMICTL_SEND_COMMAND, req); {
   259  		case err == syscall.EINTR:
   260  			continue
   261  		case err != nil:
   262  			return nil, fmt.Errorf("ioctlSetReq failed with %v", err)
   263  		}
   264  		break
   265  	}
   266  
   267  	buf := make([]byte, _IPMI_BUF_SIZE)
   268  	recvMsg := Msg{
   269  		Data:    unsafe.Pointer(&buf[0]),
   270  		DataLen: _IPMI_BUF_SIZE,
   271  	}
   272  	recv := &recv{
   273  		addr:    req.addr,
   274  		addrLen: req.addrLen,
   275  		msg:     recvMsg,
   276  	}
   277  
   278  	// This will be passed to the RawConn Read function. Until readMsg returns
   279  	// true, Read blocks and retries until the connection is ready for reading.
   280  	var result []byte
   281  	var rerr error
   282  	readMsg := func(fd uintptr) bool {
   283  		if err := ioctlGetRecv(fd, _IPMICTL_RECEIVE_MSG_TRUNC, recv); err != nil {
   284  			rerr = fmt.Errorf("ioctlGetRecv failed with %v", err)
   285  			return false
   286  		}
   287  
   288  		if recv.msgid != req.msgid {
   289  			log.Printf("Received wrong message. Trying again.")
   290  			return false
   291  		}
   292  
   293  		if recv.msg.DataLen >= _IPMI_BUF_SIZE {
   294  			rerr = fmt.Errorf("data length received too large: %d > %d", recv.msg.DataLen, _IPMI_BUF_SIZE)
   295  		} else if buf[0] != 0 {
   296  			rerr = fmt.Errorf("invalid response, expected first byte of response to be 0, got: %v", buf[0])
   297  		} else {
   298  			result = buf[:recv.msg.DataLen:recv.msg.DataLen]
   299  			rerr = nil
   300  		}
   301  		return true
   302  	}
   303  
   304  	// Read response.
   305  	conn, err := i.File.SyscallConn()
   306  	if err != nil {
   307  		return nil, fmt.Errorf("failed to get file rawconn: %v", err)
   308  	}
   309  	if err := i.File.SetReadDeadline(time.Now().Add(timeout)); err != nil {
   310  		return nil, fmt.Errorf("failed to set read deadline: %v", err)
   311  	}
   312  	if err := conn.Read(readMsg); err != nil {
   313  		return nil, fmt.Errorf("failed to read rawconn: %v", err)
   314  	}
   315  
   316  	return result, rerr
   317  }
   318  
   319  func (i *IPMI) WatchdogRunning() (bool, error) {
   320  	recv, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_WATCHDOG_TIMER, nil)
   321  	if err != nil {
   322  		return false, err
   323  	}
   324  
   325  	if len(recv) > 2 && (recv[1]&0x40) != 0 {
   326  		return true, nil
   327  	}
   328  
   329  	return false, nil
   330  }
   331  
   332  func (i *IPMI) ShutoffWatchdog() error {
   333  	var data [6]byte
   334  	data[0] = IPM_WATCHDOG_SMS_OS
   335  	data[1] = IPM_WATCHDOG_NO_ACTION
   336  	data[2] = 0x00 // pretimeout interval
   337  	data[3] = IPM_WATCHDOG_CLEAR_SMS_OS
   338  	data[4] = 0xb8 // countdown lsb (100 ms/count)
   339  	data[5] = 0x0b // countdown msb - 5 mins
   340  
   341  	_, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_WATCHDOG_TIMER, data[:6])
   342  	if err != nil {
   343  		return err
   344  	}
   345  
   346  	return nil
   347  }
   348  
   349  // marshall converts the Event struct to binary data and the content of returned data is based on the record type
   350  func (e *Event) marshall() ([]byte, error) {
   351  	buf := &bytes.Buffer{}
   352  
   353  	if err := binary.Write(buf, binary.LittleEndian, *e); err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	data := make([]byte, 16)
   358  
   359  	// system event record
   360  	if buf.Bytes()[2] == 0x2 {
   361  		copy(data[:], buf.Bytes()[:16])
   362  	}
   363  
   364  	// OEM timestamped
   365  	if buf.Bytes()[2] >= 0xC0 && buf.Bytes()[2] <= 0xDF {
   366  		copy(data[0:3], buf.Bytes()[0:3])
   367  		copy(data[3:16], buf.Bytes()[16:29])
   368  	}
   369  
   370  	// OEM non-timestamped
   371  	if buf.Bytes()[2] >= 0xE0 && buf.Bytes()[2] <= 0xFF {
   372  		copy(data[0:3], buf.Bytes()[0:3])
   373  		copy(data[3:16], buf.Bytes()[29:42])
   374  	}
   375  
   376  	return data, nil
   377  }
   378  
   379  // LogSystemEvent adds an SEL (System Event Log) entry.
   380  func (i *IPMI) LogSystemEvent(e *Event) error {
   381  	data, err := e.marshall()
   382  
   383  	if err != nil {
   384  		return err
   385  	}
   386  	_, err = i.SendRecv(_IPMI_NETFN_STORAGE, BMC_ADD_SEL, data)
   387  	return err
   388  }
   389  
   390  func (i *IPMI) setsysinfo(data *setSystemInfoReq) error {
   391  	msg := Msg{
   392  		Cmd:     SET_SYSTEM_INFO_PARAMETERS,
   393  		Netfn:   _IPMI_NETFN_APP,
   394  		Data:    unsafe.Pointer(data),
   395  		DataLen: 18,
   396  	}
   397  
   398  	if _, err := i.RawSendRecv(msg); err != nil {
   399  		return err
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  func strcpyPadded(dst []byte, src string) {
   406  	dstLen := len(dst)
   407  	if copied := copy(dst, src); copied < dstLen {
   408  		padding := make([]byte, dstLen-copied)
   409  		copy(dst[copied:], padding)
   410  	}
   411  }
   412  
   413  // SetSystemFWVersion sets the provided system firmware version to BMC via IPMI.
   414  func (i *IPMI) SetSystemFWVersion(version string) error {
   415  	len := len(version)
   416  
   417  	if len == 0 {
   418  		return fmt.Errorf("Version length is 0")
   419  	} else if len > strlenMax {
   420  		len = strlenMax
   421  	}
   422  
   423  	var data setSystemInfoReq
   424  	var index int
   425  	data.paramSelector = _SYSTEM_FW_VERSION
   426  	data.setSelector = 0
   427  	for len > index {
   428  		if data.setSelector == 0 { // the fisrt block of string data
   429  			data.strData[0] = _ASCII
   430  			data.strData[1] = byte(len)
   431  			strcpyPadded(data.strData[2:], version)
   432  			index += _SYSTEM_INFO_BLK_SZ - 2
   433  		} else {
   434  			strcpyPadded(data.strData[:], version)
   435  			index += _SYSTEM_INFO_BLK_SZ
   436  		}
   437  
   438  		if err := i.setsysinfo(&data); err != nil {
   439  			return err
   440  		}
   441  		data.setSelector++
   442  	}
   443  
   444  	return nil
   445  }
   446  
   447  func (i *IPMI) GetDeviceID() (*DevID, error) {
   448  	data, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_DEVICE_ID, nil)
   449  
   450  	if err != nil {
   451  		return nil, err
   452  	}
   453  
   454  	buf := bytes.NewReader(data[1:])
   455  	mcInfo := DevID{}
   456  
   457  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo); err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	return &mcInfo, nil
   462  }
   463  
   464  func (i *IPMI) setGlobalEnables(enables byte) error {
   465  	_, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_GLOBAL_ENABLES, []byte{enables})
   466  	return err
   467  }
   468  
   469  func (i *IPMI) getGlobalEnables() ([]byte, error) {
   470  	return i.SendRecv(_IPMI_NETFN_APP, BMC_GET_GLOBAL_ENABLES, nil)
   471  }
   472  
   473  func (i *IPMI) EnableSEL() (bool, error) {
   474  	// Check if SEL device is supported or not
   475  	mcInfo, err := i.GetDeviceID()
   476  
   477  	if err != nil {
   478  		return false, err
   479  	} else if (mcInfo.AdtlDeviceSupport & ADTL_SEL_DEVICE) == 0 {
   480  		return false, nil
   481  	}
   482  
   483  	data, err := i.getGlobalEnables()
   484  
   485  	if err != nil {
   486  		return false, err
   487  	}
   488  
   489  	if (data[1] & EN_SYSTEM_EVENT_LOGGING) == 0 {
   490  		// SEL is not enabled, enable SEL
   491  		if err = i.setGlobalEnables(data[1] | EN_SYSTEM_EVENT_LOGGING); err != nil {
   492  			return false, err
   493  		}
   494  	}
   495  
   496  	return true, nil
   497  }
   498  
   499  func (i *IPMI) GetChassisStatus() (*ChassisStatus, error) {
   500  	data, err := i.SendRecv(_IPMI_NETFN_CHASSIS, BMC_GET_CHASSIS_STATUS, nil)
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  
   505  	buf := bytes.NewReader(data[1:])
   506  
   507  	var status ChassisStatus
   508  	if err := binary.Read(buf, binary.LittleEndian, &status); err != nil {
   509  		return nil, err
   510  	}
   511  	return &status, nil
   512  }
   513  
   514  func (i *IPMI) GetSELInfo() (*SELInfo, error) {
   515  	data, err := i.SendRecv(_IPMI_NETFN_STORAGE, BMC_GET_SEL_INFO, nil)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  
   520  	buf := bytes.NewReader(data[1:])
   521  
   522  	var info SELInfo
   523  	if err := binary.Read(buf, binary.LittleEndian, &info); err != nil {
   524  		return nil, err
   525  	}
   526  	return &info, nil
   527  }
   528  
   529  func (i *IPMI) GetLanConfig(channel byte, param byte) ([]byte, error) {
   530  	var data [4]byte
   531  	data[0] = channel
   532  	data[1] = param
   533  	data[2] = 0
   534  	data[3] = 0
   535  
   536  	return i.SendRecv(_IPMI_NETFN_TRANSPORT, BMC_GET_LAN_CONFIG, data[:4])
   537  }
   538  
   539  func (i *IPMI) RawCmd(param []byte) ([]byte, error) {
   540  	if len(param) < 2 {
   541  		return nil, errors.New("Not enough parameters given")
   542  	}
   543  
   544  	msg := Msg{
   545  		Netfn: NetFn(param[0]),
   546  		Cmd:   Command(param[1]),
   547  	}
   548  	if len(param) > 2 {
   549  		msg.Data = unsafe.Pointer(&param[2])
   550  	}
   551  
   552  	msg.DataLen = uint16(len(param) - 2)
   553  
   554  	return i.RawSendRecv(msg)
   555  }