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