diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index e588147..9542872 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -32,9 +32,31 @@ jobs: - name: Test run: make test + - name: Cross-build linux/arm64 + run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build ./... + - name: Codecov report run: bash <(curl -s https://codecov.io/bash) + arm64: + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: go.mod + + - uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 + with: + version: v2.13.2 + + - name: Verify + run: make verify + + - name: Test + run: make test + trivy: permissions: contents: read diff --git a/pkg/sst/internal/isst/_types_amd64.go b/pkg/sst/internal/isst/_types.go similarity index 87% rename from pkg/sst/internal/isst/_types_amd64.go rename to pkg/sst/internal/isst/_types.go index 6e52e41..0ebf8c8 100644 --- a/pkg/sst/internal/isst/_types_amd64.go +++ b/pkg/sst/internal/isst/_types.go @@ -17,9 +17,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -// This file is used for auto-generation of types_amd64.go +// This file is used for auto-generation of types.go. The generated +// declarations follow the Linux/amd64 ABI but compile on every architecture +// so that importers build everywhere. Only the amd64 ioctl transport can +// execute them. package isst +// #if !defined(__linux__) || !defined(__x86_64__) +// #error "ISST types must be generated with a Linux amd64 C compiler" +// #endif // #include // #include // diff --git a/pkg/sst/internal/isst/gen_types.sh b/pkg/sst/internal/isst/gen_types.sh index 8641e68..5dde2d1 100755 --- a/pkg/sst/internal/isst/gen_types.sh +++ b/pkg/sst/internal/isst/gen_types.sh @@ -5,6 +5,16 @@ set -o pipefail tmpfile="_types_out.go" trap "rm -f $tmpfile" EXIT +# types.go compiles on every architecture so that importers of pkg/sst build +# everywhere, but its contents are the Linux/amd64 ABI. Refuse to generate it +# for anything else. +target_os="$(go env GOOS)" +target_arch="$(go env GOARCH)" +if [ "$target_os" != "linux" ] || [ "$target_arch" != "amd64" ]; then + echo "ERROR: ISST types must be generated for the linux/amd64 ABI, not $target_os/$target_arch" >&2 + exit 1 +fi + generate() { local target="$1" shift @@ -21,7 +31,7 @@ KERNEL_SRC_DIR="${KERNEL_SRC_DIR:-/usr/src/linux}" 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.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) diff --git a/pkg/sst/internal/isst/ioctl_amd64.go b/pkg/sst/internal/isst/ioctl_amd64.go new file mode 100644 index 0000000..640ea0b --- /dev/null +++ b/pkg/sst/internal/isst/ioctl_amd64.go @@ -0,0 +1,38 @@ +/* +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. +*/ + +package isst + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +// Ioctl executes an ioctl on the Linux isst_if device driver. +func Ioctl(ioctl uintptr, req unsafe.Pointer) error { + devPath := DevPath() + f, err := os.Open(devPath) + if err != nil { + return fmt.Errorf("failed to open isst device %q: %v", devPath, err) + } + defer f.Close() //nolint:errcheck + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), ioctl, uintptr(req)); errno != 0 { + return errno + } + return nil +} diff --git a/pkg/sst/internal/isst/ioctl_unsupported.go b/pkg/sst/internal/isst/ioctl_unsupported.go new file mode 100644 index 0000000..451c873 --- /dev/null +++ b/pkg/sst/internal/isst/ioctl_unsupported.go @@ -0,0 +1,32 @@ +//go: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. +*/ + +package isst + +import ( + "errors" + "unsafe" +) + +var errUnsupportedArchitecture = errors.New("SST ioctls are not supported on non-amd64 architectures") + +// Ioctl rejects SST ioctl requests on architectures where the transport is +// unavailable. +func Ioctl(_ uintptr, _ unsafe.Pointer) error { + return errUnsupportedArchitecture +} diff --git a/pkg/sst/internal/isst/ioctl_unsupported_test.go b/pkg/sst/internal/isst/ioctl_unsupported_test.go new file mode 100644 index 0000000..411f30e --- /dev/null +++ b/pkg/sst/internal/isst/ioctl_unsupported_test.go @@ -0,0 +1,44 @@ +//go: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. +*/ + +package isst + +import ( + "os" + "path/filepath" + "testing" + + goresctrlpath "github.com/intel/goresctrl/pkg/path" +) + +func TestIoctlUnsupportedArchitecture(t *testing.T) { + root := t.TempDir() + devDir := filepath.Join(root, "dev") + if err := os.MkdirAll(devDir, 0o755); err != nil { + t.Fatalf("failed to create fake dev directory: %v", err) + } + if err := os.WriteFile(filepath.Join(devDir, "isst_interface"), nil, 0o600); err != nil { + t.Fatalf("failed to create fake isst device: %v", err) + } + goresctrlpath.SetPrefix(root) + t.Cleanup(func() { goresctrlpath.SetPrefix("/") }) + + if err := Ioctl(0, nil); err != errUnsupportedArchitecture { + t.Fatalf("Ioctl returned unexpected error on a non-amd64 architecture: %v", err) + } +} diff --git a/pkg/sst/internal/isst/isst.go b/pkg/sst/internal/isst/isst.go index 9b8d151..3dc4fd4 100644 --- a/pkg/sst/internal/isst/isst.go +++ b/pkg/sst/internal/isst/isst.go @@ -16,14 +16,16 @@ limitations under the License. package isst +// The generated fixed-width ISST UAPI declarations compile on all +// architectures so packages can import SST everywhere. They are generated +// from the Linux/amd64 ABI, while the ioctl transport rejects execution +// outside amd64. //go:generate ./gen_types.sh import ( "fmt" "log/slog" - "os" "sync" - "syscall" "unsafe" goresctrlpath "github.com/intel/goresctrl/pkg/path" @@ -37,20 +39,6 @@ func SetLogger(l *slog.Logger) { log = l } // DevPath returns the path to the isst_interface device. func DevPath() string { return goresctrlpath.Path("dev/isst_interface") } -// Ioctl executes an ioctl on the linux isst_if device driver. -func Ioctl(ioctl uintptr, req unsafe.Pointer) error { - devPath := DevPath() - f, err := os.Open(devPath) - if err != nil { - return fmt.Errorf("failed to open isst device %q: %v", devPath, err) - } - defer f.Close() //nolint:errcheck - if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), ioctl, uintptr(req)); errno != 0 { - return errno - } - return nil -} - var ( cpuMapMu sync.RWMutex cpuMap = make(map[uint16]uint16) diff --git a/pkg/sst/internal/isst/types_amd64.go b/pkg/sst/internal/isst/types.go similarity index 99% rename from pkg/sst/internal/isst/types_amd64.go rename to pkg/sst/internal/isst/types.go index b12d528..379b99d 100644 --- a/pkg/sst/internal/isst/types_amd64.go +++ b/pkg/sst/internal/isst/types.go @@ -1,5 +1,5 @@ // Code generated by cmd/cgo -godefs; DO NOT EDIT. -// cgo -godefs -- -I/usr/src/linux/include/uapi -I/usr/src/linux/include _types_amd64.go +// cgo -godefs -- -I/usr/src/linux/include/uapi -I/usr/src/linux/include _types.go package isst diff --git a/pkg/sst/platform.go b/pkg/sst/platform.go index c60e4ad..5d8db8c 100644 --- a/pkg/sst/platform.go +++ b/pkg/sst/platform.go @@ -18,10 +18,8 @@ package sst import ( "fmt" - "os" "slices" - "github.com/intel/goresctrl/pkg/sst/internal/isst" "github.com/intel/goresctrl/pkg/utils" ) @@ -31,21 +29,6 @@ type Platform struct { packages map[int]*cpuPackageInfo } -// SstSupported returns true if Intel Speed Select Technologies (SST) is -// supported by the system and can be interfaced via the Linux kernel device. -func SstSupported() bool { - devPath := isst.DevPath() - if _, err := os.Stat(devPath); err != nil { - if !os.IsNotExist(err) { - sstlog.Error("failed to access sst device", "path", devPath, "error", err) - } else { - sstlog.Debug("sst device does not exist", "path", devPath) - } - return false - } - return true -} - // Init initializes SST and returns a handle. The handle stores a snapshot of // CPU topology of online CPUs at init time and is not updated on CPU hotplug. func Init() (*Platform, error) { diff --git a/pkg/sst/platform_amd64.go b/pkg/sst/platform_amd64.go new file mode 100644 index 0000000..a5abaf4 --- /dev/null +++ b/pkg/sst/platform_amd64.go @@ -0,0 +1,38 @@ +/* +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. +*/ + +package sst + +import ( + "os" + + "github.com/intel/goresctrl/pkg/sst/internal/isst" +) + +// SstSupported returns true if Intel Speed Select Technologies (SST) is +// supported by the system and can be interfaced via the Linux kernel device. +func SstSupported() bool { + devPath := isst.DevPath() + if _, err := os.Stat(devPath); err != nil { + if !os.IsNotExist(err) { + sstlog.Error("failed to access sst device", "path", devPath, "error", err) + } else { + sstlog.Debug("sst device does not exist", "path", devPath) + } + return false + } + return true +} diff --git a/pkg/sst/platform_amd64_test.go b/pkg/sst/platform_amd64_test.go new file mode 100644 index 0000000..0470223 --- /dev/null +++ b/pkg/sst/platform_amd64_test.go @@ -0,0 +1,47 @@ +/* +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. +*/ + +package sst + +import ( + "os" + "path/filepath" + "testing" + + goresctrlpath "github.com/intel/goresctrl/pkg/path" +) + +func TestSstSupported(t *testing.T) { + root := t.TempDir() + goresctrlpath.SetPrefix(root) + t.Cleanup(func() { goresctrlpath.SetPrefix("/") }) + + if SstSupported() { + t.Fatal("SstSupported returned true with no isst device") + } + + devDir := filepath.Join(root, "dev") + if err := os.MkdirAll(devDir, 0o755); err != nil { + t.Fatalf("failed to create fake dev directory: %v", err) + } + if err := os.WriteFile(filepath.Join(devDir, "isst_interface"), nil, 0o600); err != nil { + t.Fatalf("failed to create fake isst device: %v", err) + } + + if !SstSupported() { + t.Fatal("SstSupported returned false with an isst device") + } +} diff --git a/pkg/sst/platform_unsupported.go b/pkg/sst/platform_unsupported.go new file mode 100644 index 0000000..621f914 --- /dev/null +++ b/pkg/sst/platform_unsupported.go @@ -0,0 +1,26 @@ +//go: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. +*/ + +package sst + +// SstSupported returns false because Intel Speed Select Technologies (SST) +// cannot be interfaced on non-amd64 architectures. +func SstSupported() bool { + sstlog.Debug("sst is not supported on non-amd64 architectures") + return false +} diff --git a/pkg/sst/platform_unsupported_test.go b/pkg/sst/platform_unsupported_test.go new file mode 100644 index 0000000..fa2c0e3 --- /dev/null +++ b/pkg/sst/platform_unsupported_test.go @@ -0,0 +1,48 @@ +//go: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. +*/ + +package sst + +import ( + "os" + "path/filepath" + "testing" + + goresctrlpath "github.com/intel/goresctrl/pkg/path" +) + +func TestSstUnsupportedArchitecture(t *testing.T) { + root := t.TempDir() + devDir := filepath.Join(root, "dev") + if err := os.MkdirAll(devDir, 0o755); err != nil { + t.Fatalf("failed to create fake dev directory: %v", err) + } + if err := os.WriteFile(filepath.Join(devDir, "isst_interface"), nil, 0o600); err != nil { + t.Fatalf("failed to create fake isst device: %v", err) + } + goresctrlpath.SetPrefix(root) + t.Cleanup(func() { goresctrlpath.SetPrefix("/") }) + + if SstSupported() { + t.Fatal("SstSupported returned true on a non-amd64 architecture with an isst device") + } + + if _, err := Init(); err == nil || err.Error() != "SST not supported on this system" { + t.Fatalf("Init returned unexpected error on a non-amd64 architecture: %v", err) + } +}