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, ®s[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 }