--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Copyright (c) 2019 Western Digital Corporation or its affiliates.
+ *
+ * Authors:
+ *     Atish Patra <atish.patra@wdc.com>
+ */
+
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/kvm_host.h>
+#include <asm/csr.h>
+#include <asm/sbi.h>
+#include <asm/kvm_vcpu_timer.h>
+
+#define SBI_VERSION_MAJOR                      0
+#define SBI_VERSION_MINOR                      1
+
+static void kvm_riscv_vcpu_sbi_forward(struct kvm_vcpu *vcpu,
+                                      struct kvm_run *run)
+{
+       struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
+
+       vcpu->arch.sbi_context.return_handled = 0;
+       vcpu->stat.ecall_exit_stat++;
+       run->exit_reason = KVM_EXIT_RISCV_SBI;
+       run->riscv_sbi.extension_id = cp->a7;
+       run->riscv_sbi.function_id = cp->a6;
+       run->riscv_sbi.args[0] = cp->a0;
+       run->riscv_sbi.args[1] = cp->a1;
+       run->riscv_sbi.args[2] = cp->a2;
+       run->riscv_sbi.args[3] = cp->a3;
+       run->riscv_sbi.args[4] = cp->a4;
+       run->riscv_sbi.args[5] = cp->a5;
+       run->riscv_sbi.ret[0] = cp->a0;
+       run->riscv_sbi.ret[1] = cp->a1;
+}
+
+int kvm_riscv_vcpu_sbi_return(struct kvm_vcpu *vcpu, struct kvm_run *run)
+{
+       struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
+
+       /* Handle SBI return only once */
+       if (vcpu->arch.sbi_context.return_handled)
+               return 0;
+       vcpu->arch.sbi_context.return_handled = 1;
+
+       /* Update return values */
+       cp->a0 = run->riscv_sbi.ret[0];
+       cp->a1 = run->riscv_sbi.ret[1];
+
+       /* Move to next instruction */
+       vcpu->arch.guest_context.sepc += 4;
+
+       return 0;
+}
+
+#ifdef CONFIG_RISCV_SBI_V01
+
+static void kvm_sbi_system_shutdown(struct kvm_vcpu *vcpu,
+                                   struct kvm_run *run, u32 type)
+{
+       int i;
+       struct kvm_vcpu *tmp;
+
+       kvm_for_each_vcpu(i, tmp, vcpu->kvm)
+               tmp->arch.power_off = true;
+       kvm_make_all_cpus_request(vcpu->kvm, KVM_REQ_SLEEP);
+
+       memset(&run->system_event, 0, sizeof(run->system_event));
+       run->system_event.type = type;
+       run->exit_reason = KVM_EXIT_SYSTEM_EVENT;
+}
+
+int kvm_riscv_vcpu_sbi_ecall(struct kvm_vcpu *vcpu, struct kvm_run *run)
+{
+       ulong hmask;
+       int i, ret = 1;
+       u64 next_cycle;
+       struct kvm_vcpu *rvcpu;
+       bool next_sepc = true;
+       struct cpumask cm, hm;
+       struct kvm *kvm = vcpu->kvm;
+       struct kvm_cpu_trap utrap = { 0 };
+       struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
+
+       if (!cp)
+               return -EINVAL;
+
+       switch (cp->a7) {
+       case SBI_EXT_0_1_CONSOLE_GETCHAR:
+       case SBI_EXT_0_1_CONSOLE_PUTCHAR:
+               /*
+                * The CONSOLE_GETCHAR/CONSOLE_PUTCHAR SBI calls cannot be
+                * handled in kernel so we forward these to user-space
+                */
+               kvm_riscv_vcpu_sbi_forward(vcpu, run);
+               next_sepc = false;
+               ret = 0;
+               break;
+       case SBI_EXT_0_1_SET_TIMER:
+#if __riscv_xlen == 32
+               next_cycle = ((u64)cp->a1 << 32) | (u64)cp->a0;
+#else
+               next_cycle = (u64)cp->a0;
+#endif
+               kvm_riscv_vcpu_timer_next_event(vcpu, next_cycle);
+               break;
+       case SBI_EXT_0_1_CLEAR_IPI:
+               kvm_riscv_vcpu_unset_interrupt(vcpu, IRQ_VS_SOFT);
+               break;
+       case SBI_EXT_0_1_SEND_IPI:
+               if (cp->a0)
+                       hmask = kvm_riscv_vcpu_unpriv_read(vcpu, false, cp->a0,
+                                                          &utrap);
+               else
+                       hmask = (1UL << atomic_read(&kvm->online_vcpus)) - 1;
+               if (utrap.scause) {
+                       utrap.sepc = cp->sepc;
+                       kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
+                       next_sepc = false;
+                       break;
+               }
+               for_each_set_bit(i, &hmask, BITS_PER_LONG) {
+                       rvcpu = kvm_get_vcpu_by_id(vcpu->kvm, i);
+                       kvm_riscv_vcpu_set_interrupt(rvcpu, IRQ_VS_SOFT);
+               }
+               break;
+       case SBI_EXT_0_1_SHUTDOWN:
+               kvm_sbi_system_shutdown(vcpu, run, KVM_SYSTEM_EVENT_SHUTDOWN);
+               next_sepc = false;
+               ret = 0;
+               break;
+       case SBI_EXT_0_1_REMOTE_FENCE_I:
+       case SBI_EXT_0_1_REMOTE_SFENCE_VMA:
+       case SBI_EXT_0_1_REMOTE_SFENCE_VMA_ASID:
+               if (cp->a0)
+                       hmask = kvm_riscv_vcpu_unpriv_read(vcpu, false, cp->a0,
+                                                          &utrap);
+               else
+                       hmask = (1UL << atomic_read(&kvm->online_vcpus)) - 1;
+               if (utrap.scause) {
+                       utrap.sepc = cp->sepc;
+                       kvm_riscv_vcpu_trap_redirect(vcpu, &utrap);
+                       next_sepc = false;
+                       break;
+               }
+               cpumask_clear(&cm);
+               for_each_set_bit(i, &hmask, BITS_PER_LONG) {
+                       rvcpu = kvm_get_vcpu_by_id(vcpu->kvm, i);
+                       if (rvcpu->cpu < 0)
+                               continue;
+                       cpumask_set_cpu(rvcpu->cpu, &cm);
+               }
+               riscv_cpuid_to_hartid_mask(&cm, &hm);
+               if (cp->a7 == SBI_EXT_0_1_REMOTE_FENCE_I)
+                       sbi_remote_fence_i(cpumask_bits(&hm));
+               else if (cp->a7 == SBI_EXT_0_1_REMOTE_SFENCE_VMA)
+                       sbi_remote_hfence_vvma(cpumask_bits(&hm),
+                                               cp->a1, cp->a2);
+               else
+                       sbi_remote_hfence_vvma_asid(cpumask_bits(&hm),
+                                               cp->a1, cp->a2, cp->a3);
+               break;
+       default:
+               /* Return error for unsupported SBI calls */
+               cp->a0 = SBI_ERR_NOT_SUPPORTED;
+               break;
+       };
+
+       if (next_sepc)
+               cp->sepc += 4;
+
+       return ret;
+}
+
+#else
+
+int kvm_riscv_vcpu_sbi_ecall(struct kvm_vcpu *vcpu, struct kvm_run *run)
+{
+       kvm_riscv_vcpu_sbi_forward(vcpu, run);
+       return 0;
+}
+
+#endif