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