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