github.com/zaolin/u-root@v0.0.0-20200428085104-64aaafd46c6d/pkg/mei/mkhiclient_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 mei 6 7 import ( 8 "encoding/binary" 9 "errors" 10 "fmt" 11 "log" 12 "strconv" 13 "strings" 14 15 "github.com/u-root/u-root/pkg/pci" 16 ) 17 18 // Intel MEI PCI dev IDs, 19 // from https://elixir.bootlin.com/linux/latest/source/drivers/misc/mei/hw-me-regs.h 20 var meiDevIDs = map[string]uint{ 21 // awk '/MEI_DEV_ID/ {print "\t\"" $2 "\": " $3 ","}' hw-me-regs.h 22 "MEI_DEV_ID_82946GZ": 0x2974, 23 "MEI_DEV_ID_82G35": 0x2984, 24 "MEI_DEV_ID_82Q965": 0x2994, 25 "MEI_DEV_ID_82G965": 0x29A4, 26 "MEI_DEV_ID_82GM965": 0x2A04, 27 "MEI_DEV_ID_82GME965": 0x2A14, 28 "MEI_DEV_ID_ICH9_82Q35": 0x29B4, 29 "MEI_DEV_ID_ICH9_82G33": 0x29C4, 30 "MEI_DEV_ID_ICH9_82Q33": 0x29D4, 31 "MEI_DEV_ID_ICH9_82X38": 0x29E4, 32 "MEI_DEV_ID_ICH9_3200": 0x29F4, 33 "MEI_DEV_ID_ICH9_6": 0x28B4, 34 "MEI_DEV_ID_ICH9_7": 0x28C4, 35 "MEI_DEV_ID_ICH9_8": 0x28D4, 36 "MEI_DEV_ID_ICH9_9": 0x28E4, 37 "MEI_DEV_ID_ICH9_10": 0x28F4, 38 "MEI_DEV_ID_ICH9M_1": 0x2A44, 39 "MEI_DEV_ID_ICH9M_2": 0x2A54, 40 "MEI_DEV_ID_ICH9M_3": 0x2A64, 41 "MEI_DEV_ID_ICH9M_4": 0x2A74, 42 "MEI_DEV_ID_ICH10_1": 0x2E04, 43 "MEI_DEV_ID_ICH10_2": 0x2E14, 44 "MEI_DEV_ID_ICH10_3": 0x2E24, 45 "MEI_DEV_ID_ICH10_4": 0x2E34, 46 "MEI_DEV_ID_IBXPK_1": 0x3B64, 47 "MEI_DEV_ID_IBXPK_2": 0x3B65, 48 "MEI_DEV_ID_CPT_1": 0x1C3A, 49 "MEI_DEV_ID_PBG_1": 0x1D3A, 50 "MEI_DEV_ID_PPT_1": 0x1E3A, 51 "MEI_DEV_ID_PPT_2": 0x1CBA, 52 "MEI_DEV_ID_PPT_3": 0x1DBA, 53 "MEI_DEV_ID_LPT_H": 0x8C3A, 54 "MEI_DEV_ID_LPT_W": 0x8D3A, 55 "MEI_DEV_ID_LPT_LP": 0x9C3A, 56 "MEI_DEV_ID_LPT_HR": 0x8CBA, 57 "MEI_DEV_ID_WPT_LP": 0x9CBA, 58 "MEI_DEV_ID_WPT_LP_2": 0x9CBB, 59 "MEI_DEV_ID_SPT": 0x9D3A, 60 "MEI_DEV_ID_SPT_2": 0x9D3B, 61 "MEI_DEV_ID_SPT_H": 0xA13A, 62 "MEI_DEV_ID_SPT_H_2": 0xA13B, 63 "MEI_DEV_ID_LBG": 0xA1BA, 64 "MEI_DEV_ID_BXT_M": 0x1A9A, 65 "MEI_DEV_ID_APL_I": 0x5A9A, 66 "MEI_DEV_ID_DNV_IE": 0x19E5, 67 "MEI_DEV_ID_GLK": 0x319A, 68 "MEI_DEV_ID_KBP": 0xA2BA, 69 "MEI_DEV_ID_KBP_2": 0xA2BB, 70 "MEI_DEV_ID_CNP_LP": 0x9DE0, 71 "MEI_DEV_ID_CNP_LP_4": 0x9DE4, 72 "MEI_DEV_ID_CNP_H": 0xA360, 73 "MEI_DEV_ID_CNP_H_4": 0xA364, 74 "MEI_DEV_ID_CMP_LP": 0x02e0, 75 "MEI_DEV_ID_CMP_LP_3": 0x02e4, 76 "MEI_DEV_ID_CMP_V": 0xA3BA, 77 "MEI_DEV_ID_CMP_H": 0x06e0, 78 "MEI_DEV_ID_CMP_H_3": 0x06e4, 79 "MEI_DEV_ID_CDF": 0x18D3, 80 "MEI_DEV_ID_ICP_LP": 0x34E0, 81 "MEI_DEV_ID_JSP_N": 0x4DE0, 82 "MEI_DEV_ID_TGP_LP": 0xA0E0, 83 "MEI_DEV_ID_MCC": 0x4B70, 84 "MEI_DEV_ID_MCC_4": 0x4B75, 85 } 86 87 // various ME-related constants 88 const ( 89 // ME - current working state set to normal 90 meHfs1CwsNormal = 0x5 91 // ME - current operation mode set to normal 92 meHfs1ComNormal = 0x0 93 // ME - CSE's firmware SKU is custom 94 meHfs3FwSkuCustom = 0x5 95 96 pciMEHfsts1 = 0x40 97 pciMEHfsts3 = 0x60 98 ) 99 100 // MKHIClient is a client to send MKHI commands via MEI. 101 type MKHIClient struct { 102 MEI MEI 103 } 104 105 // MKHI command groups, see 106 // https://github.com/coreboot/coreboot/blob/b8b8ec832360ada5a313f10938bb6cfc310a11eb/src/soc/intel/common/block/include/intelblocks/cse.h#L22 107 const ( 108 mkhiGroupIDCbm = 0x0 109 mkhiGroupIDHMRFPO = 0x5 110 mkhiGroupIDGen = 0xff 111 ) 112 113 // MKHI HMRFPO command IDs, see 114 // https://github.com/coreboot/coreboot/blob/b8b8ec832360ada5a313f10938bb6cfc310a11eb/src/soc/intel/common/block/include/intelblocks/cse.h#L33 115 const ( 116 mkhiHMRFPOEnable = 0x1 117 mkhiHMRFPOGetStatus = 0x3 118 mkhiGetEOPState = 0x1d 119 mkhiGetHMRFPOLock = 0x3 120 ) 121 122 // pciAddressToInt converts the last part of a PCI address string to integer, 123 // e.g. 0000:00:16.0 will return 0x16 . 124 func pciAddressToInt(pciaddr string) (int, error) { 125 parts := strings.Split(pciaddr, ":") 126 if len(parts) != 3 { 127 return 0, fmt.Errorf("wrong PCI address string '%s': want 3 colon-separated groups, got %d", pciaddr, len(parts)) 128 } 129 parts = strings.Split(parts[2], ".") 130 if len(parts) != 2 { 131 return 0, fmt.Errorf("wrong PCI address sub-component '%s': want 2 dot-separated groups, got %d", pciaddr, len(parts)) 132 } 133 a, err := strconv.ParseInt(parts[0], 16, 64) 134 if err != nil { 135 return 0, fmt.Errorf("failed to parse hex string '%s': %v", parts[0], err) 136 } 137 return int(a), nil 138 } 139 140 // Open opens an MKHI client connection. 141 func (m *MKHIClient) Open(meiPath string) error { 142 if err := m.MEI.Open(meiPath, MKHIGuid); err != nil { 143 return err 144 } 145 return nil 146 } 147 148 // Close closes an MKHI client connection. 149 func (m *MKHIClient) Close() error { 150 return m.MEI.Close() 151 } 152 153 // IsHMRFPOEnableAllowed queries whether the HMRFPO enable is allowed. 154 func (m *MKHIClient) IsHMRFPOEnableAllowed() (bool, error) { 155 /* This is a reimplementation of cse_is_hmrfpo_enable_allowed from 156 * coreboot/src/soc/intel/common/block/cse/cse.c . The below comment also 157 * comes from that function. 158 * 159 * Allow sending HMRFPO ENABLE command only if: 160 * - CSE's current working state is Normal and current operation mode is Normal 161 * - (or) cse's current working state is normal and current operation mode is 162 * Soft Temp Disable if CSE's Firmware SKU is Custom 163 * 164 */ 165 meiDev, err := GetMeiPciDevice() 166 if err != nil { 167 return false, fmt.Errorf("failed to get PCI MEI device: %v", err) 168 } 169 log.Printf("MEI Device found: %s", meiDev) 170 171 // check that CSE's current working state is normal 172 cs, err := meiDev.ReadConfigRegister(pciMEHfsts1, 32) 173 if err != nil { 174 return false, fmt.Errorf("PCI config read failed: %v", err) 175 } 176 // check that the current working state is ME_HFS1_CWS_NORMAL (0x05) and 177 // current operation mode is ME_HFS1_COM_NORMAL (0x0). 178 // `working_state` is bits 1-4 and `operation_state` is bits 7-9. 179 if (cs&0xf) != meHfs1CwsNormal || ((cs>>6)&0x7) != meHfs1ComNormal { 180 return false, nil 181 } 182 // check that CSE's firmware SKU is not custom, and if it is, that the 183 // current operation mode is Soft Temp Disable. 184 cs, err = meiDev.ReadConfigRegister(pciMEHfsts3, 32) 185 if err != nil { 186 return false, fmt.Errorf("PCI config read failed: %v", err) 187 } 188 // fw_sku is in bits 5-7 . ME_HFS3_FW_SKU_CUSTOM is 0x5 189 if (cs>>4)&0x7 == meHfs3FwSkuCustom { 190 // TODO implement the same as coreboot's cse_is_hfs1_com_soft_temp_disable() 191 return false, errors.New("IsHMRFPOEnableAllowed does not support checking for Soft Temp Disable yet") 192 } 193 return true, nil 194 } 195 196 type hmrfpoEnableMsg struct { 197 header mkhiHdr 198 nonce uint32 199 } 200 201 type getEOPStateMsg struct { 202 header mkhiHdr 203 nonce uint32 204 } 205 206 type getHMRFPOLockMsg struct { 207 header mkhiHdr 208 nonce uint32 209 } 210 211 func (hem hmrfpoEnableMsg) ToBytes() []byte { 212 var buf []byte 213 buf = append(buf, hem.header[:]...) 214 var nonce [4]byte 215 binary.LittleEndian.PutUint32(nonce[:], hem.nonce) 216 return append(buf, nonce[:]...) 217 } 218 219 func (gesm getEOPStateMsg) ToBytes() []byte { 220 var buf []byte 221 buf = append(buf, gesm.header[:]...) 222 var nonce [4]byte 223 binary.LittleEndian.PutUint32(nonce[:], gesm.nonce) 224 return append(buf, nonce[:]...) 225 } 226 227 func (gesm getHMRFPOLockMsg) ToBytes() []byte { 228 var buf []byte 229 buf = append(buf, gesm.header[:]...) 230 var nonce [4]byte 231 binary.LittleEndian.PutUint32(nonce[:], gesm.nonce) 232 return append(buf, nonce[:]...) 233 } 234 235 type hmrfpoEnableResponse struct { 236 Header mkhiHdr 237 FctBase uint32 238 FctLimit uint32 239 Status uint8 240 reserved [3]byte 241 } 242 243 type getEOPStateResponse struct { 244 Header mkhiHdr 245 Status uint8 246 reserved [3]byte 247 } 248 249 type getHMRFPOLockResponse struct { 250 Header mkhiHdr 251 Status uint8 252 reserved [3]byte 253 } 254 255 func hmrfpoEnableResponseFromBytes(b []byte) (*hmrfpoEnableResponse, error) { 256 var resp hmrfpoEnableResponse 257 minlen := len(resp.Header) 258 maxlen := minlen + 259 4 /* FctBase */ + 260 4 /* FctLimit */ + 261 1 /* Status */ + 262 3 /* reserved bytes */ 263 if len(b) != minlen && len(b) != maxlen { 264 return nil, fmt.Errorf("size mismatch, want %d/%d bytes, got %d", minlen, maxlen, len(b)) 265 } 266 copy(resp.Header[:], b[:4]) 267 if len(b) == minlen { 268 // don't parse the rest, we got a partial response 269 return &resp, nil 270 } 271 // TODO this could use u-root's pkg/uio 272 resp.FctBase = binary.LittleEndian.Uint32(b[4:8]) 273 resp.FctLimit = binary.LittleEndian.Uint32(b[8:12]) 274 resp.Status = b[12] 275 return &resp, nil 276 } 277 278 func getEOPStateResponseFromBytes(b []byte) (*getEOPStateResponse, error) { 279 var resp getEOPStateResponse 280 minlen := len(resp.Header) 281 maxlen := minlen + 282 1 /* Status */ + 283 3 /* reserved bytes */ 284 if len(b) != minlen && len(b) != maxlen { 285 return nil, fmt.Errorf("size mismatch, want %d/%d bytes, got %d", minlen, maxlen, len(b)) 286 } 287 copy(resp.Header[:], b[:4]) 288 if len(b) == minlen { 289 // don't parse the rest, we got a partial response 290 return &resp, nil 291 } 292 // TODO this could use u-root's pkg/uio 293 resp.Status = b[4] 294 return &resp, nil 295 } 296 297 func getHMRFPOLockResponseFromBytes(b []byte) (*getHMRFPOLockResponse, error) { 298 var resp getHMRFPOLockResponse 299 minlen := len(resp.Header) 300 maxlen := minlen + 301 1 /* Status */ + 302 3 /* reserved bytes */ 303 if len(b) != minlen && len(b) != maxlen { 304 return nil, fmt.Errorf("size mismatch, want %d/%d bytes, got %d", minlen, maxlen, len(b)) 305 } 306 copy(resp.Header[:], b[:4]) 307 if len(b) == minlen { 308 // don't parse the rest, we got a partial response 309 return &resp, nil 310 } 311 // TODO this could use u-root's pkg/uio 312 resp.Status = b[4] 313 return &resp, nil 314 } 315 316 // EnableHMRFPO enables the HMRFPO (Host ME Region Flash Protection Override) via CSE, 317 // see cse_hmrfpo_enable at 318 // https://github.com/coreboot/coreboot/blob/b8b8ec832360ada5a313f10938bb6cfc310a11eb/src/soc/intel/common/block/include/intelblocks/cse.h#L64 319 func (m *MKHIClient) EnableHMRFPO() error { 320 321 var hdr mkhiHdr 322 hdr.SetGroupID(mkhiGroupIDHMRFPO) 323 hdr.SetCommand(mkhiHMRFPOEnable) 324 canEnable, err := m.IsHMRFPOEnableAllowed() 325 if err != nil { 326 return fmt.Errorf("enabling HMRFPO failed: %v", err) 327 } 328 if !canEnable { 329 return fmt.Errorf("enabling HMRFPO is not allowed") 330 } 331 msg := hmrfpoEnableMsg{ 332 header: hdr, 333 nonce: 0, 334 } 335 if _, err := m.MEI.Write(msg.ToBytes()); err != nil { 336 return fmt.Errorf("write to MEI failed: %v", err) 337 } 338 buf := make([]byte, m.MEI.ClientProperties.MaxMsgLength()) 339 n, err := m.MEI.Read(buf) 340 if err != nil { 341 return fmt.Errorf("read from MEI failed: %v", err) 342 } 343 resp, err := hmrfpoEnableResponseFromBytes(buf[:n]) 344 if err != nil { 345 return fmt.Errorf("failed to parse HMRFPOEnableResponse: %v", err) 346 } 347 if resp.Header.Result() != 0 { 348 return fmt.Errorf("failed to enable HMRFPO, request result is 0x%02x, want 0x0", resp.Header.Result()) 349 } 350 if resp.Status != 0 { 351 return fmt.Errorf("failed to enable HMRFPO, request status is 0x%02x, want 0x0", resp.Status) 352 } 353 return nil 354 } 355 356 // GetMeiPciDevice will return the MEI PCI device object after scanning the PCI 357 // bus. 358 func GetMeiPciDevice() (*pci.PCI, error) { 359 br, err := pci.NewBusReader() 360 if err != nil { 361 return nil, fmt.Errorf("failed to create PCI bus reader: %v", err) 362 } 363 devices, err := br.Read() 364 if err != nil { 365 return nil, fmt.Errorf("failed to scan PCI bus: %v", err) 366 } 367 for _, device := range devices { 368 // look for vendor ID 8086 (Intel) 369 if device.Vendor != "8086" { 370 continue 371 } 372 productID, err := strconv.ParseInt(device.Device, 16, 64) 373 if err != nil { 374 return nil, fmt.Errorf("failed to parse device ID '%s' as hex: %v", device.Device, err) 375 } 376 // look for a known MEI product ID 377 for _, devID := range meiDevIDs { 378 if int64(devID) == productID { 379 device.SetVendorDeviceName() 380 // there is only one MEI device, right? 381 return device, nil 382 } 383 } 384 } 385 return nil, errors.New("no MEI device found") 386 } 387 388 func (m MKHIClient) GetEOPState() error { 389 var hdr mkhiHdr 390 hdr.SetGroupID(mkhiGroupIDGen) 391 hdr.SetCommand(mkhiGetEOPState) 392 msg := getEOPStateMsg{ 393 header: hdr, 394 nonce: 0, 395 } 396 if _, err := m.MEI.Write(msg.ToBytes()); err != nil { 397 return fmt.Errorf("write to MEI failed: %w", err) 398 } 399 // TODO get the size value from the client properties returned by MEI.OpenMKHI 400 buf := make([]byte, 2048) 401 n, err := m.MEI.Read(buf) 402 if err != nil { 403 return fmt.Errorf("read from MEI failed: %w", err) 404 } 405 resp, err := getEOPStateResponseFromBytes(buf[:n]) 406 if err != nil { 407 return fmt.Errorf("failed to parse GetEOPStateResponse: %w", err) 408 } 409 // TODO remove these debug prints 410 log.Printf("GroupID : %d", resp.Header.GroupID()) 411 log.Printf("Command : %d", resp.Header.Command()) 412 log.Printf("IsResponse: %v", resp.Header.IsResponse()) 413 log.Printf("RSVD : %d", resp.Header.RSVD()) 414 log.Printf("Result : %d", resp.Header.Result()) 415 if resp.Header.Result() != 0 { 416 return fmt.Errorf("failed to get EOP status, request result is 0x%02x, want 0x0", resp.Header.Result()) 417 } 418 log.Printf("EOP Status: %d\n", resp.Status) 419 420 return nil 421 } 422 423 func (m MKHIClient) GetHMRFPOLock() error { 424 var hdr mkhiHdr 425 hdr.SetGroupID(mkhiGroupIDGen) 426 hdr.SetCommand(mkhiGetHMRFPOLock) 427 msg := getHMRFPOLockMsg{ 428 header: hdr, 429 nonce: 0, 430 } 431 if _, err := m.MEI.Write(msg.ToBytes()); err != nil { 432 return fmt.Errorf("write to MEI failed: %w", err) 433 } 434 // TODO get the size value from the client properties returned by MEI.OpenMKHI 435 buf := make([]byte, 2048) 436 n, err := m.MEI.Read(buf) 437 if err != nil { 438 return fmt.Errorf("read from MEI failed: %w", err) 439 } 440 resp, err := getHMRFPOLockResponseFromBytes(buf[:n]) 441 if err != nil { 442 return fmt.Errorf("failed to parse GetEOPStateResponse: %w", err) 443 } 444 // TODO remove these debug prints 445 log.Printf("GroupID : %d", resp.Header.GroupID()) 446 log.Printf("Command : %d", resp.Header.Command()) 447 log.Printf("IsResponse: %v", resp.Header.IsResponse()) 448 log.Printf("RSVD : %d", resp.Header.RSVD()) 449 log.Printf("Result : %d", resp.Header.Result()) 450 if resp.Header.Result() != 0 { 451 return fmt.Errorf("failed to get EOP status, request result is 0x%02x, want 0x0", resp.Header.Result()) 452 } 453 switch resp.Status { 454 case 0: 455 log.Println("HMRFPO Status: Disabled") 456 break 457 case 1: 458 log.Println("HMRFPO Status: Locked") 459 break 460 case 2: 461 log.Println("HMRFPO Status: Enabled") 462 break 463 default: 464 log.Println("HMRFPO Status: Unknown") 465 } 466 467 return nil 468 }