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

     1  // Copyright 2020 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 ocp implements OCP/Facebook-specific IPMI client functions.
     6  package ocp
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"unsafe"
    16  
    17  	"github.com/mvdan/u-root-coreutils/pkg/ipmi"
    18  	"github.com/mvdan/u-root-coreutils/pkg/pci"
    19  	"github.com/mvdan/u-root-coreutils/pkg/smbios"
    20  )
    21  
    22  const (
    23  	_IPMI_FB_OEM_NET_FUNCTION1 ipmi.NetFn = 0x30
    24  	_IPMI_FB_OEM_NET_FUNCTION2 ipmi.NetFn = 0x36
    25  
    26  	_FB_OEM_SET_PROC_INFO       ipmi.Command = 0x10
    27  	_FB_OEM_SET_DIMM_INFO       ipmi.Command = 0x12
    28  	_FB_OEM_SET_BOOT_DRIVE_INFO ipmi.Command = 0x14
    29  	_FB_OEM_SET_BIOS_BOOT_ORDER ipmi.Command = 0x52
    30  	_FB_OEM_GET_BIOS_BOOT_ORDER ipmi.Command = 0x53
    31  	_FB_OEM_SET_POST_END        ipmi.Command = 0x74
    32  )
    33  
    34  type ProcessorInfo struct {
    35  	ManufacturerID        [3]uint8
    36  	Index                 uint8
    37  	ParameterSelector     uint8
    38  	ProductName           [48]byte
    39  	CoreNumber            uint8
    40  	ThreadNumberLSB       uint8
    41  	ThreadNumberMSB       uint8
    42  	ProcessorFrequencyLSB uint8
    43  	ProcessorFrequencyMSB uint8
    44  	Revision1             uint8
    45  	Revision2             uint8
    46  }
    47  
    48  type DimmInfo struct {
    49  	ManufacturerID          [3]uint8
    50  	Index                   uint8
    51  	ParameterSelector       uint8
    52  	DIMMPresent             uint8
    53  	NodeNumber              uint8
    54  	ChannelNumber           uint8
    55  	DIMMNumber              uint8
    56  	DIMMType                uint8
    57  	DIMMSpeed               uint16
    58  	DIMMSize                uint32
    59  	ModulePartNumber        [20]byte
    60  	ModuleSerialNumber      uint32
    61  	ModuleManufacturerIDLSB uint8
    62  	ModuleManufacturerIDMSB uint8
    63  }
    64  
    65  type BootDriveInfo struct {
    66  	ManufacturerID    [3]uint8
    67  	ControlType       uint8
    68  	DriveNumber       uint8
    69  	ParameterSelector uint8
    70  	VendorID          uint16
    71  	DeviceID          uint16
    72  }
    73  
    74  // OENMap maps OEM names to a 3 byte OEM number.
    75  //
    76  // OENs are typically serialized as the first 3 bytes of a request body.
    77  var OENMap = map[string][3]uint8{
    78  	"Wiwynn": {0x0, 0x9c, 0x9c},
    79  }
    80  
    81  func SendOemIpmiProcessorInfo(i *ipmi.IPMI, info []ProcessorInfo) error {
    82  	for index := 0; index < len(info); index++ {
    83  		for param := 1; param <= 2; param++ {
    84  			data, err := info[index].marshall(param)
    85  			if err != nil {
    86  				return err
    87  			}
    88  
    89  			_, err = i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION2, _FB_OEM_SET_PROC_INFO, data)
    90  			if err != nil {
    91  				return err
    92  			}
    93  		}
    94  	}
    95  	return nil
    96  }
    97  
    98  func SendOemIpmiDimmInfo(i *ipmi.IPMI, info []DimmInfo) error {
    99  	for index := 0; index < len(info); index++ {
   100  		for param := 1; param <= 6; param++ {
   101  			// If DIMM is not present, only send the information of DIMM location
   102  			if info[index].DIMMPresent != 0x01 && param >= 2 {
   103  				continue
   104  			}
   105  
   106  			data, err := info[index].marshall(param)
   107  			if err != nil {
   108  				return err
   109  			}
   110  			_, err = i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION2, _FB_OEM_SET_DIMM_INFO, data)
   111  			if err != nil {
   112  				return err
   113  			}
   114  		}
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  func SendOemIpmiBootDriveInfo(i *ipmi.IPMI, info *BootDriveInfo) error {
   121  	var data []byte
   122  	buf := &bytes.Buffer{}
   123  	err := binary.Write(buf, binary.LittleEndian, *info)
   124  
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	data = make([]byte, 10)
   130  	copy(data, buf.Bytes())
   131  
   132  	_, err = i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION2, _FB_OEM_SET_BOOT_DRIVE_INFO, data)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	return nil
   137  }
   138  
   139  func (p *ProcessorInfo) marshall(param int) ([]byte, error) {
   140  	var data []byte
   141  	buf := &bytes.Buffer{}
   142  
   143  	if err := binary.Write(buf, binary.LittleEndian, *p); err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	buf.Bytes()[4] = byte(param)
   148  
   149  	switch param {
   150  	case 1:
   151  		data = make([]byte, 53)
   152  		copy(data[:], buf.Bytes()[:53])
   153  	case 2:
   154  		data = make([]byte, 12)
   155  		copy(data[0:5], buf.Bytes()[0:5])
   156  		copy(data[5:12], buf.Bytes()[53:60])
   157  	}
   158  
   159  	return data, nil
   160  }
   161  
   162  func (d *DimmInfo) marshall(param int) ([]byte, error) {
   163  	var data []byte
   164  	buf := &bytes.Buffer{}
   165  
   166  	if err := binary.Write(buf, binary.LittleEndian, *d); err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	buf.Bytes()[4] = byte(param)
   171  
   172  	switch param {
   173  	case 1:
   174  		data = make([]byte, 9)
   175  		copy(data[:], buf.Bytes()[:9])
   176  	case 2:
   177  		data = make([]byte, 6)
   178  		copy(data[0:5], buf.Bytes()[0:5])
   179  		copy(data[5:6], buf.Bytes()[9:10])
   180  	case 3:
   181  		data = make([]byte, 11)
   182  		copy(data[0:5], buf.Bytes()[0:5])
   183  		copy(data[5:11], buf.Bytes()[10:16])
   184  	case 4:
   185  		data = make([]byte, 25)
   186  		copy(data[0:5], buf.Bytes()[0:5])
   187  		copy(data[5:25], buf.Bytes()[16:36])
   188  	case 5:
   189  		data = make([]byte, 9)
   190  		copy(data[0:5], buf.Bytes()[0:5])
   191  		copy(data[5:9], buf.Bytes()[36:40])
   192  	case 6:
   193  		data = make([]byte, 7)
   194  		copy(data[0:5], buf.Bytes()[0:5])
   195  		copy(data[5:7], buf.Bytes()[40:42])
   196  	}
   197  
   198  	return data, nil
   199  }
   200  
   201  func GetOemIpmiProcessorInfo(si *smbios.Info) ([]ProcessorInfo, error) {
   202  	t1, err := si.GetSystemInfo()
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	t4, err := si.GetProcessorInfo()
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	info := make([]ProcessorInfo, len(t4))
   213  
   214  	boardManufacturerID, ok := OENMap[t1.Manufacturer]
   215  
   216  	for index := 0; index < len(t4); index++ {
   217  		if ok {
   218  			info[index].ManufacturerID = boardManufacturerID
   219  		}
   220  
   221  		info[index].Index = uint8(index)
   222  		copy(info[index].ProductName[:], t4[index].Version)
   223  		info[index].CoreNumber = uint8(t4[index].GetCoreCount())
   224  		info[index].ThreadNumberLSB = uint8(t4[index].GetThreadCount() & 0x00ff)
   225  		info[index].ThreadNumberMSB = uint8(t4[index].GetThreadCount() >> 8)
   226  		info[index].ProcessorFrequencyLSB = uint8(t4[index].CurrentSpeed & 0x00ff)
   227  		info[index].ProcessorFrequencyMSB = uint8(t4[index].CurrentSpeed >> 8)
   228  		info[index].Revision1 = 0
   229  		info[index].Revision2 = 0
   230  	}
   231  
   232  	return info, nil
   233  }
   234  
   235  // DIMM type: bit[7:6] for DDR3 00-Normal Voltage(1.5V), 01-Ultra Low Voltage(1.25V), 10-Low Voltage(1.35V), 11-Reserved
   236  //
   237  //	                    for DDR4 00~10-Reserved, 11-Normal Voltage(1.2V)
   238  //	           bit[5:0] 0x00=SDRAM, 0x01=DDR1 RAM, 0x02-Rambus, 0x03-DDR2 RAM, 0x04-FBDIMM, 0x05-DDR3 RAM, 0x06-DDR4 RAM
   239  //			       , 0x07-DDR5 RAM
   240  func detectDimmType(meminfo *DimmInfo, t17 *smbios.MemoryDevice) {
   241  	if t17.Type == smbios.MemoryDeviceTypeDDR3 {
   242  		switch t17.ConfiguredVoltage {
   243  		case 1500:
   244  			meminfo.DIMMType = 0x05
   245  		case 1250:
   246  			meminfo.DIMMType = 0x45
   247  		case 1350:
   248  			meminfo.DIMMType = 0x85
   249  		default:
   250  			meminfo.DIMMType = 0x05
   251  		}
   252  	} else {
   253  		switch t17.Type {
   254  		case smbios.MemoryDeviceTypeSDRAM:
   255  			meminfo.DIMMType = 0x00
   256  		case smbios.MemoryDeviceTypeDDR:
   257  			meminfo.DIMMType = 0x01
   258  		case smbios.MemoryDeviceTypeRDRAM:
   259  			meminfo.DIMMType = 0x02
   260  		case smbios.MemoryDeviceTypeDDR2:
   261  			meminfo.DIMMType = 0x03
   262  		case smbios.MemoryDeviceTypeDDR2FBDIMM:
   263  			meminfo.DIMMType = 0x04
   264  		case smbios.MemoryDeviceTypeDDR4:
   265  			meminfo.DIMMType = 0xC6
   266  		case smbios.MemoryDeviceTypeDDR5:
   267  			meminfo.DIMMType = 0xC7
   268  		default:
   269  			meminfo.DIMMType = 0xC6
   270  		}
   271  	}
   272  }
   273  
   274  func GetOemIpmiDimmInfo(si *smbios.Info) ([]DimmInfo, error) {
   275  	t1, err := si.GetSystemInfo()
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	t17, err := si.GetMemoryDevices()
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	info := make([]DimmInfo, len(t17))
   286  
   287  	boardManufacturerID, ok := OENMap[t1.Manufacturer]
   288  
   289  	for index := 0; index < len(t17); index++ {
   290  		if ok {
   291  			info[index].ManufacturerID = boardManufacturerID
   292  		}
   293  
   294  		info[index].Index = uint8(index)
   295  
   296  		if t17[index].AssetTag == "NO DIMM" {
   297  			info[index].DIMMPresent = 0xFF // 0xFF - Not Present
   298  		} else {
   299  			info[index].DIMMPresent = 0x01 // 0x01 - Present
   300  		}
   301  
   302  		data := strings.Split(strings.TrimPrefix(t17[index].BankLocator, "_"), "_")
   303  		dimm, _ := strconv.ParseUint(strings.TrimPrefix(data[2], "Dimm"), 16, 8)
   304  		channel, _ := strconv.ParseUint(strings.TrimPrefix(data[1], "Channel"), 16, 8)
   305  		node, _ := strconv.ParseUint(strings.TrimPrefix(data[0], "Node"), 16, 8)
   306  		info[index].DIMMNumber = uint8(dimm)
   307  		info[index].ChannelNumber = uint8(channel)
   308  		info[index].NodeNumber = uint8(node)
   309  		detectDimmType(&info[index], t17[index])
   310  		info[index].DIMMSpeed = t17[index].Speed
   311  		info[index].DIMMSize = uint32(t17[index].Size)
   312  		copy(info[index].ModulePartNumber[:], t17[index].PartNumber)
   313  		sn, _ := strconv.ParseUint(t17[index].SerialNumber, 16, 32)
   314  		info[index].ModuleSerialNumber = uint32(sn)
   315  		memoryDeviceManufacturerID, ok := smbios.MemoryDeviceManufacturer[t17[index].Manufacturer]
   316  		if ok {
   317  			info[index].ModuleManufacturerIDLSB = uint8(memoryDeviceManufacturerID & 0x00ff)
   318  			info[index].ModuleManufacturerIDMSB = uint8(memoryDeviceManufacturerID >> 8)
   319  		}
   320  	}
   321  
   322  	return info, nil
   323  }
   324  
   325  // Read type 9 from SMBIOS tables and look for SlotDesignation which contains string 'Boot_Drive',
   326  // and get bus and device number from the matched table to read the Device ID and Vendor ID of
   327  // the boot drive for sending the IPMI OEM command.
   328  // This requires the BDF number is correctly set in the type 9 table.
   329  func GetOemIpmiBootDriveInfo(si *smbios.Info) (*BootDriveInfo, error) {
   330  	t1, err := si.GetSystemInfo()
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	t9, err := si.GetSystemSlots()
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	const systemPath = "/sys/bus/pci/devices/"
   341  	const bootDriveName = "Boot_Drive"
   342  	var info BootDriveInfo
   343  
   344  	if boardManufacturerID, ok := OENMap[t1.Manufacturer]; ok == true {
   345  		info.ManufacturerID = boardManufacturerID
   346  	}
   347  
   348  	for index := 0; index < len(t9); index++ {
   349  		if strings.Contains(t9[index].SlotDesignation, bootDriveName) == false {
   350  			continue
   351  		}
   352  
   353  		deviceNumber := t9[index].DeviceFunctionNumber >> 3
   354  		devicePath := fmt.Sprintf("%04d:%02x:%02x.0",
   355  			t9[index].SegmentGroupNumber, t9[index].BusNumber, deviceNumber)
   356  		p, err := pci.OnePCI(filepath.Join(systemPath, devicePath))
   357  		if err != nil {
   358  			return nil, err
   359  		}
   360  
   361  		info.VendorID = p.Vendor
   362  		info.DeviceID = p.Device
   363  		info.ControlType = 0
   364  		info.DriveNumber = 0
   365  		info.ParameterSelector = 2
   366  		// There will only be one Boot_Drive
   367  		return &info, nil
   368  	}
   369  	return nil, nil
   370  }
   371  
   372  func SetOemIpmiPostEnd(i *ipmi.IPMI) error {
   373  	_, err := i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION1, _FB_OEM_SET_POST_END, nil)
   374  	if err != nil {
   375  		return err
   376  	}
   377  	return nil
   378  }
   379  
   380  // Get BIOS boot order data and check if CMOS clear bit and valid bit are both set
   381  func IsCMOSClearSet(i *ipmi.IPMI) (bool, []byte, error) {
   382  	recv, err := i.SendRecv(_IPMI_FB_OEM_NET_FUNCTION1, _FB_OEM_GET_BIOS_BOOT_ORDER, nil)
   383  	if err != nil {
   384  		return false, nil, err
   385  	}
   386  	// recv[1] bit 1: CMOS clear, bit 7: valid bit, check if both are set
   387  	if len(recv) > 6 && (recv[1]&0x82) == 0x82 {
   388  		return true, recv[1:], nil
   389  	}
   390  	return false, nil, nil
   391  }
   392  
   393  // Set BIOS boot order with both CMOS clear and valid bits cleared
   394  func ClearCMOSClearValidBits(i *ipmi.IPMI, data []byte) error {
   395  	// Clear bit 1 and bit 7
   396  	data[0] &= 0x7d
   397  
   398  	msg := ipmi.Msg{
   399  		Netfn:   _IPMI_FB_OEM_NET_FUNCTION1,
   400  		Cmd:     _FB_OEM_SET_BIOS_BOOT_ORDER,
   401  		Data:    unsafe.Pointer(&data[0]),
   402  		DataLen: 6,
   403  	}
   404  
   405  	if _, err := i.RawSendRecv(msg); err != nil {
   406  		return err
   407  	}
   408  	return nil
   409  }