aboutsummaryrefslogtreecommitdiff
path: root/drivers/firmware/kvm-hyp-services.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware/kvm-hyp-services.c')
-rw-r--r--drivers/firmware/kvm-hyp-services.c98
1 files changed, 97 insertions, 1 deletions
diff --git a/drivers/firmware/kvm-hyp-services.c b/drivers/firmware/kvm-hyp-services.c
index bfb5ab5fba1..2e62f16aa50 100644
--- a/drivers/firmware/kvm-hyp-services.c
+++ b/drivers/firmware/kvm-hyp-services.c
@@ -5,8 +5,14 @@
#include <common.h>
#include <dm.h>
+#include <malloc.h>
+#include <virtio_types.h>
+#include <virtio.h>
+#include <asm/io.h>
#include <dm/device_compat.h>
#include <linux/arm-smccc.h>
+#include <linux/compat.h>
+#include <linux/psci.h>
#define DRIVER_NAME "kvm-hyp-services"
@@ -16,6 +22,23 @@ static bool kvm_hyp_services_is_supported(void (*invoke_fn)(unsigned long a0, un
unsigned long a6, unsigned long a7,
struct arm_smccc_res *res))
{
+ u32 smccc_version = ARM_SMCCC_VERSION_1_0;
+ struct arm_smccc_res res;
+
+ smccc_version = invoke_psci_fn(ARM_SMCCC_VERSION_FUNC_ID, 0, 0, 0);
+
+ if (smccc_version < ARM_SMCCC_VERSION_1_1)
+ return false;
+
+ (*invoke_fn)(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID,
+ 0, 0, 0, 0, 0, 0, 0, &res);
+
+ if (res.a0 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_0 ||
+ res.a1 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_1 ||
+ res.a2 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_2 ||
+ res.a3 != ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_3)
+ return false;
+
return true;
}
@@ -24,11 +47,84 @@ ARM_SMCCC_FEATURE_DRIVER(kvm_hyp_services) = {
.is_supported = kvm_hyp_services_is_supported,
};
-static int kvm_hyp_services_probe(struct udevice *dev)
+extern struct virtio_iommu_platform_ops *virtio_iommu_platform_ops;
+
+static int kvm_hyp_mem_share(struct udevice *udev, void *addr, u32 npages)
+{
+ struct psci_plat_data *smccc = dev_get_parent_plat(udev);
+
+ while (npages--) {
+ struct arm_smccc_res res;
+ phys_addr_t phys = virt_to_phys(addr);
+
+ smccc->invoke_fn(ARM_SMCCC_VENDOR_HYP_KVM_MEM_SHARE_FUNC_ID,
+ phys, 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != SMCCC_RET_SUCCESS)
+ return -EPERM;
+
+ addr += PAGE_SIZE;
+ }
+
+ return 0;
+}
+
+static int kvm_hyp_mem_unshare(struct udevice *udev, void *addr, u32 npages)
+{
+ struct psci_plat_data *smccc = dev_get_parent_plat(udev);
+
+ while (npages--) {
+ struct arm_smccc_res res;
+ phys_addr_t phys = virt_to_phys(addr);
+
+ smccc->invoke_fn(ARM_SMCCC_VENDOR_HYP_KVM_MEM_UNSHARE_FUNC_ID,
+ phys, 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0 != SMCCC_RET_SUCCESS)
+ return -EPERM;
+
+ addr += PAGE_SIZE;
+ }
+
+ return 0;
+}
+
+static int kvm_hyp_memshare_init(unsigned long features, struct psci_plat_data *smccc)
{
+ static struct virtio_iommu_platform_ops ops = {
+ .map = kvm_hyp_mem_share,
+ .unmap = kvm_hyp_mem_unshare,
+ };
+ struct arm_smccc_res res;
+
+ smccc->invoke_fn(ARM_SMCCC_VENDOR_HYP_KVM_HYP_MEMINFO_FUNC_ID,
+ 0, 0, 0, 0, 0, 0, 0, &res);
+
+ if (res.a0 != PAGE_SIZE)
+ return -ENXIO;
+
+ virtio_iommu_platform_ops = &ops;
return 0;
}
+static int kvm_hyp_services_probe(struct udevice *dev)
+{
+ int ret = 0;
+ struct psci_plat_data *smccc = dev_get_parent_plat(dev);
+ struct arm_smccc_res res;
+
+ if (!(kvm_hyp_services_is_supported(smccc->invoke_fn)))
+ return -ENXIO;
+
+ memset(&res, 0, sizeof(res));
+ smccc->invoke_fn(ARM_SMCCC_VENDOR_HYP_KVM_FEATURES_FUNC_ID,
+ 0, 0, 0, 0, 0, 0, 0, &res);
+
+ if (res.a0 & BIT(ARM_SMCCC_KVM_FUNC_HYP_MEMINFO))
+ ret = kvm_hyp_memshare_init(res.a0, smccc);
+
+ pr_debug("Probed KVM hypervisor services: 0x%08x\n", (u32)res.a0);
+ return ret;
+}
+
U_BOOT_DRIVER(kvm_hyp_services) = {
.name = DRIVER_NAME,
.id = UCLASS_FIRMWARE,