github.com/rminnich/u-root@v7.0.0+incompatible/pkg/msr/msr_linux.go (about)

     1  // Copyright 2012-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  // This file contains support functions for msr access for Linux.
     6  package msr
     7  
     8  import (
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/intel-go/cpuid"
    19  )
    20  
    21  // CPUs is a slice of the various cpus to read or write the MSR to.
    22  type CPUs []uint64
    23  
    24  func parseCPUs(s string) (CPUs, error) {
    25  	cpus := make(CPUs, 0)
    26  	// We expect the format to be "0-5,7-8..." or we could also get just one cpu.
    27  	// We're unlikely to get more than one range since we're looking at present cpus,
    28  	// but handle it just in case.
    29  	ranges := strings.Split(strings.TrimSpace(s), ",")
    30  	for _, r := range ranges {
    31  		if len(r) == 0 {
    32  			continue
    33  		}
    34  		// Split on a - if it exists.
    35  		cs := strings.Split(r, "-")
    36  		switch len(cs) {
    37  		case 1:
    38  			u, err := strconv.ParseUint(cs[0], 0, 64)
    39  			if err != nil {
    40  				return nil, fmt.Errorf("unknown cpu range: %v, failed to parse %v", r, err)
    41  			}
    42  			cpus = append(cpus, uint64(u))
    43  		case 2:
    44  			ul, err := strconv.ParseUint(cs[0], 0, 64)
    45  			if err != nil {
    46  				return nil, fmt.Errorf("unknown cpu range: %v, failed to parse %v", r, err)
    47  			}
    48  			uh, err := strconv.ParseUint(cs[1], 0, 64)
    49  			if err != nil {
    50  				return nil, fmt.Errorf("unknown cpu range: %v, failed to parse %v", r, err)
    51  			}
    52  			if ul > uh {
    53  				return nil, fmt.Errorf("invalid cpu range, upper bound greater than lower: %v", r)
    54  			}
    55  			for i := ul; i <= uh; i++ {
    56  				cpus = append(cpus, uint64(i))
    57  			}
    58  		default:
    59  			return nil, fmt.Errorf("unknown cpu range: %v", r)
    60  		}
    61  	}
    62  	if len(cpus) == 0 {
    63  		return nil, fmt.Errorf("no cpus found, input was %v", s)
    64  	}
    65  	sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] })
    66  	// Remove duplicates
    67  	for i := 0; i < len(cpus)-1; i++ {
    68  		if cpus[i] == cpus[i+1] {
    69  			cpus = append(cpus[:i], cpus[i+1:]...)
    70  			i--
    71  		}
    72  	}
    73  	return cpus, nil
    74  
    75  }
    76  
    77  // AllCPUs searches for actual present CPUs instead of relying on the glob.
    78  // This is more accurate than what's presented in /dev/cpu/*/msr
    79  func AllCPUs() (CPUs, error) {
    80  	v, err := ioutil.ReadFile("/sys/devices/system/cpu/present")
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	return parseCPUs(string(v))
    85  }
    86  
    87  // GlobCPUs allow the user to specify CPUs using a glob as one would in /dev/cpu
    88  func GlobCPUs(g string) (CPUs, []error) {
    89  	var hadErr bool
    90  
    91  	f, err := filepath.Glob(filepath.Join("/dev/cpu", g, "msr"))
    92  	if err != nil {
    93  		return nil, []error{err}
    94  	}
    95  
    96  	c := make([]uint64, len(f))
    97  	errs := make([]error, len(f))
    98  	for i, v := range f {
    99  		c[i], errs[i] = strconv.ParseUint(filepath.Base(filepath.Dir(v)), 0, 64)
   100  		if errs[i] != nil {
   101  			hadErr = true
   102  		}
   103  	}
   104  	if hadErr {
   105  		return nil, errs
   106  	}
   107  	return c, nil
   108  }
   109  
   110  // String implements String() for MSR.
   111  func (m MSR) String() string {
   112  	return fmt.Sprintf("%#x", uint32(m))
   113  }
   114  
   115  // String pretty prints the list of CPUs. For example: 1-2,4
   116  func (c CPUs) String() string {
   117  	if len(c) == 0 {
   118  		return "nil"
   119  	}
   120  	sort.Slice(c, func(i, j int) bool { return c[i] < c[j] })
   121  
   122  	var s []string
   123  	for i := 0; i < len(c); i++ {
   124  		// Find the last CPU in this continuous range.
   125  		j := i
   126  		for j+1 < len(c) && c[j]+1 == c[j+1] {
   127  			j++
   128  		}
   129  
   130  		if i == j {
   131  			// Continuous set of size 1.
   132  			s = append(s, fmt.Sprintf("%d", c[i]))
   133  		} else {
   134  			// Multiple CPUs in continous set.
   135  			s = append(s, fmt.Sprintf("%d-%d", c[i], c[j]))
   136  		}
   137  
   138  		i = j // Skip over set.
   139  	}
   140  	return strings.Join(s, ",")
   141  }
   142  
   143  func (c CPUs) paths() []string {
   144  	var p = make([]string, len(c))
   145  
   146  	for i, v := range c {
   147  		p[i] = filepath.Join("/dev/cpu", strconv.Itoa(int(v)), "msr")
   148  	}
   149  	return p
   150  }
   151  
   152  // Read reads an MSR from a set of CPUs.
   153  func (m MSR) Read(c CPUs) ([]uint64, []error) {
   154  	var hadErr bool
   155  	var regs = make([]uint64, len(c))
   156  
   157  	paths := c.paths()
   158  	f, errs := openAll(paths, os.O_RDONLY)
   159  	if errs != nil {
   160  		return nil, errs
   161  	}
   162  	errs = make([]error, len(f))
   163  	for i := range f {
   164  		defer f[i].Close()
   165  		errs[i] = doIO(f[i], m, func(port *os.File) error {
   166  			return binary.Read(port, binary.LittleEndian, &regs[i])
   167  		})
   168  		if errs[i] != nil {
   169  			hadErr = true
   170  		}
   171  	}
   172  	if hadErr {
   173  		return nil, errs
   174  	}
   175  
   176  	return regs, nil
   177  }
   178  
   179  // Write writes values to an MSR on a set of CPUs.
   180  // The data must be passed as a scalar (single value) or a slice.
   181  // If the data slice has more than one element,
   182  // the length of the data slice and the CPU slice must be the same.
   183  // If a single value is given, it will be written to all the CPUs.
   184  // If multiple data values are given, each will be written to its corresponding
   185  // CPU.
   186  func (m MSR) Write(c CPUs, data ...uint64) []error {
   187  	var hadErr bool
   188  
   189  	if len(data) == 1 {
   190  		// Expand value to all cpus
   191  		for i := 1; i < len(c); i++ {
   192  			data = append(data, data[0])
   193  		}
   194  	}
   195  	if len(data) != len(c) {
   196  		return []error{fmt.Errorf("mismatched lengths: cpus %v, data %v", c, data)}
   197  	}
   198  
   199  	paths := c.paths()
   200  	f, errs := openAll(paths, os.O_RDWR)
   201  
   202  	if errs != nil {
   203  		return errs
   204  	}
   205  	errs = make([]error, len(f))
   206  	for i := range f {
   207  		defer f[i].Close()
   208  		errs[i] = doIO(f[i], m, func(port *os.File) error {
   209  			return binary.Write(port, binary.LittleEndian, data[i])
   210  		})
   211  		if errs[i] != nil {
   212  			hadErr = true
   213  		}
   214  	}
   215  	if hadErr {
   216  		return errs
   217  	}
   218  	return nil
   219  }
   220  
   221  // testAndSetMaybe takes a mask of bits to clear and to set, and applies them to the specified MSR in
   222  // each of the CPUs. It will set the MSR only if the value is different and a set is requested.
   223  // If the MSR is different for any reason that is an error.
   224  func (m MSR) testAndSetMaybe(c CPUs, clearMask uint64, setMask uint64, set bool) []error {
   225  	var hadErr bool
   226  	paths := c.paths()
   227  	f, errs := openAll(paths, os.O_RDWR)
   228  
   229  	if errs != nil {
   230  		return errs
   231  	}
   232  	errs = make([]error, len(f))
   233  	for i := range f {
   234  		defer f[i].Close()
   235  		errs[i] = doIO(f[i], m, func(port *os.File) error {
   236  			var v uint64
   237  			err := binary.Read(port, binary.LittleEndian, &v)
   238  			if err != nil {
   239  				return err
   240  			}
   241  			n := v & ^clearMask
   242  			n |= setMask
   243  			// We write only if there is a change. This is to avoid
   244  			// cases where we try to set a lock bit again, but the bit is
   245  			// already set
   246  			if n != v && set {
   247  				return binary.Write(port, binary.LittleEndian, n)
   248  			}
   249  			if n != v {
   250  				return fmt.Errorf("%#x", v)
   251  			}
   252  			return nil
   253  		})
   254  		if errs[i] != nil {
   255  			hadErr = true
   256  		}
   257  	}
   258  	if hadErr {
   259  		return errs
   260  	}
   261  	return nil
   262  }
   263  
   264  // Test takes a mask of bits to clear and to set, and returns an error for those
   265  // that do not match.
   266  func (m MSR) Test(c CPUs, clearMask uint64, setMask uint64) []error {
   267  	return m.testAndSetMaybe(c, clearMask, setMask, false)
   268  }
   269  
   270  // TestAndSet takes a mask of bits to clear and to set, and applies them to the specified MSR in
   271  // each of the CPUs. Note that TestAndSet does not write if the mask does not change the MSR.
   272  func (m MSR) TestAndSet(c CPUs, clearMask uint64, setMask uint64) []error {
   273  	return m.testAndSetMaybe(c, clearMask, setMask, true)
   274  }
   275  
   276  // Locked verifies that for all MSRVal's for the CPU vendor, the MSRs are locked.
   277  // TODO: this is another Intel-specific function at present.
   278  func Locked() error {
   279  	vendor := cpuid.VendorIdentificatorString
   280  	// TODO: support more than Intel. Use the vendor id to look up msrs.
   281  	if vendor != "GenuineIntel" {
   282  		return fmt.Errorf("Sorry, this package only supports Intel at present")
   283  	}
   284  
   285  	cpus, err := AllCPUs()
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	var allerrors string
   291  	for _, m := range LockIntel {
   292  		Debug("MSR %v on cpus %v, clearmask 0x%8x, setmask 0x%8x", m.Addr, cpus, m.Clear, m.Set)
   293  		if m.WriteOnly {
   294  			continue
   295  		}
   296  		errs := m.Addr.Test(cpus, m.Clear, m.Set)
   297  
   298  		for i, e := range errs {
   299  			if e != nil {
   300  				allerrors += fmt.Sprintf("[cpu%d(%s)%v ", cpus[i], m.String(), e)
   301  			}
   302  		}
   303  	}
   304  
   305  	if allerrors != "" {
   306  		return fmt.Errorf("%s: %v", vendor, allerrors)
   307  	}
   308  	return nil
   309  
   310  }
   311  
   312  func openAll(m []string, o int) ([]*os.File, []error) {
   313  	var (
   314  		f      = make([]*os.File, len(m))
   315  		errs   = make([]error, len(m))
   316  		hadErr bool
   317  	)
   318  	for i := range m {
   319  		f[i], errs[i] = os.OpenFile(m[i], o, 0)
   320  		if errs[i] != nil {
   321  			hadErr = true
   322  			f[i] = nil // Not sure if I need to do this, it doesn't seem guaranteed.
   323  		}
   324  	}
   325  	if hadErr {
   326  		for i := range f {
   327  			if f[i] != nil {
   328  				f[i].Close()
   329  			}
   330  		}
   331  		return nil, errs
   332  	}
   333  	return f, nil
   334  }
   335  
   336  func doIO(msr *os.File, addr MSR, f func(*os.File) error) error {
   337  	if _, err := msr.Seek(int64(addr), 0); err != nil {
   338  		return fmt.Errorf("bad address %v: %v", addr, err)
   339  	}
   340  	return f(msr)
   341  }