github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+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
     6  // the OpenIPMI driver interface.
     7  package ipmi
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/binary"
    12  	"fmt"
    13  	"os"
    14  	"syscall"
    15  	"unsafe"
    16  )
    17  
    18  const (
    19  	_IPMI_BMC_CHANNEL                = 0xf
    20  	_IPMI_BUF_SIZE                   = 1024
    21  	_IPMI_IOC_MAGIC                  = 'i'
    22  	_IPMI_NETFN_CHASSIS              = 0x0
    23  	_IPMI_NETFN_APP                  = 0x6
    24  	_IPMI_NETFN_STORAGE              = 0xA
    25  	_IPMI_OPENIPMI_READ_TIMEOUT      = 15
    26  	_IPMI_SYSTEM_INTERFACE_ADDR_TYPE = 0x0c
    27  
    28  	// IPM Device "Global" Commands
    29  	_BMC_GET_DEVICE_ID = 0x01
    30  
    31  	// BMC Device and Messaging Commands
    32  	_BMC_SET_WATCHDOG_TIMER     = 0x24
    33  	_BMC_GET_WATCHDOG_TIMER     = 0x25
    34  	_BMC_SET_GLOBAL_ENABLES     = 0x2E
    35  	_BMC_GET_GLOBAL_ENABLES     = 0x2F
    36  	_SET_SYSTEM_INFO_PARAMETERS = 0x58
    37  	_BMC_ADD_SEL                = 0x44
    38  
    39  	// Chassis Device Commands
    40  	_BMC_GET_CHASSIS_STATUS = 0x01
    41  
    42  	// SEL device Commands
    43  	_BMC_GET_SEL_INFO = 0x40
    44  
    45  	_IPM_WATCHDOG_NO_ACTION    = 0x00
    46  	_IPM_WATCHDOG_SMS_OS       = 0x04
    47  	_IPM_WATCHDOG_CLEAR_SMS_OS = 0x10
    48  
    49  	_ADTL_SEL_DEVICE         = 0x04
    50  	_EN_SYSTEM_EVENT_LOGGING = 0x08
    51  
    52  	_SYSTEM_INFO_BLK_SZ = 16
    53  
    54  	_SYSTEM_FW_VERSION = 1
    55  
    56  	_ASCII = 0
    57  
    58  	// Set 62 Bytes (4 sets) as the maximal string length
    59  	strlenMax = 62
    60  )
    61  
    62  var (
    63  	_IPMICTL_RECEIVE_MSG  = iowr(_IPMI_IOC_MAGIC, 12, int(unsafe.Sizeof(recv{})))
    64  	_IPMICTL_SEND_COMMAND = ior(_IPMI_IOC_MAGIC, 13, int(unsafe.Sizeof(req{})))
    65  )
    66  
    67  type IPMI struct {
    68  	*os.File
    69  }
    70  
    71  type msg struct {
    72  	netfn   byte
    73  	cmd     byte
    74  	dataLen uint16
    75  	data    unsafe.Pointer
    76  }
    77  
    78  type req struct {
    79  	addr    *systemInterfaceAddr
    80  	addrLen uint32
    81  	msgid   int64 //nolint:structcheck
    82  	msg     msg
    83  }
    84  
    85  type recv struct {
    86  	recvType int32 //nolint:structcheck
    87  	addr     *systemInterfaceAddr
    88  	addrLen  uint32
    89  	msgid    int64 //nolint:structcheck
    90  	msg      msg
    91  }
    92  
    93  type systemInterfaceAddr struct {
    94  	addrType int32
    95  	channel  int16
    96  	lun      byte //nolint:unused
    97  }
    98  
    99  // StandardEvent is a standard systemevent.
   100  //
   101  // The data in this event should follow IPMI spec
   102  type StandardEvent struct {
   103  	Timestamp    uint32
   104  	GenID        uint16
   105  	EvMRev       uint8
   106  	SensorType   uint8
   107  	SensorNum    uint8
   108  	EventTypeDir uint8
   109  	EventData    [3]uint8
   110  }
   111  
   112  // OEMTsEvent is a timestamped OEM-custom event.
   113  //
   114  // It holds 6 bytes of OEM-defined arbitrary data.
   115  type OEMTsEvent struct {
   116  	Timestamp        uint32
   117  	ManfID           [3]uint8
   118  	OEMTsDefinedData [6]uint8
   119  }
   120  
   121  // OEMNonTsEvent is a non-timestamped OEM-custom event.
   122  //
   123  // It holds 13 bytes of OEM-defined arbitrary data.
   124  type OEMNontsEvent struct {
   125  	OEMNontsDefinedData [13]uint8
   126  }
   127  
   128  // Event is included three kinds of events, Standard, OEM timestamped and OEM non-timestamped
   129  //
   130  // The record type decides which event should be used
   131  type Event struct {
   132  	RecordID   uint16
   133  	RecordType uint8
   134  	StandardEvent
   135  	OEMTsEvent
   136  	OEMNontsEvent
   137  }
   138  
   139  type setSystemInfoReq struct {
   140  	paramSelector byte
   141  	setSelector   byte
   142  	strData       [_SYSTEM_INFO_BLK_SZ]byte
   143  }
   144  
   145  type DevID struct {
   146  	DeviceID          byte
   147  	DeviceRevision    byte
   148  	FwRev1            byte
   149  	FwRev2            byte
   150  	IpmiVersion       byte
   151  	AdtlDeviceSupport byte
   152  	ManufacturerID    [3]byte
   153  	ProductID         [2]byte
   154  	AuxFwRev          [4]byte
   155  }
   156  
   157  type ChassisStatus struct {
   158  	CurrentPowerState byte
   159  	LastPowerEvent    byte
   160  	MiscChassisState  byte
   161  	FrontPanelButton  byte
   162  }
   163  
   164  type SELInfo struct {
   165  	Version     byte
   166  	Entries     uint16
   167  	FreeSpace   uint16
   168  	LastAddTime uint32
   169  	LastDelTime uint32
   170  	OpSupport   byte
   171  }
   172  
   173  func fdSet(fd uintptr, p *syscall.FdSet) {
   174  	p.Bits[fd/64] |= 1 << (uint(fd) % 64)
   175  }
   176  
   177  func (i *IPMI) sendrecv(req *req) ([]byte, error) {
   178  	addr := systemInterfaceAddr{
   179  		addrType: _IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
   180  		channel:  _IPMI_BMC_CHANNEL,
   181  	}
   182  
   183  	req.addr = &addr
   184  	req.addrLen = uint32(unsafe.Sizeof(addr))
   185  	if err := ioctl(i.Fd(), _IPMICTL_SEND_COMMAND, unsafe.Pointer(req)); err != 0 {
   186  		return nil, err
   187  	}
   188  
   189  	set := &syscall.FdSet{}
   190  	fdSet(i.Fd(), set)
   191  	time := &syscall.Timeval{
   192  		Sec:  _IPMI_OPENIPMI_READ_TIMEOUT,
   193  		Usec: 0,
   194  	}
   195  	if _, err := syscall.Select(int(i.Fd()+1), set, nil, nil, time); err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	recv := &recv{}
   200  	recv.addr = &systemInterfaceAddr{}
   201  	recv.addrLen = uint32(unsafe.Sizeof(addr))
   202  	buf := make([]byte, _IPMI_BUF_SIZE)
   203  	recv.msg.data = unsafe.Pointer(&buf[0])
   204  	recv.msg.dataLen = _IPMI_BUF_SIZE
   205  	if err := ioctl(i.Fd(), _IPMICTL_RECEIVE_MSG, unsafe.Pointer(recv)); err != 0 {
   206  		return nil, err
   207  	}
   208  
   209  	return buf[:recv.msg.dataLen:recv.msg.dataLen], nil
   210  }
   211  
   212  func (i *IPMI) WatchdogRunning() (bool, error) {
   213  	req := &req{}
   214  	req.msg.cmd = _BMC_GET_WATCHDOG_TIMER
   215  	req.msg.netfn = _IPMI_NETFN_APP
   216  
   217  	recv, err := i.sendrecv(req)
   218  	if err != nil {
   219  		return false, err
   220  	}
   221  
   222  	if len(recv) > 2 && (recv[1]&0x40) != 0 {
   223  		return true, nil
   224  	}
   225  
   226  	return false, nil
   227  }
   228  
   229  func (i *IPMI) ShutoffWatchdog() error {
   230  	req := &req{}
   231  	req.msg.cmd = _BMC_SET_WATCHDOG_TIMER
   232  	req.msg.netfn = _IPMI_NETFN_APP
   233  
   234  	var data [6]byte
   235  	data[0] = _IPM_WATCHDOG_SMS_OS
   236  	data[1] = _IPM_WATCHDOG_NO_ACTION
   237  	data[2] = 0x00 // pretimeout interval
   238  	data[3] = _IPM_WATCHDOG_CLEAR_SMS_OS
   239  	data[4] = 0xb8 // countdown lsb (100 ms/count)
   240  	data[5] = 0x0b // countdown msb - 5 mins
   241  	req.msg.data = unsafe.Pointer(&data)
   242  	req.msg.dataLen = 6
   243  
   244  	_, err := i.sendrecv(req)
   245  	if err != nil {
   246  		return err
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  // marshall converts the Event struct to binary data and the content of returned data is based on the record type
   253  func (e *Event) marshall() ([]byte, error) {
   254  	buf := &bytes.Buffer{}
   255  
   256  	if err := binary.Write(buf, binary.LittleEndian, *e); err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	data := make([]byte, 16)
   261  
   262  	// system event record
   263  	if buf.Bytes()[2] == 0x2 {
   264  		copy(data[:], buf.Bytes()[:16])
   265  	}
   266  
   267  	// OEM timestamped
   268  	if buf.Bytes()[2] >= 0xC0 && buf.Bytes()[2] <= 0xDF {
   269  		copy(data[0:3], buf.Bytes()[0:3])
   270  		copy(data[3:16], buf.Bytes()[16:29])
   271  	}
   272  
   273  	// OEM non-timestamped
   274  	if buf.Bytes()[2] >= 0xE0 && buf.Bytes()[2] <= 0xFF {
   275  		copy(data[0:3], buf.Bytes()[0:3])
   276  		copy(data[3:16], buf.Bytes()[29:42])
   277  	}
   278  
   279  	return data, nil
   280  }
   281  
   282  // LogSystemEvent adds an SEL (System Event Log) entry.
   283  func (i *IPMI) LogSystemEvent(e *Event) error {
   284  	req := &req{}
   285  	req.msg.cmd = _BMC_ADD_SEL
   286  	req.msg.netfn = _IPMI_NETFN_STORAGE
   287  
   288  	data, err := e.marshall()
   289  
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	req.msg.data = unsafe.Pointer(&data[0])
   295  	req.msg.dataLen = 16
   296  
   297  	_, err = i.sendrecv(req)
   298  
   299  	return err
   300  }
   301  
   302  func (i *IPMI) setsysinfo(data *setSystemInfoReq) error {
   303  	req := &req{}
   304  	req.msg.cmd = _SET_SYSTEM_INFO_PARAMETERS
   305  	req.msg.netfn = _IPMI_NETFN_APP
   306  	req.msg.dataLen = 18 // size of setSystemInfoReq
   307  	req.msg.data = unsafe.Pointer(data)
   308  
   309  	if _, err := i.sendrecv(req); err != nil {
   310  		return err
   311  	}
   312  
   313  	return nil
   314  }
   315  
   316  func strcpyPadded(dst []byte, src string) {
   317  	dstLen := len(dst)
   318  	if copied := copy(dst, src); copied < dstLen {
   319  		padding := make([]byte, dstLen-copied)
   320  		copy(dst[copied:], padding)
   321  	}
   322  }
   323  
   324  // SetSystemFWVersion sets the provided system firmware version to BMC via IPMI.
   325  func (i *IPMI) SetSystemFWVersion(version string) error {
   326  	len := len(version)
   327  
   328  	if len == 0 {
   329  		return fmt.Errorf("Version length is 0")
   330  	} else if len > strlenMax {
   331  		len = strlenMax
   332  	}
   333  
   334  	var data setSystemInfoReq
   335  	var index int
   336  	data.paramSelector = _SYSTEM_FW_VERSION
   337  	data.setSelector = 0
   338  	for len > index {
   339  		if data.setSelector == 0 { // the fisrt block of string data
   340  			data.strData[0] = _ASCII
   341  			data.strData[1] = byte(len)
   342  			strcpyPadded(data.strData[2:], version)
   343  			index += _SYSTEM_INFO_BLK_SZ - 2
   344  		} else {
   345  			strcpyPadded(data.strData[:], version)
   346  			index += _SYSTEM_INFO_BLK_SZ
   347  		}
   348  
   349  		if err := i.setsysinfo(&data); err != nil {
   350  			return err
   351  		}
   352  		data.setSelector++
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  func (i *IPMI) GetDeviceID() (*DevID, error) {
   359  	req := &req{}
   360  	req.msg.netfn = _IPMI_NETFN_APP
   361  	req.msg.cmd = _BMC_GET_DEVICE_ID
   362  
   363  	data, err := i.sendrecv(req)
   364  
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  
   369  	buf := bytes.NewReader(data[1:])
   370  	mcInfo := DevID{}
   371  
   372  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo); err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	return &mcInfo, nil
   377  }
   378  
   379  func (i *IPMI) setGlobalEnables(enables byte) error {
   380  	req := &req{}
   381  	req.msg.netfn = _IPMI_NETFN_APP
   382  	req.msg.cmd = _BMC_SET_GLOBAL_ENABLES
   383  	req.msg.data = unsafe.Pointer(&enables)
   384  	req.msg.dataLen = 1
   385  
   386  	_, err := i.sendrecv(req)
   387  	return err
   388  }
   389  
   390  func (i *IPMI) getGlobalEnables() ([]byte, error) {
   391  	req := &req{}
   392  	req.msg.netfn = _IPMI_NETFN_APP
   393  	req.msg.cmd = _BMC_GET_GLOBAL_ENABLES
   394  
   395  	return i.sendrecv(req)
   396  }
   397  
   398  func (i *IPMI) EnableSEL() (bool, error) {
   399  	// Check if SEL device is supported or not
   400  	mcInfo, err := i.GetDeviceID()
   401  
   402  	if err != nil {
   403  		return false, err
   404  	} else if (mcInfo.AdtlDeviceSupport & _ADTL_SEL_DEVICE) == 0 {
   405  		return false, nil
   406  	}
   407  
   408  	data, err := i.getGlobalEnables()
   409  
   410  	if err != nil {
   411  		return false, err
   412  	}
   413  
   414  	if (data[1] & _EN_SYSTEM_EVENT_LOGGING) == 0 {
   415  		// SEL is not enabled, enable SEL
   416  		if err = i.setGlobalEnables(data[1] | _EN_SYSTEM_EVENT_LOGGING); err != nil {
   417  			return false, err
   418  		}
   419  	}
   420  
   421  	return true, nil
   422  }
   423  
   424  func (i *IPMI) GetChassisStatus() (*ChassisStatus, error) {
   425  	req := &req{}
   426  	req.msg.netfn = _IPMI_NETFN_CHASSIS
   427  	req.msg.cmd = _BMC_GET_CHASSIS_STATUS
   428  
   429  	data, err := i.sendrecv(req)
   430  	if err != nil {
   431  		return nil, err
   432  	}
   433  
   434  	buf := bytes.NewReader(data[1:])
   435  
   436  	var status ChassisStatus
   437  	if err := binary.Read(buf, binary.LittleEndian, &status); err != nil {
   438  		return nil, err
   439  	}
   440  	return &status, nil
   441  }
   442  
   443  func (i *IPMI) GetSELInfo() (*SELInfo, error) {
   444  	req := &req{}
   445  	req.msg.netfn = _IPMI_NETFN_STORAGE
   446  	req.msg.cmd = _BMC_GET_SEL_INFO
   447  
   448  	data, err := i.sendrecv(req)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	buf := bytes.NewReader(data[1:])
   454  
   455  	var info SELInfo
   456  	if err := binary.Read(buf, binary.LittleEndian, &info); err != nil {
   457  		return nil, err
   458  	}
   459  	return &info, nil
   460  }