diff --git a/.github/workflows/build-checks-preview-riscv64.yaml b/.github/workflows/build-checks-preview-riscv64.yaml index d8af099413..69f872d3f1 100644 --- a/.github/workflows/build-checks-preview-riscv64.yaml +++ b/.github/workflows/build-checks-preview-riscv64.yaml @@ -43,6 +43,11 @@ jobs: - rust - musl-tools - protobuf-compiler + - name: runtime + path: src/runtime + needs: + - golang + - XDG_RUNTIME_DIR - name: runtime-rs path: src/runtime-rs needs: @@ -101,6 +106,10 @@ jobs: run: | XDG_RUNTIME_DIR=$(mktemp -d "/tmp/kata-tests-$USER.XXX" | tee >(xargs chmod 0700)) echo "XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}" >> "$GITHUB_ENV" + - name: Skip tests that depend on virtualization capable runners when needed + if: inputs.instance == 'riscv-builder' + run: | + echo "GITHUB_RUNNER_CI_NON_VIRT=true" >> "$GITHUB_ENV" - name: Running `${{ matrix.command }}` for ${{ matrix.component.name }} run: | cd ${{ matrix.component.path }} diff --git a/src/runtime/Makefile b/src/runtime/Makefile index 192bab185c..71c3e3ad76 100644 --- a/src/runtime/Makefile +++ b/src/runtime/Makefile @@ -20,6 +20,9 @@ endif ifeq ($(ARCH),aarch64) override ARCH = arm64 endif +ifeq ($(ARCH),riscv64gc) + override ARCH = riscv64 +endif ARCH_DIR = arch ARCH_FILE_SUFFIX = -options.mk diff --git a/src/runtime/arch/riscv64-options.mk b/src/runtime/arch/riscv64-options.mk new file mode 100644 index 0000000000..55ba9da804 --- /dev/null +++ b/src/runtime/arch/riscv64-options.mk @@ -0,0 +1,13 @@ +# Copyright (c) 2024 Institute of Software, CAS. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# riscv64 settings + +MACHINETYPE := virt +KERNELPARAMS := +MACHINEACCELERATORS := +CPUFEATURES := + +QEMUCMD := qemu-system-riscv64 diff --git a/src/runtime/cmd/kata-runtime/kata-check_data_riscv64_test.go b/src/runtime/cmd/kata-runtime/kata-check_data_riscv64_test.go new file mode 100644 index 0000000000..8bf5aa8f95 --- /dev/null +++ b/src/runtime/cmd/kata-runtime/kata-check_data_riscv64_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2025 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +const testCPUInfoTemplate = ` +processor : 0 +hart : 1 +isa : rv64imafdch_zicbom_zicboz_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_smaia_ssaia_sstc +mmu : sv48 +mvendorid : 0x0 +marchid : 0x0 +mimpid : 0x0 +hart isa : rv64imafdch_zicbom_zicboz_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_smaia_ssaia_sstc + +processor : 1 +hart : 0 +isa : rv64imafdch_zicbom_zicboz_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_smaia_ssaia_sstc +mmu : sv48 +mvendorid : 0x0 +marchid : 0x0 +mimpid : 0x0 +hart isa : rv64imafdch_zicbom_zicboz_zicntr_zicsr_zifencei_zihintntl_zihintpause_zihpm_zawrs_zfa_zca_zcd_zba_zbb_zbc_zbs_smaia_ssaia_sstc +` diff --git a/src/runtime/cmd/kata-runtime/kata-check_generic_test.go b/src/runtime/cmd/kata-runtime/kata-check_generic_test.go index 18e722e431..e52b0cbcc3 100644 --- a/src/runtime/cmd/kata-runtime/kata-check_generic_test.go +++ b/src/runtime/cmd/kata-runtime/kata-check_generic_test.go @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // -//go:build arm64 || ppc64le +//go:build arm64 || ppc64le || riscv64 package main diff --git a/src/runtime/cmd/kata-runtime/kata-check_riscv64.go b/src/runtime/cmd/kata-runtime/kata-check_riscv64.go new file mode 100644 index 0000000000..02420b0a07 --- /dev/null +++ b/src/runtime/cmd/kata-runtime/kata-check_riscv64.go @@ -0,0 +1,141 @@ +// Copyright (c) 2024 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +import ( + "fmt" + "strings" + + vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers" + "github.com/sirupsen/logrus" +) + +const ( + cpuFlagsTag = genericCPUFlagsTag + archCPUVendorField = "mvenderid" + archCPUModelField = "marchid" +) + +// archRequiredCPUFlags maps a CPU flag value to search for and a +// human-readable description of that value. +var archRequiredCPUFlags = map[string]string{} + +// archRequiredCPUAttribs maps a CPU (non-CPU flag) attribute value to search for +// and a human-readable description of that value. +var archRequiredCPUAttribs = map[string]string{} + +// archRequiredKernelModules maps a required module name to a human-readable +// description of the modules functionality and an optional list of +// required module parameters. +var archRequiredKernelModules = map[string]kernelModule{ + "kvm": { + desc: "Kernel-based Virtual Machine", + required: true, + }, + "vhost": { + desc: "Host kernel accelerator for virtio", + required: true, + }, + "vhost_net": { + desc: "Host kernel accelerator for virtio network", + required: true, + }, + "vhost_vsock": { + desc: "Host Support for Linux VM Sockets", + required: false, + }, +} + +func setCPUtype(hypervisorType vc.HypervisorType) error { + return nil +} + +// kvmIsUsable determines if it will be possible to create a full virtual machine +// by creating a minimal VM and then deleting it. +func kvmIsUsable() error { + return genericKvmIsUsable() +} + +func archHostCanCreateVMContainer(hypervisorType vc.HypervisorType) error { + if hypervisorType == "remote" { + return nil + } + return kvmIsUsable() +} + +// hostIsVMContainerCapable checks to see if the host is theoretically capable +// of creating a VM container. +func hostIsVMContainerCapable(details vmContainerCapableDetails) error { + + _, err := getCPUInfo(details.cpuInfoFile) + if err != nil { + return err + } + + count, err := checkKernelModules(details.requiredKernelModules, archKernelParamHandler) + if err != nil { + return err + } + + if count == 0 { + return nil + } + + return fmt.Errorf("ERROR: %s", failMessage) + +} + +func archKernelParamHandler(onVMM bool, fields logrus.Fields, msg string) bool { + return genericArchKernelParamHandler(onVMM, fields, msg) +} + +func getRiscv64CPUDetails() (vendor, model string, err error) { + prefixModel := "processor" + cpuinfo, err := getCPUInfo(procCPUInfo) + if err != nil { + return "", "", err + } + + lines := strings.Split(cpuinfo, "\n") + + for _, line := range lines { + if archCPUVendorField != "" { + if strings.HasPrefix(line, archCPUVendorField) { + fields := strings.Split(line, ":") + if len(fields) > 1 { + vendor = strings.TrimSpace(fields[1]) + } + } + } else { + vendor = "Unknown" + } + if archCPUModelField != "" { + if strings.HasPrefix(line, prefixModel) { + fields := strings.Split(line, ":") + if len(fields) > 1 { + model = strings.TrimSpace(fields[1]) + } + } + } + } + + if vendor == "" { + return "", "", fmt.Errorf("cannot find vendor field in file %v", procCPUInfo) + } + + if model == "" { + return "", "", fmt.Errorf("Error in parsing cpu model from %v", procCPUInfo) + } + + return vendor, model, nil +} + +func getCPUDetails() (string, string, error) { + if vendor, model, err := genericGetCPUDetails(); err == nil { + return vendor, model, nil + } + return getRiscv64CPUDetails() +} diff --git a/src/runtime/cmd/kata-runtime/kata-check_riscv64_test.go b/src/runtime/cmd/kata-runtime/kata-check_riscv64_test.go new file mode 100644 index 0000000000..ce76915ac9 --- /dev/null +++ b/src/runtime/cmd/kata-runtime/kata-check_riscv64_test.go @@ -0,0 +1,106 @@ +// Copyright (c) 2025 Institute of Software, CAS. +// Copyright (c) 2018 ARM Limited +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func setupCheckHostIsVMContainerCapable(assert *assert.Assertions, cpuInfoFile string, cpuData []testCPUData, moduleData []testModuleData) { + createModules(assert, cpuInfoFile, moduleData) + err := makeCPUInfoFile(cpuInfoFile, "", "") + assert.NoError(err) +} + +func TestCCCheckCLIFunction(t *testing.T) { + if os.Getenv("GITHUB_RUNNER_CI_NON_VIRT") == "true" { + t.Skip("Skipping the test as the GitHub self hosted runners for RISC-V do not support Virtualization") + } + + var cpuData []testCPUData + moduleData := []testModuleData{ + {filepath.Join(sysModuleDir, "kvm"), "", true}, + {filepath.Join(sysModuleDir, "vhost"), "", true}, + {filepath.Join(sysModuleDir, "vhost_net"), "", true}, + } + + genericCheckCLIFunction(t, cpuData, moduleData) +} + +func TestGetCPUDetails(t *testing.T) { + type testData struct { + contents string + expectedNormalizeVendor string + expectedNormalizeModel string + expectError bool + } + + validVendorName := "0x0" + validModelName := "0x0" + validVendor := fmt.Sprintf(`%s : %s`, archCPUVendorField, validVendorName) + validModel := fmt.Sprintf(`%s : %s`, archCPUModelField, validModelName) + + validContents := fmt.Sprintf(` +a : b +%s +foo : bar +%s +`, validVendor, validModel) + + data := []testData{ + {"", "", "", true}, + {"invalid", "", "", true}, + {archCPUVendorField, "", "", true}, + {archCPUModelField, "", "", true}, + {"", validVendorName, "", true}, + {"", "", validModelName, true}, + {validContents, validVendorName, validModelName, false}, + } + + tmpdir := t.TempDir() + + savedProcCPUInfo := procCPUInfo + + testProcCPUInfo := filepath.Join(tmpdir, "cpuinfo") + + // override + procCPUInfo = testProcCPUInfo + + defer func() { + procCPUInfo = savedProcCPUInfo + }() + + _, _, err := getCPUDetails() + // ENOENT + assert.Error(t, err) + assert.True(t, os.IsNotExist(err)) + + for _, d := range data { + err := createFile(procCPUInfo, d.contents) + assert.NoError(t, err) + + vendor, model, err := getCPUDetails() + + if d.expectError { + assert.Error(t, err, fmt.Sprintf("%+v", d)) + continue + } else { + assert.NoError(t, err, fmt.Sprintf("%+v", d)) + assert.Equal(t, d.expectedNormalizeVendor, vendor) + assert.Equal(t, d.expectedNormalizeModel, model) + } + } +} + +func TestSetCPUtype(t *testing.T) { + testSetCPUTypeGeneric(t) +} diff --git a/src/runtime/cmd/kata-runtime/kata-env_riscv64_test.go b/src/runtime/cmd/kata-runtime/kata-env_riscv64_test.go new file mode 100644 index 0000000000..5b8acaf794 --- /dev/null +++ b/src/runtime/cmd/kata-runtime/kata-env_riscv64_test.go @@ -0,0 +1,13 @@ +// Copyright (c) 2025 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package main + +func getExpectedHostDetails(tmpdir string) (HostInfo, error) { + expectedVendor := "0x0" + expectedModel := "0x0" + expectedVMContainerCapable := true + return genericGetExpectedHostDetails(tmpdir, expectedVendor, expectedModel, expectedVMContainerCapable) +} diff --git a/src/runtime/go-test.sh b/src/runtime/go-test.sh index 3f43175829..4f34c2c81b 100755 --- a/src/runtime/go-test.sh +++ b/src/runtime/go-test.sh @@ -22,8 +22,8 @@ if [ -z "$go_test_flags" ]; then # "go test -timeout X" go_test_flags="-timeout ${KATA_GO_TEST_TIMEOUT:-30s}" - # -race flag is not supported on s390x - [ "$(go env GOARCH)" != "s390x" ] && go_test_flags+=" -race" + # -race flag is not supported on s390x and riscv64 + [ "$(go env GOARCH)" != "s390x" ] && [ "$(go env GOARCH)" != "riscv64" ] && go_test_flags+=" -race" # s390x requires special linker flags [ "$(go env GOARCH)" = s390x ] && go_test_flags+=" -ldflags '-extldflags -Wl,--s390-pgste'" diff --git a/src/runtime/pkg/govmm/vmm_riscv64.go b/src/runtime/pkg/govmm/vmm_riscv64.go new file mode 100644 index 0000000000..cd11af702b --- /dev/null +++ b/src/runtime/pkg/govmm/vmm_riscv64.go @@ -0,0 +1,12 @@ +// +// Copyright (c) 2024 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package govmm + +// MaxVCPUs returns the maximum number of vCPUs supported +func MaxVCPUs() uint32 { + return uint32(512) +} diff --git a/src/runtime/virtcontainers/factory/template/template_riscv64.go b/src/runtime/virtcontainers/factory/template/template_riscv64.go new file mode 100644 index 0000000000..ebdba3aaf1 --- /dev/null +++ b/src/runtime/virtcontainers/factory/template/template_riscv64.go @@ -0,0 +1,11 @@ +// Copyright (c) 2024 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 +// +// template implements base vm factory with vm templating. + +package template + +// templateDeviceStateSize denotes device state size when +// mount tmpfs. +const templateDeviceStateSize = 8 diff --git a/src/runtime/virtcontainers/hypervisor_linux_riscv64.go b/src/runtime/virtcontainers/hypervisor_linux_riscv64.go new file mode 100644 index 0000000000..a53fcb6ceb --- /dev/null +++ b/src/runtime/virtcontainers/hypervisor_linux_riscv64.go @@ -0,0 +1,10 @@ +// Copyright (c) 2024 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 + +package virtcontainers + +// Guest protection is not available on RISC-V. +func availableGuestProtection() (guestProtection, error) { + return noneProtection, nil +} diff --git a/src/runtime/virtcontainers/qemu_riscv64.go b/src/runtime/virtcontainers/qemu_riscv64.go new file mode 100644 index 0000000000..3c66a1d362 --- /dev/null +++ b/src/runtime/virtcontainers/qemu_riscv64.go @@ -0,0 +1,65 @@ +// Copyright (c) 2024 Institute of Software, CAS. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package virtcontainers + +import ( + "fmt" + "time" + + govmmQemu "github.com/kata-containers/kata-containers/src/runtime/pkg/govmm/qemu" +) + +type qemuRiscv64 struct { + // inherit from qemuArchBase, overwrite methods if needed + qemuArchBase +} + +const defaultQemuPath = "/usr/bin/qemu-system-riscv64" + +const defaultQemuMachineType = QemuVirt + +const qmpMigrationWaitTimeout = 10 * time.Second + +const defaultQemuMachineOptions = "accel=kvm,usb=off" + +var kernelParams = []Param{ + {"numa", "off"}, +} + +var supportedQemuMachine = govmmQemu.Machine{ + Type: QemuVirt, + Options: defaultQemuMachineOptions, +} + +func newQemuArch(config HypervisorConfig) (qemuArch, error) { + machineType := config.HypervisorMachineType + if machineType == "" { + machineType = defaultQemuMachineType + } + + if machineType != defaultQemuMachineType { + return nil, fmt.Errorf("unrecognised machinetype: %v", machineType) + } + + q := &qemuRiscv64{ + qemuArchBase{ + qemuMachine: supportedQemuMachine, + qemuExePath: defaultQemuPath, + memoryOffset: config.MemOffset, + kernelParamsNonDebug: kernelParamsNonDebug, + kernelParamsDebug: kernelParamsDebug, + kernelParams: kernelParams, + }, + } + + q.handleImagePath(config) + + return q, nil +} + +func (q *qemuRiscv64) appendIOMMU(devices []govmmQemu.Device) ([]govmmQemu.Device, error) { + return devices, fmt.Errorf("riscv64 does not support appending a vIOMMU") +}