aboutsummaryrefslogtreecommitdiff
path: root/drivers/firmware/kvm-hyp-services.c
blob: 2e62f16aa50d6d95ac51a3c63bbfce86dea6a491 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2022 Google LLC
 */

#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"

static bool kvm_hyp_services_is_supported(void (*invoke_fn)(unsigned long a0, unsigned long a1,
							    unsigned long a2, unsigned long a3,
							    unsigned long a4, unsigned long a5,
							    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;
}

ARM_SMCCC_FEATURE_DRIVER(kvm_hyp_services) = {
	.driver_name = DRIVER_NAME,
	.is_supported = kvm_hyp_services_is_supported,
};

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,
	.probe = kvm_hyp_services_probe,
};