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 }