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