162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2021 Western Digital Corporation or its affiliates. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: 662306a36Sopenharmony_ci * Atish Patra <atish.patra@wdc.com> 762306a36Sopenharmony_ci * Anup Patel <anup.patel@wdc.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/errno.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/kvm_host.h> 1362306a36Sopenharmony_ci#include <linux/uaccess.h> 1462306a36Sopenharmony_ci#include <asm/hwcap.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#ifdef CONFIG_FPU 1762306a36Sopenharmony_civoid kvm_riscv_vcpu_fp_reset(struct kvm_vcpu *vcpu) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci struct kvm_cpu_context *cntx = &vcpu->arch.guest_context; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci cntx->sstatus &= ~SR_FS; 2262306a36Sopenharmony_ci if (riscv_isa_extension_available(vcpu->arch.isa, f) || 2362306a36Sopenharmony_ci riscv_isa_extension_available(vcpu->arch.isa, d)) 2462306a36Sopenharmony_ci cntx->sstatus |= SR_FS_INITIAL; 2562306a36Sopenharmony_ci else 2662306a36Sopenharmony_ci cntx->sstatus |= SR_FS_OFF; 2762306a36Sopenharmony_ci} 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic void kvm_riscv_vcpu_fp_clean(struct kvm_cpu_context *cntx) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci cntx->sstatus &= ~SR_FS; 3262306a36Sopenharmony_ci cntx->sstatus |= SR_FS_CLEAN; 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_civoid kvm_riscv_vcpu_guest_fp_save(struct kvm_cpu_context *cntx, 3662306a36Sopenharmony_ci const unsigned long *isa) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci if ((cntx->sstatus & SR_FS) == SR_FS_DIRTY) { 3962306a36Sopenharmony_ci if (riscv_isa_extension_available(isa, d)) 4062306a36Sopenharmony_ci __kvm_riscv_fp_d_save(cntx); 4162306a36Sopenharmony_ci else if (riscv_isa_extension_available(isa, f)) 4262306a36Sopenharmony_ci __kvm_riscv_fp_f_save(cntx); 4362306a36Sopenharmony_ci kvm_riscv_vcpu_fp_clean(cntx); 4462306a36Sopenharmony_ci } 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_civoid kvm_riscv_vcpu_guest_fp_restore(struct kvm_cpu_context *cntx, 4862306a36Sopenharmony_ci const unsigned long *isa) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci if ((cntx->sstatus & SR_FS) != SR_FS_OFF) { 5162306a36Sopenharmony_ci if (riscv_isa_extension_available(isa, d)) 5262306a36Sopenharmony_ci __kvm_riscv_fp_d_restore(cntx); 5362306a36Sopenharmony_ci else if (riscv_isa_extension_available(isa, f)) 5462306a36Sopenharmony_ci __kvm_riscv_fp_f_restore(cntx); 5562306a36Sopenharmony_ci kvm_riscv_vcpu_fp_clean(cntx); 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_civoid kvm_riscv_vcpu_host_fp_save(struct kvm_cpu_context *cntx) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci /* No need to check host sstatus as it can be modified outside */ 6262306a36Sopenharmony_ci if (riscv_isa_extension_available(NULL, d)) 6362306a36Sopenharmony_ci __kvm_riscv_fp_d_save(cntx); 6462306a36Sopenharmony_ci else if (riscv_isa_extension_available(NULL, f)) 6562306a36Sopenharmony_ci __kvm_riscv_fp_f_save(cntx); 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_civoid kvm_riscv_vcpu_host_fp_restore(struct kvm_cpu_context *cntx) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci if (riscv_isa_extension_available(NULL, d)) 7162306a36Sopenharmony_ci __kvm_riscv_fp_d_restore(cntx); 7262306a36Sopenharmony_ci else if (riscv_isa_extension_available(NULL, f)) 7362306a36Sopenharmony_ci __kvm_riscv_fp_f_restore(cntx); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci#endif 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ciint kvm_riscv_vcpu_get_reg_fp(struct kvm_vcpu *vcpu, 7862306a36Sopenharmony_ci const struct kvm_one_reg *reg, 7962306a36Sopenharmony_ci unsigned long rtype) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct kvm_cpu_context *cntx = &vcpu->arch.guest_context; 8262306a36Sopenharmony_ci unsigned long __user *uaddr = 8362306a36Sopenharmony_ci (unsigned long __user *)(unsigned long)reg->addr; 8462306a36Sopenharmony_ci unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK | 8562306a36Sopenharmony_ci KVM_REG_SIZE_MASK | 8662306a36Sopenharmony_ci rtype); 8762306a36Sopenharmony_ci void *reg_val; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if ((rtype == KVM_REG_RISCV_FP_F) && 9062306a36Sopenharmony_ci riscv_isa_extension_available(vcpu->arch.isa, f)) { 9162306a36Sopenharmony_ci if (KVM_REG_SIZE(reg->id) != sizeof(u32)) 9262306a36Sopenharmony_ci return -EINVAL; 9362306a36Sopenharmony_ci if (reg_num == KVM_REG_RISCV_FP_F_REG(fcsr)) 9462306a36Sopenharmony_ci reg_val = &cntx->fp.f.fcsr; 9562306a36Sopenharmony_ci else if ((KVM_REG_RISCV_FP_F_REG(f[0]) <= reg_num) && 9662306a36Sopenharmony_ci reg_num <= KVM_REG_RISCV_FP_F_REG(f[31])) 9762306a36Sopenharmony_ci reg_val = &cntx->fp.f.f[reg_num]; 9862306a36Sopenharmony_ci else 9962306a36Sopenharmony_ci return -ENOENT; 10062306a36Sopenharmony_ci } else if ((rtype == KVM_REG_RISCV_FP_D) && 10162306a36Sopenharmony_ci riscv_isa_extension_available(vcpu->arch.isa, d)) { 10262306a36Sopenharmony_ci if (reg_num == KVM_REG_RISCV_FP_D_REG(fcsr)) { 10362306a36Sopenharmony_ci if (KVM_REG_SIZE(reg->id) != sizeof(u32)) 10462306a36Sopenharmony_ci return -EINVAL; 10562306a36Sopenharmony_ci reg_val = &cntx->fp.d.fcsr; 10662306a36Sopenharmony_ci } else if ((KVM_REG_RISCV_FP_D_REG(f[0]) <= reg_num) && 10762306a36Sopenharmony_ci reg_num <= KVM_REG_RISCV_FP_D_REG(f[31])) { 10862306a36Sopenharmony_ci if (KVM_REG_SIZE(reg->id) != sizeof(u64)) 10962306a36Sopenharmony_ci return -EINVAL; 11062306a36Sopenharmony_ci reg_val = &cntx->fp.d.f[reg_num]; 11162306a36Sopenharmony_ci } else 11262306a36Sopenharmony_ci return -ENOENT; 11362306a36Sopenharmony_ci } else 11462306a36Sopenharmony_ci return -ENOENT; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (copy_to_user(uaddr, reg_val, KVM_REG_SIZE(reg->id))) 11762306a36Sopenharmony_ci return -EFAULT; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci return 0; 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ciint kvm_riscv_vcpu_set_reg_fp(struct kvm_vcpu *vcpu, 12362306a36Sopenharmony_ci const struct kvm_one_reg *reg, 12462306a36Sopenharmony_ci unsigned long rtype) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct kvm_cpu_context *cntx = &vcpu->arch.guest_context; 12762306a36Sopenharmony_ci unsigned long __user *uaddr = 12862306a36Sopenharmony_ci (unsigned long __user *)(unsigned long)reg->addr; 12962306a36Sopenharmony_ci unsigned long reg_num = reg->id & ~(KVM_REG_ARCH_MASK | 13062306a36Sopenharmony_ci KVM_REG_SIZE_MASK | 13162306a36Sopenharmony_ci rtype); 13262306a36Sopenharmony_ci void *reg_val; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if ((rtype == KVM_REG_RISCV_FP_F) && 13562306a36Sopenharmony_ci riscv_isa_extension_available(vcpu->arch.isa, f)) { 13662306a36Sopenharmony_ci if (KVM_REG_SIZE(reg->id) != sizeof(u32)) 13762306a36Sopenharmony_ci return -EINVAL; 13862306a36Sopenharmony_ci if (reg_num == KVM_REG_RISCV_FP_F_REG(fcsr)) 13962306a36Sopenharmony_ci reg_val = &cntx->fp.f.fcsr; 14062306a36Sopenharmony_ci else if ((KVM_REG_RISCV_FP_F_REG(f[0]) <= reg_num) && 14162306a36Sopenharmony_ci reg_num <= KVM_REG_RISCV_FP_F_REG(f[31])) 14262306a36Sopenharmony_ci reg_val = &cntx->fp.f.f[reg_num]; 14362306a36Sopenharmony_ci else 14462306a36Sopenharmony_ci return -ENOENT; 14562306a36Sopenharmony_ci } else if ((rtype == KVM_REG_RISCV_FP_D) && 14662306a36Sopenharmony_ci riscv_isa_extension_available(vcpu->arch.isa, d)) { 14762306a36Sopenharmony_ci if (reg_num == KVM_REG_RISCV_FP_D_REG(fcsr)) { 14862306a36Sopenharmony_ci if (KVM_REG_SIZE(reg->id) != sizeof(u32)) 14962306a36Sopenharmony_ci return -EINVAL; 15062306a36Sopenharmony_ci reg_val = &cntx->fp.d.fcsr; 15162306a36Sopenharmony_ci } else if ((KVM_REG_RISCV_FP_D_REG(f[0]) <= reg_num) && 15262306a36Sopenharmony_ci reg_num <= KVM_REG_RISCV_FP_D_REG(f[31])) { 15362306a36Sopenharmony_ci if (KVM_REG_SIZE(reg->id) != sizeof(u64)) 15462306a36Sopenharmony_ci return -EINVAL; 15562306a36Sopenharmony_ci reg_val = &cntx->fp.d.f[reg_num]; 15662306a36Sopenharmony_ci } else 15762306a36Sopenharmony_ci return -ENOENT; 15862306a36Sopenharmony_ci } else 15962306a36Sopenharmony_ci return -ENOENT; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci if (copy_from_user(reg_val, uaddr, KVM_REG_SIZE(reg->id))) 16262306a36Sopenharmony_ci return -EFAULT; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci} 166