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  }