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

     1  // Copyright 2019-2022 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  	"math/rand"
    17  	"syscall"
    18  	"time"
    19  	"unsafe"
    20  
    21  	"github.com/vtolstov/go-ioctl"
    22  )
    23  
    24  var (
    25  	_IPMICTL_RECEIVE_MSG_TRUNC = ioctl.IOWR(_IPMI_IOC_MAGIC, 11, uintptr(unsafe.Sizeof(response{})))
    26  	_IPMICTL_SEND_COMMAND      = ioctl.IOR(_IPMI_IOC_MAGIC, 13, uintptr(unsafe.Sizeof(request{})))
    27  
    28  	timeout = time.Second * 10
    29  )
    30  
    31  // IPMI represents access to the IPMI interface.
    32  type IPMI struct {
    33  	*dev
    34  }
    35  
    36  // SendRecv sends the IPMI message, receives the response, and returns the
    37  // response data. This is recommended for use unless the user must be able to
    38  // specify the data pointer and length on their own.
    39  func (i *IPMI) SendRecv(netfn NetFn, cmd Command, data []byte) ([]byte, error) {
    40  	var dataPtr unsafe.Pointer
    41  	if data != nil {
    42  		dataPtr = unsafe.Pointer(&data[0])
    43  	}
    44  	msg := Msg{
    45  		Netfn:   netfn,
    46  		Cmd:     cmd,
    47  		Data:    dataPtr,
    48  		DataLen: uint16(len(data)),
    49  	}
    50  	return i.RawSendRecv(msg)
    51  }
    52  
    53  // RawSendRecv sends the IPMI message, receives the response, and returns the
    54  // response data.
    55  func (i *IPMI) RawSendRecv(msg Msg) ([]byte, error) {
    56  	addr := &systemInterfaceAddr{
    57  		addrType: _IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
    58  		channel:  _IPMI_BMC_CHANNEL,
    59  	}
    60  	req := &request{
    61  		addr:    addr,
    62  		addrLen: uint32(unsafe.Sizeof(addr)),
    63  		msgid:   rand.Int63(),
    64  		msg:     msg,
    65  	}
    66  
    67  	// Send request.
    68  	for {
    69  		switch err := i.dev.SendRequest(req); {
    70  		case err == syscall.EINTR:
    71  			continue
    72  		case err != nil:
    73  			return nil, fmt.Errorf("ioctlSetReq failed with %v", err)
    74  		}
    75  		break
    76  	}
    77  
    78  	buf := make([]byte, _IPMI_BUF_SIZE)
    79  	recvMsg := Msg{
    80  		Data:    unsafe.Pointer(&buf[0]),
    81  		DataLen: _IPMI_BUF_SIZE,
    82  	}
    83  	recv := &response{
    84  		addr:    req.addr,
    85  		addrLen: req.addrLen,
    86  		msg:     recvMsg,
    87  	}
    88  
    89  	return i.dev.ReceiveResponse(req.msgid, recv, buf)
    90  }
    91  
    92  func (i *IPMI) WatchdogRunning() (bool, error) {
    93  	recv, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_WATCHDOG_TIMER, nil)
    94  	if err != nil {
    95  		return false, err
    96  	}
    97  
    98  	if len(recv) > 2 && (recv[1]&0x40) != 0 {
    99  		return true, nil
   100  	}
   101  
   102  	return false, nil
   103  }
   104  
   105  func (i *IPMI) ShutoffWatchdog() error {
   106  	var data [6]byte
   107  	data[0] = IPM_WATCHDOG_SMS_OS
   108  	data[1] = IPM_WATCHDOG_NO_ACTION
   109  	data[2] = 0x00 // pretimeout interval
   110  	data[3] = IPM_WATCHDOG_CLEAR_SMS_OS
   111  	data[4] = 0xb8 // countdown lsb (100 ms/count)
   112  	data[5] = 0x0b // countdown msb - 5 mins
   113  
   114  	_, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_WATCHDOG_TIMER, data[:6])
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  // marshall converts the Event struct to binary data and the content of returned data is based on the record type
   123  func (e *Event) marshall() ([]byte, error) {
   124  	buf := &bytes.Buffer{}
   125  
   126  	if err := binary.Write(buf, binary.LittleEndian, *e); err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	data := make([]byte, 16)
   131  
   132  	// system event record
   133  	if buf.Bytes()[2] == 0x2 {
   134  		copy(data[:], buf.Bytes()[:16])
   135  	}
   136  
   137  	// OEM timestamped
   138  	if buf.Bytes()[2] >= 0xC0 && buf.Bytes()[2] <= 0xDF {
   139  		copy(data[0:3], buf.Bytes()[0:3])
   140  		copy(data[3:16], buf.Bytes()[16:29])
   141  	}
   142  
   143  	// OEM non-timestamped
   144  	if buf.Bytes()[2] >= 0xE0 && buf.Bytes()[2] <= 0xFF {
   145  		copy(data[0:3], buf.Bytes()[0:3])
   146  		copy(data[3:16], buf.Bytes()[29:42])
   147  	}
   148  
   149  	return data, nil
   150  }
   151  
   152  // LogSystemEvent adds an SEL (System Event Log) entry.
   153  func (i *IPMI) LogSystemEvent(e *Event) error {
   154  	data, err := e.marshall()
   155  	if err != nil {
   156  		return err
   157  	}
   158  	_, err = i.SendRecv(_IPMI_NETFN_STORAGE, BMC_ADD_SEL, data)
   159  	return err
   160  }
   161  
   162  func (i *IPMI) setsysinfo(data *setSystemInfoReq) error {
   163  	msg := Msg{
   164  		Cmd:     SET_SYSTEM_INFO_PARAMETERS,
   165  		Netfn:   _IPMI_NETFN_APP,
   166  		Data:    unsafe.Pointer(data),
   167  		DataLen: 18,
   168  	}
   169  
   170  	if _, err := i.RawSendRecv(msg); err != nil {
   171  		return err
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func strcpyPadded(dst []byte, src string) {
   178  	dstLen := len(dst)
   179  	if copied := copy(dst, src); copied < dstLen {
   180  		padding := make([]byte, dstLen-copied)
   181  		copy(dst[copied:], padding)
   182  	}
   183  }
   184  
   185  // SetSystemFWVersion sets the provided system firmware version to BMC via IPMI.
   186  func (i *IPMI) SetSystemFWVersion(version string) error {
   187  	len := len(version)
   188  
   189  	if len == 0 {
   190  		return fmt.Errorf("version length is 0")
   191  	} else if len > strlenMax {
   192  		len = strlenMax
   193  	}
   194  
   195  	var data setSystemInfoReq
   196  	var index int
   197  	data.paramSelector = _SYSTEM_FW_VERSION
   198  	data.setSelector = 0
   199  	for len > index {
   200  		if data.setSelector == 0 { // the fisrt block of string data
   201  			data.strData[0] = _ASCII
   202  			data.strData[1] = byte(len)
   203  			strcpyPadded(data.strData[2:], version)
   204  			index += _SYSTEM_INFO_BLK_SZ - 2
   205  		} else {
   206  			strcpyPadded(data.strData[:], version)
   207  			index += _SYSTEM_INFO_BLK_SZ
   208  		}
   209  
   210  		if err := i.setsysinfo(&data); err != nil {
   211  			return err
   212  		}
   213  		data.setSelector++
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  func (i *IPMI) GetDeviceID() (*DevID, error) {
   220  	data, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_DEVICE_ID, nil)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	buf := bytes.NewReader(data[1:])
   225  	mcInfo := DevID{}
   226  
   227  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.DeviceID); err != nil {
   228  		return nil, err
   229  	}
   230  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.DeviceRevision); err != nil {
   231  		return nil, err
   232  	}
   233  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.FwRev1); err != nil {
   234  		return nil, err
   235  	}
   236  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.FwRev2); err != nil {
   237  		return nil, err
   238  	}
   239  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.IpmiVersion); err != nil {
   240  		return nil, err
   241  	}
   242  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.AdtlDeviceSupport); err != nil {
   243  		return nil, err
   244  	}
   245  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.ManufacturerID); err != nil {
   246  		return nil, err
   247  	}
   248  	if err := binary.Read(buf, binary.LittleEndian, &mcInfo.ProductID); err != nil {
   249  		return nil, err
   250  	}
   251  	// In some cases we have 11 bytes, in others we may have 15 bytes. Carefully parsing the struct is important here.
   252  	if buf.Len() > 0 {
   253  		if err := binary.Read(buf, binary.LittleEndian, &mcInfo.AuxFwRev); err != nil {
   254  			return nil, err
   255  		}
   256  	}
   257  
   258  	return &mcInfo, nil
   259  }
   260  
   261  func (i *IPMI) setGlobalEnables(enables byte) error {
   262  	_, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_GLOBAL_ENABLES, []byte{enables})
   263  	return err
   264  }
   265  
   266  func (i *IPMI) getGlobalEnables() ([]byte, error) {
   267  	return i.SendRecv(_IPMI_NETFN_APP, BMC_GET_GLOBAL_ENABLES, nil)
   268  }
   269  
   270  func (i *IPMI) EnableSEL() (bool, error) {
   271  	// Check if SEL device is supported or not
   272  	mcInfo, err := i.GetDeviceID()
   273  
   274  	if err != nil {
   275  		return false, err
   276  	} else if (mcInfo.AdtlDeviceSupport & ADTL_SEL_DEVICE) == 0 {
   277  		return false, nil
   278  	}
   279  
   280  	data, err := i.getGlobalEnables()
   281  	if err != nil {
   282  		return false, err
   283  	}
   284  
   285  	if (data[1] & EN_SYSTEM_EVENT_LOGGING) == 0 {
   286  		// SEL is not enabled, enable SEL
   287  		if err = i.setGlobalEnables(data[1] | EN_SYSTEM_EVENT_LOGGING); err != nil {
   288  			return false, err
   289  		}
   290  	}
   291  
   292  	return true, nil
   293  }
   294  
   295  func (i *IPMI) GetChassisStatus() (*ChassisStatus, error) {
   296  	data, err := i.SendRecv(_IPMI_NETFN_CHASSIS, BMC_GET_CHASSIS_STATUS, nil)
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	buf := bytes.NewReader(data[1:])
   302  
   303  	var status ChassisStatus
   304  	if err := binary.Read(buf, binary.LittleEndian, &status); err != nil {
   305  		return nil, err
   306  	}
   307  	return &status, nil
   308  }
   309  
   310  func (i *IPMI) GetSELInfo() (*SELInfo, error) {
   311  	data, err := i.SendRecv(_IPMI_NETFN_STORAGE, BMC_GET_SEL_INFO, nil)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	buf := bytes.NewReader(data[1:])
   317  
   318  	var info SELInfo
   319  	if err := binary.Read(buf, binary.LittleEndian, &info); err != nil {
   320  		return nil, err
   321  	}
   322  	return &info, nil
   323  }
   324  
   325  func (i *IPMI) GetLanConfig(channel byte, param byte) ([]byte, error) {
   326  	var data [4]byte
   327  	data[0] = channel
   328  	data[1] = param
   329  	data[2] = 0
   330  	data[3] = 0
   331  
   332  	return i.SendRecv(_IPMI_NETFN_TRANSPORT, BMC_GET_LAN_CONFIG, data[:4])
   333  }
   334  
   335  func (i *IPMI) RawCmd(param []byte) ([]byte, error) {
   336  	if len(param) < 2 {
   337  		return nil, errors.New("not enough parameters given")
   338  	}
   339  
   340  	msg := Msg{
   341  		Netfn: NetFn(param[0]),
   342  		Cmd:   Command(param[1]),
   343  	}
   344  	if len(param) > 2 {
   345  		msg.Data = unsafe.Pointer(&param[2])
   346  	}
   347  
   348  	msg.DataLen = uint16(len(param) - 2)
   349  
   350  	return i.RawSendRecv(msg)
   351  }
   352  
   353  // Close closes the file attached to ipmi
   354  func (i *IPMI) Close() error {
   355  	return i.dev.Close()
   356  }