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