github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/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_TRUNC = ioctl.IOWR(_IPMI_IOC_MAGIC, 11, uintptr(unsafe.Sizeof(recv{}))) 84 _IPMICTL_SEND_COMMAND = ioctl.IOR(_IPMI_IOC_MAGIC, 13, uintptr(unsafe.Sizeof(req{}))) 85 86 timeout = time.Second * 10 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 // Send request. 257 for { 258 switch err := ioctlSetReq(i.Fd(), _IPMICTL_SEND_COMMAND, req); { 259 case err == syscall.EINTR: 260 continue 261 case err != nil: 262 return nil, fmt.Errorf("ioctlSetReq failed with %v", err) 263 } 264 break 265 } 266 267 buf := make([]byte, _IPMI_BUF_SIZE) 268 recvMsg := Msg{ 269 Data: unsafe.Pointer(&buf[0]), 270 DataLen: _IPMI_BUF_SIZE, 271 } 272 recv := &recv{ 273 addr: req.addr, 274 addrLen: req.addrLen, 275 msg: recvMsg, 276 } 277 278 // This will be passed to the RawConn Read function. Until readMsg returns 279 // true, Read blocks and retries until the connection is ready for reading. 280 var result []byte 281 var rerr error 282 readMsg := func(fd uintptr) bool { 283 if err := ioctlGetRecv(fd, _IPMICTL_RECEIVE_MSG_TRUNC, recv); err != nil { 284 rerr = fmt.Errorf("ioctlGetRecv failed with %v", err) 285 return false 286 } 287 288 if recv.msgid != req.msgid { 289 log.Printf("Received wrong message. Trying again.") 290 return false 291 } 292 293 if recv.msg.DataLen >= _IPMI_BUF_SIZE { 294 rerr = fmt.Errorf("data length received too large: %d > %d", recv.msg.DataLen, _IPMI_BUF_SIZE) 295 } else if buf[0] != 0 { 296 rerr = fmt.Errorf("invalid response, expected first byte of response to be 0, got: %v", buf[0]) 297 } else { 298 result = buf[:recv.msg.DataLen:recv.msg.DataLen] 299 rerr = nil 300 } 301 return true 302 } 303 304 // Read response. 305 conn, err := i.File.SyscallConn() 306 if err != nil { 307 return nil, fmt.Errorf("failed to get file rawconn: %v", err) 308 } 309 if err := i.File.SetReadDeadline(time.Now().Add(timeout)); err != nil { 310 return nil, fmt.Errorf("failed to set read deadline: %v", err) 311 } 312 if err := conn.Read(readMsg); err != nil { 313 return nil, fmt.Errorf("failed to read rawconn: %v", err) 314 } 315 316 return result, rerr 317 } 318 319 func (i *IPMI) WatchdogRunning() (bool, error) { 320 recv, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_WATCHDOG_TIMER, nil) 321 if err != nil { 322 return false, err 323 } 324 325 if len(recv) > 2 && (recv[1]&0x40) != 0 { 326 return true, nil 327 } 328 329 return false, nil 330 } 331 332 func (i *IPMI) ShutoffWatchdog() error { 333 var data [6]byte 334 data[0] = IPM_WATCHDOG_SMS_OS 335 data[1] = IPM_WATCHDOG_NO_ACTION 336 data[2] = 0x00 // pretimeout interval 337 data[3] = IPM_WATCHDOG_CLEAR_SMS_OS 338 data[4] = 0xb8 // countdown lsb (100 ms/count) 339 data[5] = 0x0b // countdown msb - 5 mins 340 341 _, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_WATCHDOG_TIMER, data[:6]) 342 if err != nil { 343 return err 344 } 345 346 return nil 347 } 348 349 // marshall converts the Event struct to binary data and the content of returned data is based on the record type 350 func (e *Event) marshall() ([]byte, error) { 351 buf := &bytes.Buffer{} 352 353 if err := binary.Write(buf, binary.LittleEndian, *e); err != nil { 354 return nil, err 355 } 356 357 data := make([]byte, 16) 358 359 // system event record 360 if buf.Bytes()[2] == 0x2 { 361 copy(data[:], buf.Bytes()[:16]) 362 } 363 364 // OEM timestamped 365 if buf.Bytes()[2] >= 0xC0 && buf.Bytes()[2] <= 0xDF { 366 copy(data[0:3], buf.Bytes()[0:3]) 367 copy(data[3:16], buf.Bytes()[16:29]) 368 } 369 370 // OEM non-timestamped 371 if buf.Bytes()[2] >= 0xE0 && buf.Bytes()[2] <= 0xFF { 372 copy(data[0:3], buf.Bytes()[0:3]) 373 copy(data[3:16], buf.Bytes()[29:42]) 374 } 375 376 return data, nil 377 } 378 379 // LogSystemEvent adds an SEL (System Event Log) entry. 380 func (i *IPMI) LogSystemEvent(e *Event) error { 381 data, err := e.marshall() 382 383 if err != nil { 384 return err 385 } 386 _, err = i.SendRecv(_IPMI_NETFN_STORAGE, BMC_ADD_SEL, data) 387 return err 388 } 389 390 func (i *IPMI) setsysinfo(data *setSystemInfoReq) error { 391 msg := Msg{ 392 Cmd: SET_SYSTEM_INFO_PARAMETERS, 393 Netfn: _IPMI_NETFN_APP, 394 Data: unsafe.Pointer(data), 395 DataLen: 18, 396 } 397 398 if _, err := i.RawSendRecv(msg); err != nil { 399 return err 400 } 401 402 return nil 403 } 404 405 func strcpyPadded(dst []byte, src string) { 406 dstLen := len(dst) 407 if copied := copy(dst, src); copied < dstLen { 408 padding := make([]byte, dstLen-copied) 409 copy(dst[copied:], padding) 410 } 411 } 412 413 // SetSystemFWVersion sets the provided system firmware version to BMC via IPMI. 414 func (i *IPMI) SetSystemFWVersion(version string) error { 415 len := len(version) 416 417 if len == 0 { 418 return fmt.Errorf("Version length is 0") 419 } else if len > strlenMax { 420 len = strlenMax 421 } 422 423 var data setSystemInfoReq 424 var index int 425 data.paramSelector = _SYSTEM_FW_VERSION 426 data.setSelector = 0 427 for len > index { 428 if data.setSelector == 0 { // the fisrt block of string data 429 data.strData[0] = _ASCII 430 data.strData[1] = byte(len) 431 strcpyPadded(data.strData[2:], version) 432 index += _SYSTEM_INFO_BLK_SZ - 2 433 } else { 434 strcpyPadded(data.strData[:], version) 435 index += _SYSTEM_INFO_BLK_SZ 436 } 437 438 if err := i.setsysinfo(&data); err != nil { 439 return err 440 } 441 data.setSelector++ 442 } 443 444 return nil 445 } 446 447 func (i *IPMI) GetDeviceID() (*DevID, error) { 448 data, err := i.SendRecv(_IPMI_NETFN_APP, BMC_GET_DEVICE_ID, nil) 449 450 if err != nil { 451 return nil, err 452 } 453 454 buf := bytes.NewReader(data[1:]) 455 mcInfo := DevID{} 456 457 if err := binary.Read(buf, binary.LittleEndian, &mcInfo); err != nil { 458 return nil, err 459 } 460 461 return &mcInfo, nil 462 } 463 464 func (i *IPMI) setGlobalEnables(enables byte) error { 465 _, err := i.SendRecv(_IPMI_NETFN_APP, BMC_SET_GLOBAL_ENABLES, []byte{enables}) 466 return err 467 } 468 469 func (i *IPMI) getGlobalEnables() ([]byte, error) { 470 return i.SendRecv(_IPMI_NETFN_APP, BMC_GET_GLOBAL_ENABLES, nil) 471 } 472 473 func (i *IPMI) EnableSEL() (bool, error) { 474 // Check if SEL device is supported or not 475 mcInfo, err := i.GetDeviceID() 476 477 if err != nil { 478 return false, err 479 } else if (mcInfo.AdtlDeviceSupport & ADTL_SEL_DEVICE) == 0 { 480 return false, nil 481 } 482 483 data, err := i.getGlobalEnables() 484 485 if err != nil { 486 return false, err 487 } 488 489 if (data[1] & EN_SYSTEM_EVENT_LOGGING) == 0 { 490 // SEL is not enabled, enable SEL 491 if err = i.setGlobalEnables(data[1] | EN_SYSTEM_EVENT_LOGGING); err != nil { 492 return false, err 493 } 494 } 495 496 return true, nil 497 } 498 499 func (i *IPMI) GetChassisStatus() (*ChassisStatus, error) { 500 data, err := i.SendRecv(_IPMI_NETFN_CHASSIS, BMC_GET_CHASSIS_STATUS, nil) 501 if err != nil { 502 return nil, err 503 } 504 505 buf := bytes.NewReader(data[1:]) 506 507 var status ChassisStatus 508 if err := binary.Read(buf, binary.LittleEndian, &status); err != nil { 509 return nil, err 510 } 511 return &status, nil 512 } 513 514 func (i *IPMI) GetSELInfo() (*SELInfo, error) { 515 data, err := i.SendRecv(_IPMI_NETFN_STORAGE, BMC_GET_SEL_INFO, nil) 516 if err != nil { 517 return nil, err 518 } 519 520 buf := bytes.NewReader(data[1:]) 521 522 var info SELInfo 523 if err := binary.Read(buf, binary.LittleEndian, &info); err != nil { 524 return nil, err 525 } 526 return &info, nil 527 } 528 529 func (i *IPMI) GetLanConfig(channel byte, param byte) ([]byte, error) { 530 var data [4]byte 531 data[0] = channel 532 data[1] = param 533 data[2] = 0 534 data[3] = 0 535 536 return i.SendRecv(_IPMI_NETFN_TRANSPORT, BMC_GET_LAN_CONFIG, data[:4]) 537 } 538 539 func (i *IPMI) RawCmd(param []byte) ([]byte, error) { 540 if len(param) < 2 { 541 return nil, errors.New("Not enough parameters given") 542 } 543 544 msg := Msg{ 545 Netfn: NetFn(param[0]), 546 Cmd: Command(param[1]), 547 } 548 if len(param) > 2 { 549 msg.Data = unsafe.Pointer(¶m[2]) 550 } 551 552 msg.DataLen = uint16(len(param) - 2) 553 554 return i.RawSendRecv(msg) 555 }