diff --git a/cmd/sst/main.go b/cmd/sst/main.go index 6fb1223..da3e429 100644 --- a/cmd/sst/main.go +++ b/cmd/sst/main.go @@ -45,6 +45,7 @@ type subCmd struct { var subCmds = map[string]subCmd{ "status": {description: "Print current status of SST features", f: subCmdStatus}, + "info": {description: "Print detailed SST-PP level info", f: subCmdInfo}, "bf": {description: "Configure SST-BF feature", f: subCmdBF}, "cp": {description: "Configure SST-CP feature", f: subCmdCP}, "tf": {description: "Configure SST-TF feature", f: subCmdTF}, @@ -222,17 +223,13 @@ func printStatusTable(allInfo map[utils.ID]*sst.PackageStatus) { // BF table fmt.Println("\nSST-BF (Base Frequency):") w = newTable() - fmt.Fprintln(w, " PKG\tPUNIT\tSUPPORTED\tENABLED\tHIGH-FREQ CORES") + fmt.Fprintln(w, " PKG\tPUNIT\tSUPPORTED\tENABLED") for _, pkgID := range pkgIDs { for _, punitID := range slices.Sorted(maps.Keys(allInfo[pkgID].Punits)) { pu := allInfo[pkgID].Punits[punitID] - cores := "-" - if pu.BF.Supported { - cores = idSetStr(pu.BF.Cores) - } - fmt.Fprintf(w, " %d\t%d\t%v\t%v\t%s\n", + fmt.Fprintf(w, " %d\t%d\t%v\t%v\n", pkgID, punitID, - pu.BF.Supported, pu.BF.Enabled, cores) + pu.BF.Supported, pu.BF.Enabled) } } w.Flush() @@ -293,6 +290,170 @@ func printStatusTable(allInfo map[utils.ID]*sst.PackageStatus) { } } +func subCmdInfo(args []string) error { + level := -1 + var format string + flags := flag.NewFlagSet("info", flag.ExitOnError) + flags.IntVar(&level, "level", -1, "Performance level to query (default: current level of the first package/punit)") + flags.StringVar(&format, "format", "table", "Output format: table, yaml, json") + addCommonFlags(flags) + addCommonPackageFlags(flags) + if err := flags.Parse(args); err != nil { + return err + } + if level < -1 { + return fmt.Errorf("invalid level %d: must be >= -1 (-1 uses the current level)", level) + } + + h, err := initHandle() + if err != nil { + return err + } + pkgs, err := getPackageHandles(h) + if err != nil { + return err + } + if len(pkgs) == 0 { + return fmt.Errorf("no packages found") + } + + // As default, use the perf level of the first package's first punit. + if level < 0 { + status, err := pkgs[0].GetStatus() + if err != nil { + return fmt.Errorf("failed to get status for package %d: %w", pkgs[0].ID(), err) + } + punits := slices.Sorted(maps.Keys(status.Punits)) + if len(punits) == 0 { + return fmt.Errorf("package %d has no punits, cannot determine default level", pkgs[0].ID()) + } + level = status.Punits[punits[0]].PP.CurrentLevel + } + + type infoOutput struct { + Level int `json:"level"` + Packages map[utils.ID]map[utils.ID]*sst.PerfLevelInfo `json:"packages"` + } + out := infoOutput{ + Level: level, + Packages: make(map[utils.ID]map[utils.ID]*sst.PerfLevelInfo, len(pkgs)), + } + for _, pkg := range pkgs { + info, err := pkg.GetPerfLevelInfo(level) + if err != nil { + return fmt.Errorf("failed to get level %d info for package %d: %w", level, pkg.ID(), err) + } + out.Packages[utils.ID(pkg.ID())] = info + } + + switch strings.ToLower(format) { + case "json": + data, err := json.MarshalIndent(out, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal info: %w", err) + } + fmt.Println(string(data)) + case "yaml": + data, err := yaml.Marshal(out) + if err != nil { + return fmt.Errorf("failed to marshal info: %w", err) + } + fmt.Print(string(data)) + case "table": + printInfoTable(level, out.Packages) + default: + return fmt.Errorf("unknown format %q (valid: table, yaml, json)", format) + } + return nil +} + +// nolint:errcheck +func printInfoTable(level int, allInfo map[utils.ID]map[utils.ID]*sst.PerfLevelInfo) { + freqsStr := func(freqs []sst.TRLFreqInfo) string { + if len(freqs) == 0 { + return "none" + } + parts := make([]string, len(freqs)) + for i, f := range freqs { + parts[i] = strconv.Itoa(f.ID) + ":" + strconv.Itoa(f.Freq) + } + return strings.Join(parts, ", ") + } + + idSetStr := func(s utils.IDSet) string { + if len(s) == 0 { + return "none" + } + return s.String() + } + + newTable := func() *tabwriter.Writer { + return tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + } + + pkgIDs := slices.Sorted(maps.Keys(allInfo)) + + // PP table + fmt.Printf("SST-PP (Perf Profile) [Level %d]:\n", level) + w := newTable() + fmt.Fprintln(w, " PKG\tPUNIT\tCPUS") + for _, pkgID := range pkgIDs { + for _, punitID := range slices.Sorted(maps.Keys(allInfo[pkgID])) { + pu := allInfo[pkgID][punitID] + fmt.Fprintf(w, " %d\t%d\t%s\n", pkgID, punitID, idSetStr(pu.CPUs)) + } + } + w.Flush() + + // BF table + fmt.Printf("\nSST-BF (Base Frequency) [Level %d]:\n", level) + w = newTable() + fmt.Fprintln(w, " PKG\tPUNIT\tSUPPORTED\tHP BASE FREQ (MHz)\tLP BASE FREQ (MHz)\tHP CPUS") + for _, pkgID := range pkgIDs { + for _, punitID := range slices.Sorted(maps.Keys(allInfo[pkgID])) { + bf := allInfo[pkgID][punitID].BF + hpFreq, lpFreq, hpCPUs := "-", "-", "-" + if bf.Supported { + hpFreq = strconv.Itoa(bf.HighPriorityBaseFreq) + lpFreq = strconv.Itoa(bf.LowPriorityBaseFreq) + hpCPUs = idSetStr(bf.HighPriorityCPUs) + } + fmt.Fprintf(w, " %d\t%d\t%v\t%s\t%s\t%s\n", + pkgID, punitID, bf.Supported, hpFreq, lpFreq, hpCPUs) + } + } + w.Flush() + + // TF tables + fmt.Printf("\nSST-TF (Turbo Frequency) [Level %d]:\n", level) + w = newTable() + fmt.Fprintln(w, " PKG\tPUNIT\tSUPPORTED\tLP CLIP FREQS PER TRL ID (MHz)") + for _, pkgID := range pkgIDs { + for _, punitID := range slices.Sorted(maps.Keys(allInfo[pkgID])) { + tf := allInfo[pkgID][punitID].TF + lpClip := "-" + if tf.Supported { + lpClip = freqsStr(tf.LPClipFreqs) + } + fmt.Fprintf(w, " %d\t%d\t%v\t%s\n", pkgID, punitID, tf.Supported, lpClip) + } + } + w.Flush() + + fmt.Printf("\nSST-TF (Turbo Frequency) Buckets [Level %d]:\n", level) + w = newTable() + fmt.Fprintln(w, " PKG\tPUNIT\tBUCKET\tHP CORES\tMAX FREQS PER TRL ID (MHz)") + for _, pkgID := range pkgIDs { + for _, punitID := range slices.Sorted(maps.Keys(allInfo[pkgID])) { + for _, bucket := range allInfo[pkgID][punitID].TF.Buckets { + fmt.Fprintf(w, " %d\t%d\t%d\t%d\t%s\n", + pkgID, punitID, bucket.ID, bucket.HighPriorityCoreCount, freqsStr(bucket.MaxFreqs)) + } + } + } + w.Flush() +} + func subCmdBF(args []string) error { if len(args) < 1 { return fmt.Errorf("usage: bf [options]") diff --git a/pkg/sst/backend.go b/pkg/sst/backend.go index 5a8caf2..d5b4281 100644 --- a/pkg/sst/backend.go +++ b/pkg/sst/backend.go @@ -17,20 +17,24 @@ limitations under the License. package sst import ( + "errors" "fmt" "math" "math/bits" "sync" - "github.com/intel/goresctrl/pkg/sst/isst" - "github.com/intel/goresctrl/pkg/sst/mbox" - "github.com/intel/goresctrl/pkg/sst/tpmi" + "github.com/intel/goresctrl/pkg/sst/internal/isst" + "github.com/intel/goresctrl/pkg/sst/internal/mbox" + "github.com/intel/goresctrl/pkg/sst/internal/tpmi" "github.com/intel/goresctrl/pkg/utils" ) // numClos is the number of CLOSes supported. const numClos = 4 +// errLevelNotAvailable is returned when a requested PP level is not available on a punit. +var errLevelNotAvailable = fmt.Errorf("level not available") + // backend handles hardware dispatch, providing a unified interface for both TPMI and Mbox interfaces. type backend struct { apiVersion int @@ -138,11 +142,31 @@ func ppLevelsFromMax(maxLevel int) []int { return levels } +// puCPUMaskToIDSet converts a 64-bit punit core bitmask to a set of logical CPU IDs +// using the punit's coreCPUs reverse map. +func puCPUMaskToIDSet(pu *punitInfo, mask uint64) utils.IDSet { + cpus := make(utils.IDSet) + for m := mask; m != 0; m &= m - 1 { + bit := utils.ID(bits.TrailingZeros64(m)) + if set, ok := pu.coreCPUs[bit]; ok { + cpus.Add(set.Members()...) + } + } + return cpus +} + +func (h *backend) getPackagePerfLevelInfo(pkg *cpuPackageInfo, level int) (map[utils.ID]*PerfLevelInfo, error) { + if h.isTPMIPlatform() { + return getPackagePerfLevelInfoTPMI(pkg, level) + } + return getPerfLevelInfoMbox(pkg, level) +} + func (h *backend) getPackageStatus(pkg *cpuPackageInfo) (*PackageStatus, error) { if h.isTPMIPlatform() { - return getPackageInfoTPMI(pkg) + return getPackageStatusTPMI(pkg) } - return getPackageInfoMbox(pkg) + return getPackageStatusMbox(pkg) } func (h *backend) cpSetStatus(pkg *cpuPackageInfo, pu *punitInfo, enable bool) error { @@ -319,104 +343,97 @@ func tpmiGetCPUTopo(cpu uint16) (tpmiCPUTopo, error) { return tpmiCPUTopo{socketID: socketID, punitID: punitID, punitCoreID: punitCoreID}, nil } -// getPackageInfoTPMI reads SST information for one CPU package via the TPMI interface. -func getPackageInfoTPMI(pkg *cpuPackageInfo) (*PackageStatus, error) { - socketID := pkg.id +// getPackageStatusTPMI reads SST information for one CPU package via the TPMI interface. +func getPunitStatusTPMI(socketID uint8, pu *punitInfo) (*PunitStatus, error) { + punitID := pu.id - info := &PackageStatus{ - ID: utils.ID(socketID), - Punits: make(map[utils.ID]*PunitStatus), + perfInfo, err := tpmi.PPGetPerfLevels(socketID, punitID) + if err != nil { + return nil, err } - for _, pu := range pkg.punits { - punitID := pu.id - - perfInfo, err := tpmi.PPGetPerfLevels(socketID, punitID) - if err != nil { - return nil, err - } + punit := &PunitStatus{ + CPUs: pu.cpus.Clone(), + PP: PPStatus{ + Supported: perfInfo.Enabled != 0, + Locked: perfInfo.Locked != 0, + Version: int(perfInfo.Feature_rev), + CurrentLevel: int(perfInfo.Current_level), + MaxLevel: int(perfInfo.Max_level), + Levels: ppLevelsFromMask(perfInfo.Level_mask), + }, + BF: BFStatus{ + Supported: perfInfo.Sst_bf_support != 0, + Enabled: (perfInfo.Feature_state & 0x01) != 0, + }, + TF: TFStatus{ + Supported: perfInfo.Sst_tf_support != 0, + Enabled: (perfInfo.Feature_state & 0x02) != 0, + }, + } - punit := &PunitStatus{ - CPUs: pu.cpus.Clone(), - PP: PPStatus{ - Supported: perfInfo.Enabled != 0, - Locked: perfInfo.Locked != 0, - Version: int(perfInfo.Feature_rev), - CurrentLevel: int(perfInfo.Current_level), - MaxLevel: int(perfInfo.Max_level), - Levels: ppLevelsFromMask(perfInfo.Level_mask), - }, - BF: BFStatus{ - Supported: perfInfo.Sst_bf_support != 0, - Enabled: (perfInfo.Feature_state & 0x01) != 0, - }, - TF: TFStatus{ - Supported: perfInfo.Sst_tf_support != 0, - Enabled: (perfInfo.Feature_state & 0x02) != 0, - }, - } + // Read CP state for this punit. + cpState, err := tpmi.CPGetState(socketID, punitID) + if err != nil { + return nil, err + } + punit.CP.Supported = cpState.Supported != 0 + punit.CP.Enabled = cpState.Enable != 0 + punit.CP.Priority = CPPriorityType(cpState.Priority_type) - // Read BF cores for this punit - if punit.BF.Supported { - punit.BF.Cores = utils.IDSet{} - mask, err := tpmi.BFGetCoreMask(socketID, punitID, uint8(perfInfo.Current_level)) + if punit.CP.Supported { + punit.Clos = make([]ClosStatus, numClos) + for i := 0; i < numClos; i++ { + closParam, err := tpmi.ClosGetParam(socketID, punitID, uint8(i)) if err != nil { return nil, err } - for m := mask; m != 0; m &= m - 1 { - bit := utils.ID(bits.TrailingZeros64(m)) - if cpus, ok := pu.coreCPUs[bit]; ok { - punit.BF.Cores.Add(cpus.Members()...) - } + punit.Clos[i] = ClosStatus{ + Config: ClosConfig{ + ProportionalPriority: int(closParam.Prop_prio), + MinFreq: int(closParam.Min_freq_mhz), + MaxFreq: int(closParam.Max_freq_mhz), + }, } } - - // Read CP state for this punit - cpState, err := tpmi.CPGetState(socketID, punitID) - if err != nil { - return nil, err - } - punit.CP.Supported = cpState.Supported != 0 - punit.CP.Enabled = cpState.Enable != 0 - punit.CP.Priority = CPPriorityType(cpState.Priority_type) - - if punit.CP.Supported { - punit.Clos = make([]ClosStatus, numClos) - for i := 0; i < numClos; i++ { - closParam, err := tpmi.ClosGetParam(socketID, punitID, uint8(i)) - if err != nil { - return nil, err - } - punit.Clos[i] = ClosStatus{ - Config: ClosConfig{ - ProportionalPriority: int(closParam.Prop_prio), - MinFreq: int(closParam.Min_freq_mhz), - MaxFreq: int(closParam.Max_freq_mhz), - }, - } + for _, id := range pu.cpus.Members() { + closID, err := tpmi.GetCPUClosID(socketID, punitID, uint16(pu.cpuPunitCoreID[id])) + if err != nil { + sstlog.Debug("failed to get CLOS id for cpu", "cpu", id, "error", err) + continue } - for _, id := range pu.cpus.Members() { - closID, err := tpmi.GetCPUClosID(socketID, punitID, uint16(pu.cpuPunitCoreID[id])) - if err != nil { - sstlog.Debug("failed to get CLOS id for cpu", "cpu", id, "error", err) - continue - } - if punit.Clos[closID].CPUs == nil { - punit.Clos[closID].CPUs = utils.NewIDSet(id) - } else { - punit.Clos[closID].CPUs.Add(id) - } + if punit.Clos[closID].CPUs == nil { + punit.Clos[closID].CPUs = utils.NewIDSet(id) + } else { + punit.Clos[closID].CPUs.Add(id) } } + } + + return punit, nil +} + +func getPackageStatusTPMI(pkg *cpuPackageInfo) (*PackageStatus, error) { + socketID := pkg.id + + info := &PackageStatus{ + ID: utils.ID(socketID), + Punits: make(map[utils.ID]*PunitStatus), + } - info.Punits[utils.ID(punitID)] = punit + for _, pu := range pkg.punits { + punit, err := getPunitStatusTPMI(socketID, pu) + if err != nil { + return nil, err + } + info.Punits[utils.ID(pu.id)] = punit } return info, nil } -// getPackageInfoMbox reads SST information for one CPU package via the Mbox interface. -func getPackageInfoMbox(pkg *cpuPackageInfo) (*PackageStatus, error) { +// getPackageStatusMbox reads SST information for one CPU package via the Mbox interface. +func getPackageStatusMbox(pkg *cpuPackageInfo) (*PackageStatus, error) { cpu := uint16(pkg.cpus.Members()[0]) ppInfo, err := mbox.PPReadInfo(cpu) @@ -454,30 +471,6 @@ func getPackageInfoMbox(pkg *cpuPackageInfo) (*PackageStatus, error) { return nil, err } - // Read BF cores - var bfCores utils.IDSet - if control.BFSupported { - bfCores = utils.IDSet{} - pu := pkg.punits[0] - var maxCoreID utils.ID - for coreID := range pu.coreCPUs { - if coreID > maxCoreID { - maxCoreID = coreID - } - } - for i := 0; i <= int(maxCoreID)/32; i++ { - mask, err := mbox.BFReadCoreMask(cpu, ppInfo.CurrentLevel, i) - if err != nil { - return nil, err - } - // Iterate over the set bits in the mask - for m := mask; m != 0; m &= m - 1 { - bit := bits.TrailingZeros32(m) - bfCores.Add(pu.coreCPUs[utils.ID(i*32+bit)].Members()...) - } - } - } - cpStatus, err := mbox.CPReadStatus(cpu) if err != nil { return nil, err @@ -547,10 +540,193 @@ func getPackageInfoMbox(pkg *cpuPackageInfo) (*PackageStatus, error) { }, Clos: closInfos, } - if control.BFSupported && bfCores != nil { - punit.BF.Cores = bfCores - } info.Punits[0] = punit return info, nil } + +// getPunitPerfLevelInfoTPMI retrieves detailed PP level info for a single TPMI punit. +func getPunitPerfLevelInfoTPMI(socketID uint8, pu *punitInfo, level int) (*PerfLevelInfo, error) { + punitID := pu.id + + // Validate requested level against this punit's level mask. + perfInfo, err := tpmi.PPGetPerfLevels(socketID, punitID) + if err != nil { + return nil, fmt.Errorf("failed to read perf levels: %w", err) + } + if perfInfo.Level_mask&(1< ppInfo.MaxLevel { + return nil, fmt.Errorf("level %d not available (max %d)", level, ppInfo.MaxLevel) + } + + cpuMask, err := mbox.PerfLevelGetCoreMask64(cpu, level) + if err != nil { + return nil, err + } + + info := &PerfLevelInfo{ + CPUs: puCPUMaskToIDSet(pu, cpuMask), + } + + if ppInfo.Supported { + control, err := mbox.PPReadTDPControl(cpu, level) + if err != nil { + return nil, err + } + + // BF info. + info.BF.Supported = control.BFSupported + if control.BFSupported { + bfData, err := mbox.BFGetInfo(cpu, level) + if err != nil { + return nil, fmt.Errorf("BF level data: %w", err) + } + info.BF.HighPriorityBaseFreq = bfData.HighPriorityBaseFreqRatio * 100 + info.BF.LowPriorityBaseFreq = bfData.LowPriorityBaseFreqRatio * 100 + info.BF.HighPriorityCPUs = puCPUMaskToIDSet(pu, bfData.CoreMask) + } + + // TF info. + info.TF.Supported = control.TFSupported + if control.TFSupported { + tfData, err := mbox.TFGetInfo(cpu, level) + if err != nil { + return nil, fmt.Errorf("TF level data: %w", err) + } + var lpClip []TRLFreqInfo + for i, r := range tfData.LPClipRatios { + if f := r * 100; f != 0 { + lpClip = append(lpClip, TRLFreqInfo{ID: i, Freq: f}) + } + } + var tfBuckets []TFBucketInfo + for bucketID, hpCoreCount := range tfData.HPCoreCounts { + if hpCoreCount == 0 { + continue + } + var freqs []TRLFreqInfo + for trlLvl, ratios := range tfData.HPTRLRatios { + if f := ratios[bucketID] * 100; f != 0 { + freqs = append(freqs, TRLFreqInfo{ID: trlLvl, Freq: f}) + } + } + tfBuckets = append(tfBuckets, TFBucketInfo{ + ID: bucketID, + HighPriorityCoreCount: hpCoreCount, + MaxFreqs: freqs, + }) + } + info.TF.LPClipFreqs = lpClip + info.TF.Buckets = tfBuckets + } + } + + return map[utils.ID]*PerfLevelInfo{0: info}, nil +} diff --git a/pkg/sst/isst/_types_amd64.go b/pkg/sst/internal/isst/_types_amd64.go similarity index 100% rename from pkg/sst/isst/_types_amd64.go rename to pkg/sst/internal/isst/_types_amd64.go diff --git a/pkg/sst/internal/isst/_types_msr_amd64.go b/pkg/sst/internal/isst/_types_msr_amd64.go new file mode 100644 index 0000000..93d0403 --- /dev/null +++ b/pkg/sst/internal/isst/_types_msr_amd64.go @@ -0,0 +1,35 @@ +//go:build amd64 +// +build amd64 + +/* +Copyright 2026 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file is used for auto-generation of types_msr_amd64.go. +// Regenerate with: go generate ./pkg/sst/... +// Requires kernel source tree at $KERNEL_SRC_DIR (default: /usr/src/linux). +package isst + +// #include +// #include +// +import "C" + +const ( + ISST_IF_MSR_COMMAND = C.ISST_IF_MSR_COMMAND +) + +type MsrCmd C.struct_isst_if_msr_cmd +type MsrCmds C.struct_isst_if_msr_cmds diff --git a/pkg/sst/isst/_types_priv.go b/pkg/sst/internal/isst/_types_priv.go similarity index 100% rename from pkg/sst/isst/_types_priv.go rename to pkg/sst/internal/isst/_types_priv.go diff --git a/pkg/sst/isst/gen_types.sh b/pkg/sst/internal/isst/gen_types.sh similarity index 88% rename from pkg/sst/isst/gen_types.sh rename to pkg/sst/internal/isst/gen_types.sh index 32a3d9d..8641e68 100755 --- a/pkg/sst/isst/gen_types.sh +++ b/pkg/sst/internal/isst/gen_types.sh @@ -22,6 +22,7 @@ echo "INFO: using kernel sources at $KERNEL_SRC_DIR" # Generate types from Linux kernel (public) headers generate types_amd64.go -I"$KERNEL_SRC_DIR/include/uapi" "-I$KERNEL_SRC_DIR/include" +generate types_msr_amd64.go -I"$KERNEL_SRC_DIR/include/uapi" "-I$KERNEL_SRC_DIR/include" # Generate constants from Linux kernel private headers (isst tool sources) generate types_priv.go "-I$KERNEL_SRC_DIR" "-I$KERNEL_SRC_DIR/include" "-I$KERNEL_SRC_DIR/arch/x86/include/generated/" diff --git a/pkg/sst/isst/isst.go b/pkg/sst/internal/isst/isst.go similarity index 100% rename from pkg/sst/isst/isst.go rename to pkg/sst/internal/isst/isst.go diff --git a/pkg/sst/internal/isst/msr.go b/pkg/sst/internal/isst/msr.go new file mode 100644 index 0000000..6385026 --- /dev/null +++ b/pkg/sst/internal/isst/msr.go @@ -0,0 +1,45 @@ +// Copyright 2026 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build amd64 + +package isst + +import ( + "fmt" + "unsafe" +) + +// SendMSRCmd reads or writes one MSR via the ISST interface. +// If write is false, data is populated with the MSR value on return. +func SendMSRCmd(cpu uint16, msr uint64, write bool, data *uint64) error { + var rw uint32 + if write { + rw = 1 + } + req := MsrCmds{ + Cmd_count: 1, + Msr_cmd: [1]MsrCmd{{ + Read_write: rw, + Logical_cpu: uint32(cpu), + Msr: msr, + Data: *data, + }}, + } + if err := Ioctl(ISST_IF_MSR_COMMAND, uintptr(unsafe.Pointer(&req))); err != nil { + return fmt.Errorf("MSR 0x%x command for cpu %d: %w", msr, cpu, err) + } + *data = req.Msr_cmd[0].Data + return nil +} diff --git a/pkg/sst/isst/types_amd64.go b/pkg/sst/internal/isst/types_amd64.go similarity index 100% rename from pkg/sst/isst/types_amd64.go rename to pkg/sst/internal/isst/types_amd64.go diff --git a/pkg/sst/internal/isst/types_msr_amd64.go b/pkg/sst/internal/isst/types_msr_amd64.go new file mode 100644 index 0000000..d3f0943 --- /dev/null +++ b/pkg/sst/internal/isst/types_msr_amd64.go @@ -0,0 +1,19 @@ +// Code generated by cmd/cgo -godefs; DO NOT EDIT. +// cgo -godefs -- -I/usr/src/linux/include/uapi -I/usr/src/linux/include _types_msr_amd64.go + +package isst + +const ( + ISST_IF_MSR_COMMAND = 0xc008fe04 +) + +type MsrCmd struct { + Read_write uint32 + Logical_cpu uint32 + Msr uint64 + Data uint64 +} +type MsrCmds struct { + Cmd_count uint32 + Msr_cmd [1]MsrCmd +} diff --git a/pkg/sst/isst/types_priv.go b/pkg/sst/internal/isst/types_priv.go similarity index 100% rename from pkg/sst/isst/types_priv.go rename to pkg/sst/internal/isst/types_priv.go diff --git a/pkg/sst/mbox/mbox.go b/pkg/sst/internal/mbox/mbox.go similarity index 75% rename from pkg/sst/mbox/mbox.go rename to pkg/sst/internal/mbox/mbox.go index bea2f1b..fcabe67 100644 --- a/pkg/sst/mbox/mbox.go +++ b/pkg/sst/internal/mbox/mbox.go @@ -19,7 +19,7 @@ package mbox import ( "fmt" - "github.com/intel/goresctrl/pkg/sst/isst" + "github.com/intel/goresctrl/pkg/sst/internal/isst" ) // numClos is the number of CLOSes supported by SST-CP. @@ -195,7 +195,7 @@ func CPSendClosCmd(cpu uint16, subCmd uint16, parameter uint32, reqData uint32) return isst.SendMMIOCmd(cpu, (id<<2)+offset, reqData, isBitSet(parameter, isst.MBOX_CMD_WRITE_BIT)) } -// ClosSetParam writes CLOS parameters for one CLOS. Frequency values are 8-bit ratios (×100 = MHz). +// ClosSetParam writes CLOS parameters for one CLOS. Frequency values are 8-bit ratios (x100 = MHz). func ClosSetParam(cpu uint16, clos, epp, proportional, minFreq, maxFreq, desiredFreq uint8) error { req := uint32(epp & 0x0f) req |= uint32((proportional)&0x0f) << 4 @@ -311,7 +311,96 @@ func TFSetStatus(cpu uint16, ppCurrentLevel int, enable bool) error { return nil } -// getBits extracts bits i..j (inclusive) from val. +// PerfLevelGetCoreMask64 reads the 64-bit punit core bitmask for a PP level. +func PerfLevelGetCoreMask64(cpu uint16, level int) (uint64, error) { + lo, err := isst.SendMboxCmd(cpu, isst.CONFIG_TDP, isst.CONFIG_TDP_GET_CORE_MASK, 0, uint32(level)) + if err != nil { + return 0, fmt.Errorf("failed to read level core mask (lo) at level %d: %w", level, err) + } + hi, err := isst.SendMboxCmd(cpu, isst.CONFIG_TDP, isst.CONFIG_TDP_GET_CORE_MASK, 0, uint32(level|(1<<8))) + if err != nil { + return 0, fmt.Errorf("failed to read level core mask (hi) at level %d: %w", level, err) + } + return uint64(lo) | (uint64(hi) << 32), nil +} + +// BFInfo holds SST-BF properties for one PP level. +type BFInfo struct { + HighPriorityBaseFreqRatio int // ratio (x100 = MHz) + LowPriorityBaseFreqRatio int // ratio (x100 = MHz) + CoreMask uint64 +} + +// BFGetInfo reads SST-BF properties for one PP level. +func BFGetInfo(cpu uint16, level int) (BFInfo, error) { + p1, err := isst.SendMboxCmd(cpu, isst.CONFIG_TDP, isst.CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO, 0, uint32(level)) + if err != nil { + return BFInfo{}, fmt.Errorf("failed to read BF P1HI/P1LO at level %d: %w", level, err) + } + lo, err := BFReadCoreMask(cpu, level, 0) + if err != nil { + return BFInfo{}, err + } + hi, err := BFReadCoreMask(cpu, level, 1) + if err != nil { + return BFInfo{}, err + } + return BFInfo{ + HighPriorityBaseFreqRatio: int(getBits(p1, 8, 15)), + LowPriorityBaseFreqRatio: int(getBits(p1, 0, 7)), + CoreMask: uint64(lo) | (uint64(hi) << 32), + }, nil +} + +// TFInfo holds SST-TF properties for one PP level. +// Note: Mbox TF only exposes up to 3 TRL levels (SSE=0, AVX2=1, AVX512=2). +type TFInfo struct { + LPClipRatios [3]int // x100 = MHz, indices 0=SSE, 1=AVX2, 2=AVX512 + HPCoreCounts [8]int // high-priority core count per bucket + HPTRLRatios [3][8]int // [trllevel][bucket] ratio, x100 = MHz +} + +// TFGetInfo reads SST-TF properties for one PP level. +func TFGetInfo(cpu uint16, level int) (TFInfo, error) { + var d TFInfo + + // LP clip ratios (3 ISA levels packed in one response) + lp, err := isst.SendMboxCmd(cpu, isst.CONFIG_TDP, isst.CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO, 0, uint32(level)) + if err != nil { + return d, fmt.Errorf("failed to read TF LP clip ratios at level %d: %w", level, err) + } + d.LPClipRatios[0] = int(getBits(lp, 0, 7)) + d.LPClipRatios[1] = int(getBits(lp, 8, 15)) + d.LPClipRatios[2] = int(getBits(lp, 16, 23)) + + // HP core counts (8 buckets, 4 per read) + for i := range 2 { + rsp, err := isst.SendMboxCmd(cpu, isst.CONFIG_TDP, isst.CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES, 0, + uint32(level|(i<<8))) + if err != nil { + return d, fmt.Errorf("failed to read TF HP core counts at level %d: %w", level, err) + } + for j := range 4 { + d.HPCoreCounts[i*4+j] = int(getBits(rsp, uint32(j*8), uint32(j*8+7))) + } + } + + // HP TRL ratios for each TRL level + for k := range 3 { + for i := range 2 { + rsp, err := isst.SendMboxCmd(cpu, isst.CONFIG_TDP, isst.CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS, 0, + uint32(level|(i<<8)|(k<<16))) + if err != nil { + return d, fmt.Errorf("failed to read TF HP TRL ratios for perf level %d TRL level %d: %w", level, k, err) + } + for j := range 4 { + d.HPTRLRatios[k][i*4+j] = int(getBits(rsp, uint32(j*8), uint32(j*8+7))) + } + } + } + return d, nil +} + func getBits(val, i, j uint32) uint32 { lsb := i msb := j diff --git a/pkg/sst/tpmi/tpmi.go b/pkg/sst/internal/tpmi/tpmi.go similarity index 84% rename from pkg/sst/tpmi/tpmi.go rename to pkg/sst/internal/tpmi/tpmi.go index bfc0072..c01254a 100644 --- a/pkg/sst/tpmi/tpmi.go +++ b/pkg/sst/internal/tpmi/tpmi.go @@ -21,7 +21,7 @@ import ( "math/bits" "unsafe" - "github.com/intel/goresctrl/pkg/sst/isst" + "github.com/intel/goresctrl/pkg/sst/internal/isst" ) // numClos is the number of CLOSes supported by SST-CP. @@ -244,3 +244,35 @@ func TFSetStatus(socketID, punitID uint8, enable bool) error { } return nil } + +// PerfLevelGetCPUMask reads the punit core bitmask for a performance level. +func PerfLevelGetCPUMask(socketID, punitID, level uint8) (uint64, error) { + cpuMask := isst.PerfLevelCpuMask{ + Socket_id: socketID, + Power_domain_id: punitID, + Level: level, + Punit_cpu_map: 1, + } + if err := isst.Ioctl(isst.ISST_IF_GET_PERF_LEVEL_CPU_MASK, uintptr(unsafe.Pointer(&cpuMask))); err != nil { + return 0, fmt.Errorf("ISST_IF_GET_PERF_LEVEL_CPU_MASK for socket %d punit %d level %d: %w", socketID, punitID, level, err) + } + return cpuMask.Mask, nil +} + +// BFGetInfo reads SST-BF frequency info for a performance level. +func BFGetInfo(socketID, punitID, level uint8) (isst.BaseFreqInfo, error) { + info := isst.BaseFreqInfo{Socket_id: socketID, Power_domain_id: punitID, Level: uint16(level)} + if err := isst.Ioctl(isst.ISST_IF_GET_BASE_FREQ_INFO, uintptr(unsafe.Pointer(&info))); err != nil { + return isst.BaseFreqInfo{}, fmt.Errorf("ISST_IF_GET_BASE_FREQ_INFO for socket %d punit %d level %d: %w", socketID, punitID, level, err) + } + return info, nil +} + +// TFGetInfo reads SST-TF frequency info for a performance level. +func TFGetInfo(socketID, punitID, level uint8) (isst.TurboFreqInfo, error) { + info := isst.TurboFreqInfo{Socket_id: socketID, Power_domain_id: punitID, Level: uint16(level)} + if err := isst.Ioctl(isst.ISST_IF_GET_TURBO_FREQ_INFO, uintptr(unsafe.Pointer(&info))); err != nil { + return isst.TurboFreqInfo{}, fmt.Errorf("ISST_IF_GET_TURBO_FREQ_INFO for socket %d punit %d level %d: %w", socketID, punitID, level, err) + } + return info, nil +} diff --git a/pkg/sst/legacy_api.go b/pkg/sst/legacy_api.go index 1727398..229733d 100644 --- a/pkg/sst/legacy_api.go +++ b/pkg/sst/legacy_api.go @@ -126,6 +126,20 @@ func GetPackageInfo(pkgs ...int) (map[int]*SstPackageInfo, error) { if err != nil { return nil, fmt.Errorf("failed to convert SST info for package %d: %w", i, err) } + if info.BFSupported { + levelInfo, err := legacyBackend.getPackagePerfLevelInfo(packages[i], info.PPCurrentLevel) + if err != nil { + sstlog.Warn("failed to get BF core info for legacy API", "package", i, "level", info.PPCurrentLevel, "error", err) + } else { + for _, puInfo := range levelInfo { + if info.BFCores == nil { + info.BFCores = puInfo.BF.HighPriorityCPUs.Clone() + } else { + info.BFCores.Add(puInfo.BF.HighPriorityCPUs.Members()...) + } + } + } + } result[i] = info } @@ -462,10 +476,6 @@ func statusFromPackage(pkg *cpuPackageInfo, pi *PackageStatus) (*SstPackageInfo, ClosStatus: closInfos, ClosCPUInfo: make(ClosCPUSet), } - if primary.BF.Cores != nil { - info.BFCores = primary.BF.Cores.Clone() - } - // Merge per-CLOS CPU sets from all punits (package-wide association). for _, punit := range pi.Punits { for i, ci := range punit.Clos { diff --git a/pkg/sst/log.go b/pkg/sst/log.go index a2655a6..9fb78e1 100644 --- a/pkg/sst/log.go +++ b/pkg/sst/log.go @@ -19,7 +19,7 @@ package sst import ( "log/slog" - "github.com/intel/goresctrl/pkg/sst/isst" + "github.com/intel/goresctrl/pkg/sst/internal/isst" ) var sstlog *slog.Logger = slog.Default() diff --git a/pkg/sst/package.go b/pkg/sst/package.go index 38da53c..6dba85f 100644 --- a/pkg/sst/package.go +++ b/pkg/sst/package.go @@ -50,9 +50,8 @@ type PPStatus struct { // BFStatus contains SST-BF (Base Frequency) state. type BFStatus struct { - Supported bool `json:"supported"` - Enabled bool `json:"enabled"` - Cores utils.IDSet `json:"cores,omitempty"` + Supported bool `json:"supported"` + Enabled bool `json:"enabled"` } // TFStatus contains SST-TF (Turbo Frequency) state. @@ -103,6 +102,52 @@ func (p CPPriorityType) Validate() bool { } } +// PerfLevelInfo contains detailed SST-PP information for one performance level. +type PerfLevelInfo struct { + CPUs utils.IDSet `json:"cpus"` + BF BFInfo `json:"bf"` + TF TFInfo `json:"tf"` +} + +// BFInfo contains SST-BF (Base Frequency) properties for one performance level. +type BFInfo struct { + Supported bool `json:"supported"` + HighPriorityBaseFreq int `json:"highPriorityBaseFreq,omitempty"` // MHz + LowPriorityBaseFreq int `json:"lowPriorityBaseFreq,omitempty"` // MHz + HighPriorityCPUs utils.IDSet `json:"highPriorityCPUs,omitempty"` +} + +// TFInfo contains SST-TF (Turbo Frequency) properties for one performance level. +// +// Buckets group CPUs into high-priority sets by core count. Each bucket +// defines how many cores receive elevated turbo frequencies and the maximum +// frequency allowed at each TRL level for the priority cores. +// +// TRL (Turbo Ratio Limit) are limits for certain instruction-set workload +// classes (e.g. SSE, AVX2, AVX-512). +type TFInfo struct { + Supported bool `json:"supported"` + // LPClipFreqs contains the low-priority core frequency limits for each TRL level. + LPClipFreqs []TRLFreqInfo `json:"lpClipFreqs,omitempty"` + // Buckets contain info about TF buckets. + Buckets []TFBucketInfo `json:"buckets,omitempty"` +} + +// TFBucketInfo holds SST-TF high-priority bucket data for one performance level. +// Empty buckets (core count == 0) are omitted. +type TFBucketInfo struct { + ID int `json:"id"` + HighPriorityCoreCount int `json:"highPriorityCoreCount"` + // MaxFreqs contains maximum turbo frequency for each TRL level. + MaxFreqs []TRLFreqInfo `json:"maxFreqs"` +} + +// TRLFreqInfo holds a TRL level ID and its associated frequency in MHz. +type TRLFreqInfo struct { + ID int `json:"id"` + Freq int `json:"freq"` // MHz +} + // Package provides SST operations for one CPU package. type Package struct { h *Platform @@ -119,6 +164,12 @@ func (p *Package) GetStatus() (*PackageStatus, error) { return p.h.getPackageStatus(p.pkg) } +// GetPerfLevelInfo returns detailed SST-PP data for the given performance level, indexed by punit ID. +// Returns an error if the level is not available on any punit. +func (p *Package) GetPerfLevelInfo(level int) (map[utils.ID]*PerfLevelInfo, error) { + return p.h.getPackagePerfLevelInfo(p.pkg, level) +} + // BFEnable enables SST-BF for this package. // NOTE: The caller should ensure that the sysfs cpufreq scaling limits of the // affected CPUs allow higher base frequency on high-priority cores; see diff --git a/pkg/sst/platform.go b/pkg/sst/platform.go index 8abc94e..c60e4ad 100644 --- a/pkg/sst/platform.go +++ b/pkg/sst/platform.go @@ -21,7 +21,7 @@ import ( "os" "slices" - "github.com/intel/goresctrl/pkg/sst/isst" + "github.com/intel/goresctrl/pkg/sst/internal/isst" "github.com/intel/goresctrl/pkg/utils" )